From e1da73a18c60ee10204e22c8da9ed1d9626cbc46 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:04:13 -0400 Subject: [PATCH 001/242] wai-ops-npu: wai-ops/1-npu on a neural processor's compute core, exact on every subset vector A new crate, wai/wai-ops-npu, with no dependency but wai-ops. Its kernel runs on one compute core of a tile array, built at run time with the array's open toolchain; its host side is compiled against the device runtime and loaded. The interpreter runs each graph through wai_ops::run_with and asks the core for every arithmetic operator's outputs: conv and conv_transpose as multiply- accumulate lines (8- and 16-bit operands into 32-bit accumulator lanes, 32-bit operands into 64-bit lanes, the width the certificate's mac record allows), rescale, chscale and mul through the unit's shift-round step under the mode that is the operator's rounding, the elementwise operators, and lut16 a table block at a time. Movement, policies, checks and state stay the interpreter's. The unit's shift-round step is exact for shifts 0..59 only: it takes 60..63 as a left shift by 64 - s (measured), so those shifts go through the core's scalar unit in 64-bit arithmetic. Tests: the corpus (every graph vector in the subset, against the committed results and the interpreter), the rounding, saturation, lane and requantisation probes, twelve kernel edits the corpus must catch, and the graphs outside the subset by acc32 alone, which are not exact here. Co-Authored-By: Claude Opus 5.5 --- wai/wai-ops-npu/Cargo.lock | 14 + wai/wai-ops-npu/Cargo.toml | 29 ++ wai/wai-ops-npu/src/device.rs | 538 ++++++++++++++++++++++++ wai/wai-ops-npu/src/exec.rs | 446 ++++++++++++++++++++ wai/wai-ops-npu/src/kernel/design.py | 95 +++++ wai/wai-ops-npu/src/kernel/host.cpp | 131 ++++++ wai/wai-ops-npu/src/kernel/ops_npu.cc | 333 +++++++++++++++ wai/wai-ops-npu/src/lib.rs | 34 ++ wai/wai-ops-npu/src/lines.rs | 389 +++++++++++++++++ wai/wai-ops-npu/src/probe.rs | 153 +++++++ wai/wai-ops-npu/tests/corpus.rs | 86 ++++ wai/wai-ops-npu/tests/mutants.rs | 49 +++ wai/wai-ops-npu/tests/probe.rs | 142 +++++++ wai/wai-ops-npu/tests/support/corpus.rs | 227 ++++++++++ wai/wai-ops-npu/tests/support/rq.rs | 93 ++++ wai/wai-ops-npu/tests/width.rs | 38 ++ 16 files changed, 2797 insertions(+) create mode 100644 wai/wai-ops-npu/Cargo.lock create mode 100644 wai/wai-ops-npu/Cargo.toml create mode 100644 wai/wai-ops-npu/src/device.rs create mode 100644 wai/wai-ops-npu/src/exec.rs create mode 100644 wai/wai-ops-npu/src/kernel/design.py create mode 100644 wai/wai-ops-npu/src/kernel/host.cpp create mode 100644 wai/wai-ops-npu/src/kernel/ops_npu.cc create mode 100644 wai/wai-ops-npu/src/lib.rs create mode 100644 wai/wai-ops-npu/src/lines.rs create mode 100644 wai/wai-ops-npu/src/probe.rs create mode 100644 wai/wai-ops-npu/tests/corpus.rs create mode 100644 wai/wai-ops-npu/tests/mutants.rs create mode 100644 wai/wai-ops-npu/tests/probe.rs create mode 100644 wai/wai-ops-npu/tests/support/corpus.rs create mode 100644 wai/wai-ops-npu/tests/support/rq.rs create mode 100644 wai/wai-ops-npu/tests/width.rs diff --git a/wai/wai-ops-npu/Cargo.lock b/wai/wai-ops-npu/Cargo.lock new file mode 100644 index 000000000..a145dfc40 --- /dev/null +++ b/wai/wai-ops-npu/Cargo.lock @@ -0,0 +1,14 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "wai-ops" +version = "0.1.0" + +[[package]] +name = "wai-ops-npu" +version = "0.1.0" +dependencies = [ + "wai-ops", +] diff --git a/wai/wai-ops-npu/Cargo.toml b/wai/wai-ops-npu/Cargo.toml new file mode 100644 index 000000000..659e77b39 --- /dev/null +++ b/wai/wai-ops-npu/Cargo.toml @@ -0,0 +1,29 @@ +# `wai-ops/1-npu` on neural processors: the integer operator set's arithmetic +# operators as kernels for one compute core of a tile array, built at run time +# with the array's open toolchain and run through its device runtime, exact for +# every graph of the accelerator subset (extensions/integer-operators.md §9.2, +# §9.5). +# +# A SEPARATE crate with its own lockfile and no dependency but `wai-ops`, so +# that `wai-ops` stays `no_std` and `wai-rs` never sees a device runtime. The +# device is found, and the program and host side are built, only on Linux with +# the device present; everywhere else the crate builds and its device tests +# say SKIPPED. +[package] +name = "wai-ops-npu" +version = "0.1.0" +edition = "2024" +rust-version = "1.97" +license = "Apache-2.0" +publish = false +description = "WAI integer operator set wai-ops/1-npu on neural processors: tile-array kernels exact for the accelerator subset" + +[lib] +name = "wai_ops_npu" +path = "src/lib.rs" + +[dependencies] +wai-ops = { path = "../wai-ops" } + +[profile.release] +debug = false diff --git a/wai/wai-ops-npu/src/device.rs b/wai/wai-ops-npu/src/device.rs new file mode 100644 index 000000000..327a2f907 --- /dev/null +++ b/wai/wai-ops-npu/src/device.rs @@ -0,0 +1,538 @@ +//! Finding a neural processor, building its program, and dispatching lines to it. +//! +//! - **Discovery** ([`discover`]): a device is found in the kernel's accelerator class +//! (`/sys/class/accel`) by the driver the device runtime drives. Its program is built +//! with the tile array's open toolchain, whose Python the host names +//! (`WAI_OPS_NPU_PYTHON`), into a cache directory keyed by the program's sources, its +//! layout and the toolchain (`WAI_OPS_NPU_CACHE`, else the system's temporary +//! directory). The host side ([`HOST`]) is compiled with the system's C++ compiler +//! (`CXX`, else `c++`) against the device runtime and loaded. A host with no such +//! device finds none and says why ([`Discovery::unavailable`]). +//! - **Dispatch** ([`Npu::dispatch`]): lines go to the core [`LINES`] at a time, the +//! last dispatch padded with lines that do nothing; each line's [`LOUT`] output +//! words come back. +//! +//! One compute core of one column runs every line; the array has many. + +use std::ffi::{CString, c_char, c_int, c_void}; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::{Mutex, OnceLock}; + +use crate::lines::{FILL, LIN, LINES, LOUT, Line}; + +/// The kernel, exactly as it is compiled for the core. +pub const KERNEL: &str = include_str!("kernel/ops_npu.cc"); +/// The program around the kernel, run by the toolchain's Python to build it. +pub const DESIGN: &str = include_str!("kernel/design.py"); +/// The host side, compiled against the device runtime and loaded. +pub const HOST: &str = include_str!("kernel/host.cpp"); + +/// The driver whose devices the runtime drives. +const DRIVER: &str = "amdxdna"; // allow-name: the kernel driver's name, as sysfs gives it + +/// The kernels a program can hold: the conforming one, or one edit of it that the +/// conformance corpus must catch. Each is its own compiled program. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct Variant(pub u32); + +impl Variant { + pub const CONFORMING: Variant = Variant(0); + + /// Every edit of the kernel (`MUT` in `kernel/ops_npu.cc`), with what it does. + pub const MUTANTS: [(Variant, &'static str); 12] = [ + (Variant(1), "half_up rounded by the unit's ties-away-from-zero mode"), + (Variant(2), "half_even rounded by the unit's ties-to-odd mode"), + (Variant(3), "floor rounded toward zero"), + (Variant(4), "the requantisation shift one more"), + (Variant(5), "the post bias dropped"), + (Variant(6), "the accumulator not started at the pre bias"), + (Variant(7), "clamp's upper bound one less"), + (Variant(8), "leaky's product rounded toward zero"), + (Variant(9), "the table read one entry on"), + (Variant(10), "the last tap of each sum dropped"), + (Variant(11), "relu as max(v, -1)"), + (Variant(12), "shifts of 60 to 63 left to the unit's shift-round step"), + ]; + + pub fn label(self) -> String { + if self.0 == 0 { "conforming".into() } else { format!("m{}", self.0) } + } +} + +/// Why there is no device to run on, or a dispatch failed. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum NpuError { + /// No device, no toolchain, or no runtime, and why. + Unavailable(String), + /// The program or the host side did not build: the tool's output. + Build(String), + /// The runtime reported an error. + Device(String), +} + +impl std::fmt::Display for NpuError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + NpuError::Unavailable(why) => write!(f, "no neural processor to drive: {why}"), + NpuError::Build(why) => write!(f, "the build failed: {why}"), + NpuError::Device(why) => write!(f, "device: {why}"), + } + } +} + +impl std::error::Error for NpuError {} + +/// A device in the accelerator class, as sysfs describes it. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Found { + pub path: PathBuf, + pub vendor: u16, + pub device: u16, + pub revision: u8, + pub firmware: String, +} + +impl Found { + /// `"npu--"`, the PCI ids in hex: the key a runner declares. + pub fn key(&self) -> String { + format!("npu-{:04x}-{:04x}", self.vendor, self.device) + } +} + +/// The toolchain's name for a device's tile-array generation, by its PCI device id, for +/// the ones this program is written for. +pub fn generation(device: u16) -> Option<&'static str> { + match device { + 0x1502 => Some("npu1"), + 0x17f0 => Some("npu2"), + _ => None, + } +} + +/// The accelerator-class devices under `root` (normally `/sys/class/accel`) whose +/// driver is the one the runtime drives. +pub fn scan(root: &Path) -> Vec { + let read = |p: PathBuf| std::fs::read_to_string(p).map(|s| s.trim().to_string()).unwrap_or_default(); + let num = |s: String| u32::from_str_radix(s.trim_start_matches("0x"), 16).ok(); + let mut out = Vec::new(); + let Ok(entries) = std::fs::read_dir(root) else { return out }; + let mut names: Vec = entries.filter_map(|e| e.ok().map(|e| e.path())).collect(); + names.sort(); + for p in names { + let dev = p.join("device"); + let driver = std::fs::read_link(dev.join("driver")).ok().and_then(|l| l.file_name().map(|n| n.to_string_lossy().into_owned())).unwrap_or_default(); + if driver != DRIVER { + continue; + } + let (Some(vendor), Some(device)) = (num(read(dev.join("vendor"))), num(read(dev.join("device")))) else { continue }; + out.push(Found { + path: p.clone(), + vendor: vendor as u16, + device: device as u16, + revision: num(read(dev.join("revision"))).unwrap_or(0) as u8, + firmware: read(dev.join("fw_version")), + }); + } + out +} + +/// The host side's entry points. +struct Host { + name: unsafe extern "C" fn(*mut c_char, usize, *mut c_char, usize) -> c_int, + open: unsafe extern "C" fn(*const c_char, *const c_char, *const c_char, usize, usize, *mut *mut c_void, *mut c_char, usize) -> c_int, + run: unsafe extern "C" fn(*mut c_void, *const i32, *mut i32, i32, *mut c_char, usize) -> c_int, + close: unsafe extern "C" fn(*mut c_void), +} + +#[cfg(target_os = "linux")] +mod dl { + use std::ffi::{c_char, c_int, c_void}; + #[link(name = "dl")] + unsafe extern "C" { + pub fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void; + pub fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void; + pub fn dlerror() -> *const c_char; + } + pub const RTLD_NOW: c_int = 2; +} + +/// `p` as the function pointer type `F`, which the caller names from the symbol's +/// declaration. +/// +/// # Safety +/// +/// `p` must be a symbol of exactly the signature `F` is. +#[cfg(target_os = "linux")] +unsafe fn as_fn(p: *mut c_void) -> F { + assert_eq!(std::mem::size_of::(), std::mem::size_of::<*mut c_void>(), "a function pointer"); + // SAFETY: the sizes agree, and the caller names the symbol's signature. + unsafe { std::mem::transmute_copy(&p) } +} + +#[cfg(target_os = "linux")] +fn load_host(lib: &Path) -> Result { + let c = CString::new(lib.as_os_str().as_encoded_bytes()).map_err(|_| "a path holding a NUL".to_string())?; + // SAFETY: dlopen and dlsym with valid C strings; each symbol is cast to the + // signature the host side declares. + unsafe { + let h = dl::dlopen(c.as_ptr(), dl::RTLD_NOW); + if h.is_null() { + let e = dl::dlerror(); + return Err(if e.is_null() { format!("{}: cannot load", lib.display()) } else { std::ffi::CStr::from_ptr(e).to_string_lossy().into_owned() }); + } + let sym = |name: &str| -> Result<*mut c_void, String> { + let n = CString::new(name).expect("no NUL"); + let p = dl::dlsym(h, n.as_ptr()); + if p.is_null() { Err(format!("the host side has no {name}")) } else { Ok(p) } + }; + Ok(Host { name: as_fn(sym("wo_npu_name")?), open: as_fn(sym("wo_npu_open")?), run: as_fn(sym("wo_npu_run")?), close: as_fn(sym("wo_npu_close")?) }) + } +} + +#[cfg(not(target_os = "linux"))] +fn load_host(_lib: &Path) -> Result { + Err(format!("the device runtime is loaded on Linux; this is {}", std::env::consts::OS)) +} + +fn hex(bytes: &[u8]) -> String { + bytes.iter().map(|b| format!("{b:02x}")).collect() +} + +fn digest(parts: &[&[u8]]) -> String { + let mut o = Vec::new(); + for p in parts { + o.extend_from_slice(&(p.len() as u64).to_be_bytes()); + o.extend_from_slice(p); + } + hex(&wai_ops::sha256::sha256(&o)[..8]) +} + +fn cache_base() -> PathBuf { + std::env::var_os("WAI_OPS_NPU_CACHE").filter(|s| !s.is_empty()).map(PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("wai-ops-npu")) +} + +/// The system's C++ compiler and its version line. +fn cxx() -> Result<(String, String), String> { + let cxx = std::env::var("CXX").unwrap_or_else(|_| "c++".into()); + let out = Command::new(&cxx).arg("--version").output().map_err(|e| format!("no C++ compiler {cxx}: {e}"))?; + Ok((cxx, String::from_utf8_lossy(&out.stdout).lines().next().unwrap_or("").to_string())) +} + +/// The toolchain's Python, and its versions as it reports them. +fn toolchain() -> Result<(PathBuf, String), String> { + let py = std::env::var_os("WAI_OPS_NPU_PYTHON") + .filter(|s| !s.is_empty()) + .map(PathBuf::from) + .ok_or("no toolchain named (WAI_OPS_NPU_PYTHON, the Python of an environment with the tile array's toolchain)")?; + let dir = cache_base().join("design"); + std::fs::create_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?; + let script = dir.join(format!("design-{}.py", digest(&[DESIGN.as_bytes()]))); + if !script.exists() { + std::fs::write(&script, DESIGN).map_err(|e| format!("{}: {e}", script.display()))?; + } + let out = Command::new(&py).arg(&script).arg("--versions").output().map_err(|e| format!("{}: {e}", py.display()))?; + let versions = String::from_utf8_lossy(&out.stdout).trim().to_string(); + if !out.status.success() || versions.is_empty() || versions.contains("absent") { + return Err(format!("{} does not have the toolchain: {versions} {}", py.display(), String::from_utf8_lossy(&out.stderr).trim())); + } + Ok((py, versions)) +} + +/// Builds into a staging directory beside `dir` with `build`, then moves it into +/// place, so that a concurrent build never sees half a directory. +fn build_into(dir: &Path, build: impl FnOnce(&Path) -> Result<(), String>) -> Result<(), String> { + if dir.exists() { + return Ok(()); + } + let staging = dir.with_extension(format!("building-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&staging); + std::fs::create_dir_all(&staging).map_err(|e| format!("{}: {e}", staging.display()))?; + if let Err(e) = build(&staging) { + let _ = std::fs::remove_dir_all(&staging); + return Err(e); + } + match std::fs::rename(&staging, dir) { + Ok(()) => Ok(()), + Err(_) if dir.exists() => { + let _ = std::fs::remove_dir_all(&staging); + Ok(()) + } + Err(e) => Err(format!("{} into {}: {e}", staging.display(), dir.display())), + } +} + +/// The identity of the program a variant builds: the sources, the line layout, the +/// variant, the generation and the toolchain. +pub fn program_id(v: Variant, generation: &str, toolchain: &str) -> String { + let layout = format!("{LIN} {LOUT} {LINES} {}", v.0); + digest(&[KERNEL.as_bytes(), DESIGN.as_bytes(), layout.as_bytes(), generation.as_bytes(), toolchain.as_bytes()]) +} + +/// The variant's program (`final.xclbin`, `insts.bin`), from the cache or built into it. +fn program(py: &Path, toolchain: &str, generation: &str, v: Variant) -> Result { + let dir = cache_base().join(format!("program-{}-{}", v.label(), program_id(v, generation, toolchain))); + build_into(&dir, |s| { + std::fs::write(s.join("ops_npu.cc"), KERNEL).map_err(|e| e.to_string())?; + std::fs::write(s.join("design.py"), DESIGN).map_err(|e| e.to_string())?; + let out = Command::new(py) + .current_dir(s) + .args(["design.py", "--kernel", "ops_npu.cc", "--device", generation, "--xclbin", "final.xclbin", "--insts", "insts.bin"]) + .args(["--lin", &LIN.to_string(), "--lout", &LOUT.to_string(), "--lines", &LINES.to_string(), "--mut", &v.0.to_string()]) + .output() + .map_err(|e| format!("{}: {e}", py.display()))?; + if !out.status.success() || !s.join("final.xclbin").exists() || !s.join("insts.bin").exists() { + let mut log = String::from_utf8_lossy(&out.stderr).into_owned(); + log.push_str(&String::from_utf8_lossy(&out.stdout)); + let tail: Vec<&str> = log.lines().filter(|l| l.contains("error") || l.contains("Error")).take(12).collect(); + return Err(if tail.is_empty() { log.lines().rev().take(20).collect::>().join("\n") } else { tail.join("\n") }); + } + Ok(()) + }) + .map_err(NpuError::Build)?; + Ok(dir) +} + +/// The host side, compiled once per process into the cache and loaded. +fn host() -> Result<&'static Host, NpuError> { + static HOST_LIB: OnceLock> = OnceLock::new(); + HOST_LIB + .get_or_init(|| { + let (cxx, version) = cxx()?; + let dir = cache_base().join(format!("host-{}", digest(&[HOST.as_bytes(), version.as_bytes()]))); + build_into(&dir, |s| { + std::fs::write(s.join("host.cpp"), HOST).map_err(|e| e.to_string())?; + let out = Command::new(&cxx) + .current_dir(s) + .args(["-O2", "-shared", "-fPIC", "-std=c++17", "host.cpp", "-o", "libwonpu.so", "-lxrt_coreutil"]) + .output() + .map_err(|e| format!("{cxx}: {e}"))?; + if !out.status.success() { + return Err(format!("the host side did not compile against the device runtime: {}", String::from_utf8_lossy(&out.stderr).lines().take(12).collect::>().join("\n"))); + } + Ok(()) + })?; + load_host(&dir.join("libwonpu.so")) + }) + .as_ref() + .map_err(|e| NpuError::Unavailable(e.clone())) +} + +fn message(buf: &[u8]) -> String { + let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len()); + String::from_utf8_lossy(&buf[..end]).into_owned() +} + +/// A program open on the device: a hardware context over it, which the runtime holds +/// until it is dropped. +struct Context { + host: &'static Host, + handle: Mutex<*mut c_void>, +} + +// SAFETY: the handle is only used under the mutex, and the runtime's objects behind it +// may be used from any thread one at a time. +unsafe impl Send for Context {} +// SAFETY: as above. +unsafe impl Sync for Context {} + +impl Context { + fn open(host: &'static Host, dir: &Path) -> Result { + let c = |p: PathBuf| CString::new(p.as_os_str().as_encoded_bytes()).map_err(|_| NpuError::Device("a path holding a NUL".into())); + let (xclbin, insts) = (c(dir.join("final.xclbin"))?, c(dir.join("insts.bin"))?); + let kernel = CString::new("MLIR_AIE").expect("no NUL"); + let mut err = [0u8; 1024]; + let mut h = std::ptr::null_mut(); + // SAFETY: valid C strings and an error buffer of its stated length. + let r = unsafe { (host.open)(xclbin.as_ptr(), insts.as_ptr(), kernel.as_ptr(), LINES * LIN, LINES * LOUT, &mut h, err.as_mut_ptr().cast(), err.len()) }; + if r != 0 { + return Err(NpuError::Device(message(&err))); + } + Ok(Context { host, handle: Mutex::new(h) }) + } + + fn run(&self, input: &[i32]) -> Result, NpuError> { + assert_eq!(input.len(), LINES * LIN); + let mut out = vec![0i32; LINES * LOUT]; + let mut err = [0u8; 1024]; + let h = self.handle.lock().unwrap_or_else(|p| p.into_inner()); + // SAFETY: the handle is open; the buffers hold the words it was opened with. + let r = unsafe { (self.host.run)(*h, input.as_ptr(), out.as_mut_ptr(), FILL, err.as_mut_ptr().cast(), err.len()) }; + if r != 0 { + return Err(NpuError::Device(message(&err))); + } + Ok(out) + } +} + +impl Drop for Context { + fn drop(&mut self) { + let h = *self.handle.get_mut().unwrap_or_else(|p| p.into_inner()); + // SAFETY: the handle came from open and is closed once. + unsafe { (self.host.close)(h) }; + } +} + +/// What a device is, as a report states it. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Identity { + pub found: Found, + /// The device's name as its runtime gives it. + pub name: String, + /// The tile-array generation the program was built for. + pub generation: &'static str, + /// The toolchain that built the program, one line per package. + pub toolchain: String, +} + +impl Identity { + pub fn describe(&self) -> String { + format!( + "device \"{}\" ({:04x}:{:04x} rev {:02x}), the tile array's {} generation, firmware {}; one compute core; toolchain {}", + self.name, + self.found.vendor, + self.found.device, + self.found.revision, + self.generation, + self.found.firmware, + self.toolchain.replace('\n', ", ") + ) + } +} + +/// A neural processor with one variant's program open on it. +pub struct Npu { + identity: Identity, + variant: Variant, + python: PathBuf, + context: Context, + dispatches: std::sync::atomic::AtomicU64, +} + +impl Npu { + pub fn identity(&self) -> &Identity { + &self.identity + } + pub fn variant(&self) -> Variant { + self.variant + } + pub fn key(&self) -> String { + self.identity.found.key() + } + /// Dispatches made so far. + pub fn dispatches(&self) -> u64 { + self.dispatches.load(std::sync::atomic::Ordering::Relaxed) + } + + fn open(f: &Found, v: Variant) -> Result { + let generation = generation(f.device).ok_or_else(|| NpuError::Unavailable(format!("device {:04x}:{:04x} is not a generation this program is written for", f.vendor, f.device)))?; + let (python, toolchain) = toolchain().map_err(NpuError::Unavailable)?; + let host = host()?; + let dir = program(&python, &toolchain, generation, v)?; + let context = Context::open(host, &dir)?; + let mut name = [0u8; 256]; + let mut err = [0u8; 1024]; + // SAFETY: buffers of their stated lengths. + let r = unsafe { (host.name)(name.as_mut_ptr().cast(), name.len(), err.as_mut_ptr().cast(), err.len()) }; + if r != 0 { + return Err(NpuError::Device(message(&err))); + } + Ok(Npu { identity: Identity { found: f.clone(), name: message(&name), generation, toolchain }, variant: v, python, context, dispatches: Default::default() }) + } + + /// The same device with another variant's program: a mutant, for tests. + pub fn with_variant(&self, v: Variant) -> Result { + let host = host()?; + let dir = program(&self.python, &self.identity.toolchain, self.identity.generation, v)?; + let context = Context::open(host, &dir)?; + Ok(Npu { identity: self.identity.clone(), variant: v, python: self.python.clone(), context, dispatches: Default::default() }) + } + + /// Run `lines` on the core, [`LINES`] per dispatch, and return each line's output + /// words, in order. + pub fn dispatch(&self, lines: &[Line]) -> Result>, NpuError> { + let mut out = Vec::with_capacity(lines.len()); + let nop = Line::nop(); + for group in lines.chunks(LINES) { + let mut input = Vec::with_capacity(LINES * LIN); + for k in 0..LINES { + let l = group.get(k).unwrap_or(&nop); + assert_eq!(l.0.len(), LIN); + input.extend_from_slice(&l.0); + } + let words = self.context.run(&input)?; + self.dispatches.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + for k in 0..group.len() { + out.push(words[k * LOUT..(k + 1) * LOUT].to_vec()); + } + } + Ok(out) + } +} + +/// A device that could not be opened, and why. +#[derive(Debug)] +pub struct Refused { + pub device: String, + pub key: String, + pub error: NpuError, +} + +/// What [`discover`] found. +pub struct Discovery { + pub found: Vec, + pub refused: Vec, + pub unavailable: Option, +} + +/// The neural processors on this host, each with the conforming program open; or why +/// there is none. +pub fn discover() -> Discovery { + let mut d = Discovery { found: Vec::new(), refused: Vec::new(), unavailable: None }; + let devices = scan(Path::new("/sys/class/accel")); + if devices.is_empty() { + d.unavailable = Some(format!("no accelerator-class device whose driver is {DRIVER}")); + return d; + } + for (k, f) in devices.into_iter().enumerate() { + let key = f.key(); + if k > 0 { + d.refused.push(Refused { device: f.path.display().to_string(), key, error: NpuError::Unavailable("the runtime opens the first device only".into()) }); + continue; + } + match Npu::open(&f, Variant::CONFORMING) { + Ok(n) => d.found.push(n), + Err(error) => d.refused.push(Refused { device: f.path.display().to_string(), key, error }), + } + } + d +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn every_variant_is_its_own_program() { + let ids: std::collections::BTreeSet = std::iter::once(Variant::CONFORMING).chain(Variant::MUTANTS.iter().map(|m| m.0)).map(|v| program_id(v, "npu2", "t 1")).collect(); + assert_eq!(ids.len(), Variant::MUTANTS.len() + 1); + assert_ne!(program_id(Variant::CONFORMING, "npu2", "t 1"), program_id(Variant::CONFORMING, "npu2", "t 2")); + assert_ne!(program_id(Variant::CONFORMING, "npu1", "t 1"), program_id(Variant::CONFORMING, "npu2", "t 1")); + for (v, _) in Variant::MUTANTS { + assert!(KERNEL.contains(&format!("MUT == {}", v.0)), "the kernel has no edit {}", v.0); + } + assert!(KERNEL.contains("extern \"C\" void wo_line(") && DESIGN.contains("\"wo_line\"") && HOST.contains("wo_npu_run")); + } + + #[test] + fn a_tree_without_the_driver_finds_nothing() { + let d = std::env::temp_dir().join(format!("wai-ops-npu-scan-{}", std::process::id())); + std::fs::create_dir_all(d.join("accel0/device")).unwrap(); + std::fs::write(d.join("accel0/device/vendor"), "0x1022\n").unwrap(); + assert!(scan(&d).is_empty()); + assert!(scan(&d.join("absent")).is_empty()); + let _ = std::fs::remove_dir_all(&d); + assert_eq!(generation(0x17f0), Some("npu2")); + assert_eq!(generation(0x1234), None); + } +} diff --git a/wai/wai-ops-npu/src/exec.rs b/wai/wai-ops-npu/src/exec.rs new file mode 100644 index 000000000..41a6f603f --- /dev/null +++ b/wai/wai-ops-npu/src/exec.rs @@ -0,0 +1,446 @@ +//! A graph's arithmetic operators on the core: an implementation of the interpreter's +//! [`Kernels`] (integer-operators §6). +//! +//! `wai_ops::run_with` asks [`NpuKernels::eval`] for each operator's outputs before it +//! computes them itself. Everything else stays the interpreter's: the plan, every +//! scalar and input check, each tensor's range policy, the state of a sequence and +//! every refusal. The operators of `wai-ops/1-npu` that compute values run on the core: +//! +//! - `conv` and `conv_transpose` as `MAC` lines: per output channel, each position's +//! taps gathered by the host (zero where padding, or a transposed convolution's +//! stride, leaves no input), summed by the core from the `pre` bias, and requantised +//! by its epilogue. The operand width is the certificate's (`mac`, §8.5): 8-bit or +//! 16-bit operands into 32-bit accumulator lanes, 32-bit operands into 64-bit lanes. +//! A sum of more taps than one line holds is split, each line's accumulators carried +//! into the next (§6: any grouping of a sum); +//! - `rescale`, `chscale` and `mul` as `RQ` lines, per channel; +//! - `add`, `chunk_add`, `clamp`, `relu`, `abs` and `leaky` as `EW` lines; +//! - `lut16` as `LUT` lines, the table carried to the core a block at a time with the +//! inputs whose entries lie in that block. +//! +//! The movement operators (`concat`, `split`, `pixel_shuffle`, `pixel_unshuffle`, +//! `pad_rep`, `crop_to`, `crop`, `subpos_split`, `subpos_merge`, `fill`) compute no +//! value; the interpreter moves their data. +//! +//! What the kernels did, and anything that went wrong, is in [`Stats`]: a device error +//! never becomes a result, since the caller holds the run to [`Stats::errors`] being +//! empty. + +use std::cell::RefCell; + +use wai_ops::graph::{Op, Params, opcode::*}; +use wai_ops::{Kernels, Loaded, Profile, Refusal, Sequence, Tensor}; + +use crate::device::Npu; +use crate::lines::{self, EW_ABS, EW_ADD, EW_CLAMP, EW_LEAKY, EW_RELU, Epi, Init, LUT_BLOCK, Line, MAX_N, RQ_MUL, RQ_RESCALE, Unfit, Width, w32}; + +/// Where a graph stands against `wai-ops/1-npu` (§9.2), and the operand width of each +/// `conv` and `conv_transpose`, from its certificate. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Plan { + /// The certificate's `npu_reasons`: empty for a graph in the subset. + pub reasons: Vec, + /// The certificate's `npu_flags`. + pub flags: Vec, + /// Per operator, the `MAC` width of a `conv` or `conv_transpose`. + pub widths: Vec>, + /// Whether the verdict meets `acc32`. + pub acc32: bool, +} + +impl Plan { + pub fn in_subset(&self) -> bool { + self.reasons.is_empty() + } +} + +/// The graph's [`Plan`], from the certificate the bounds pass writes for it alone (§8.5). +pub fn plan(l: &Loaded) -> Result { + let bytes = l.graph().to_bytes(); + let cert = wai_ops::bounds::certify(None, "whg1", None, &[("g", &bytes)]).map_err(|u| format!("the graph does not certify: {:?}", u.refusal))?; + let v = cert.verdict(); + let nodes = &cert.graphs[0].bounds.nodes; + let widths = l + .graph() + .ops + .iter() + .enumerate() + .map(|(i, op)| match op.code { + CONV | CONV_TRANSPOSE => { + let n = &nodes[i]; + let a = n.a_bits.as_ref().and_then(|v| v.first().copied()).unwrap_or(64); + Some(Width::of(a, n.w_bits.unwrap_or(64))) + } + _ => None, + }) + .collect(); + Ok(Plan { reasons: v.npu_reasons.clone(), flags: v.npu_flags.clone(), widths, acc32: v.meets(Profile::Acc32) }) +} + +/// What the kernels did in one or more decodes. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Stats { + /// Operators whose outputs the core computed. + pub npu_ops: u64, + /// Movement operators, which the interpreter ran. + pub moved: u64, + /// Lines run, by job, and `MAC` lines by width. + pub mac: [u64; 3], + pub rq: u64, + pub ew: u64, + pub lut: u64, + /// Operators declined with a reason other than movement: outside the subset, or a + /// value past the words a line carries. + pub declined: Vec, + /// Device errors. + pub errors: Vec, +} + +impl Stats { + pub fn add(&mut self, o: &Stats) { + self.npu_ops += o.npu_ops; + self.moved += o.moved; + for k in 0..3 { + self.mac[k] += o.mac[k]; + } + self.rq += o.rq; + self.ew += o.ew; + self.lut += o.lut; + self.declined.extend(o.declined.iter().cloned()); + self.errors.extend(o.errors.iter().cloned()); + } + pub fn lines(&self) -> u64 { + self.mac.iter().sum::() + self.rq + self.ew + self.lut + } +} + +/// The core as the interpreter's kernels for one decode of one graph. +pub struct NpuKernels<'a> { + npu: &'a Npu, + graph: &'a Loaded, + plan: &'a Plan, + scalars: &'a [i64], + stats: RefCell, +} + +enum Fail { + Unfit(Unfit), + Device(String), +} + +impl From for Fail { + fn from(u: Unfit) -> Fail { + Fail::Unfit(u) + } +} + +fn bound32(v: i64) -> i32 { + v.clamp(i32::MIN as i64, i32::MAX as i64) as i32 +} + +impl<'a> NpuKernels<'a> { + pub fn new(npu: &'a Npu, graph: &'a Loaded, plan: &'a Plan, scalars: &'a [i64]) -> NpuKernels<'a> { + NpuKernels { npu, graph, plan, scalars, stats: RefCell::new(Stats::default()) } + } + + pub fn into_stats(self) -> Stats { + self.stats.into_inner() + } + + fn run_lines(&self, lines: &[Line]) -> Result>, Fail> { + self.npu.dispatch(lines).map_err(|e| Fail::Device(e.to_string())) + } + + /// Elementwise lines over `n` elements, chunked: `make(lo, hi)` builds the line for + /// elements `lo..hi`; the outputs come back in order. + fn chunked(&self, n: usize, mut make: impl FnMut(usize, usize) -> Result) -> Result, Fail> { + let mut lines = Vec::new(); + let mut spans = Vec::new(); + let mut lo = 0; + while lo < n { + let hi = (lo + MAX_N).min(n); + lines.push(make(lo, hi)?); + spans.push(hi - lo); + lo = hi; + } + let outs = self.run_lines(&lines)?; + let mut v = Vec::with_capacity(n); + for (o, len) in outs.iter().zip(spans) { + v.extend(o[..len].iter().map(|&x| x as i64)); + } + Ok(v) + } + + fn epi_of(rq: &wai_ops::graph::Rq, o: usize) -> Result { + Ok(Epi { + round: lines::round_code(rq.round), + shift: rq.shift(o) as i32, + mult: w32(rq.mult(o).unwrap_or(1), "a multiplier")?, + post: w32(rq.post(o), "a post bias")?, + }) + } + + /// A sum over taps for every output channel: `taps(o)` gives channel `o`'s weights + /// and the index of its patch matrix, `patches[g][k][p]` each patch matrix's tap `k` + /// at position `p`. Returns the requantised `[cout][P]`. + fn mac(&self, width: Width, rq: &wai_ops::graph::Rq, cout: usize, p_n: usize, taps: &dyn Fn(usize) -> (Vec, usize), patches: &[Vec>]) -> Result, Fail> { + let npos = lines::max_positions(width).min(p_n); + let mut out = vec![0i64; cout * p_n]; + // Work items: (o, p0, p1), each carried through the rounds of its K chunks. + let mut items: Vec<(usize, usize, usize, Vec, usize, Init)> = Vec::new(); + for o in 0..cout { + let (w, g) = taps(o); + let mut p0 = 0; + while p0 < p_n { + let p1 = (p0 + npos).min(p_n); + items.push((o, p0, p1, w.clone(), g, Init::Scalar(w32(rq.pre(o), "a pre bias")?))); + p0 = p1; + } + } + // Every line of a round holds the same taps: as many as the widest one holds. + let k_all = items.first().map_or(0, |it| it.3.len()); + let kc = lines::max_taps(width, npos, true).max(1); + let mut k0 = 0; + while k0 < k_all { + let k1 = (k0 + kc).min(k_all); + let fin = k1 == k_all; + let mut lines_v = Vec::with_capacity(items.len()); + for (o, p0, p1, w, g, init) in &items { + let x: Vec> = (k0..k1).map(|k| patches[*g][k][*p0..*p1].to_vec()).collect(); + lines_v.push(lines::mac_line(width, &w[k0..k1], &x, init, fin, if fin { Self::epi_of(rq, *o)? } else { Epi::IDENTITY })?); + } + let outs = self.run_lines(&lines_v)?; + self.stats.borrow_mut().mac[width as usize] += lines_v.len() as u64; + for (it, o) in items.iter_mut().zip(&outs) { + let n = it.2 - it.1; + if fin { + for p in 0..n { + out[it.0 * p_n + it.1 + p] = o[p] as i64; + } + } else { + it.5 = Init::Vector(o[..n].to_vec()); + } + } + k0 = k1; + } + Ok(out) + } + + fn eval_op(&self, i: usize, op: &Op, ins: &[&Tensor], out: &[(u32, u32, u32)]) -> Result>, Fail> { + let x = ins[0]; + let (co, ho, wo) = out[0]; + let one = |data: Vec| Some(vec![Tensor::new(co, ho, wo, data)]); + let n_in = x.data.len(); + let hw = (x.h * x.w) as usize; + let r = match (&op.p, op.code) { + (Params::Conv { cout, kh, kw, sh, sw, ph, pw, groups, rq, w, .. }, _) => { + let width = self.plan.widths[i].unwrap_or(Width::W32); + let (cin, h, wd) = (x.c as usize, x.h as i64, x.w as i64); + let (cout, kh, kw, groups) = (*cout as usize, *kh as usize, *kw as usize, *groups as usize); + let (cg, og) = (cin / groups, cout / groups); + let k_n = cg * kh * kw; + let p_n = (ho * wo) as usize; + let mut patches = Vec::with_capacity(groups); + for g in 0..groups { + let mut m = vec![vec![0i64; p_n]; k_n]; + for ci in 0..cg { + let c = g * cg + ci; + for ii in 0..kh { + for jj in 0..kw { + let k = (ci * kh + ii) * kw + jj; + for y in 0..ho as i64 { + let u = y * *sh as i64 + ii as i64 - *ph as i64; + for xx in 0..wo as i64 { + let v = xx * *sw as i64 + jj as i64 - *pw as i64; + if u >= 0 && u < h && v >= 0 && v < wd { + m[k][(y * wo as i64 + xx) as usize] = x.data[((c as i64 * h + u) * wd + v) as usize]; + } + } + } + } + } + } + patches.push(m); + } + let taps = |o: usize| (w[o * k_n..(o + 1) * k_n].to_vec(), o / og); + one(self.mac(width, rq, cout, p_n, &taps, &patches)?) + } + (Params::ConvT { cout, kh, kw, sh, sw, ph, pw, rq, w, .. }, _) => { + let width = self.plan.widths[i].unwrap_or(Width::W32); + let (cin, h, wd) = (x.c as usize, x.h as i64, x.w as i64); + let (cout, kh, kw) = (*cout as usize, *kh as usize, *kw as usize); + let (sh, sw, ph, pw) = (*sh as i64, *sw as i64, *ph as i64, *pw as i64); + let k_n = cin * kh * kw; + let p_n = (ho * wo) as usize; + let mut m = vec![vec![0i64; p_n]; k_n]; + for ci in 0..cin { + for ii in 0..kh { + for jj in 0..kw { + let k = (ci * kh + ii) * kw + jj; + for y in 0..ho as i64 { + let ty = y + ph - ii as i64; + if ty < 0 || ty % sh != 0 || ty / sh >= h { + continue; + } + for xx in 0..wo as i64 { + let tx = xx + pw - jj as i64; + if tx < 0 || tx % sw != 0 || tx / sw >= wd { + continue; + } + m[k][(y * wo as i64 + xx) as usize] = x.data[((ci as i64 * h + ty / sh) * wd + tx / sw) as usize]; + } + } + } + } + } + let taps = |o: usize| { + let mut wv = Vec::with_capacity(k_n); + for ci in 0..cin { + for ii in 0..kh { + for jj in 0..kw { + wv.push(w[((ci * cout + o) * kh + ii) * kw + jj]); + } + } + } + (wv, 0) + }; + one(self.mac(width, rq, cout, p_n, &taps, &[m])?) + } + (Params::Rescale(rq), _) => { + let mut v = Vec::with_capacity(n_in); + for c in 0..x.c as usize { + let (pre, epi) = (w32(rq.pre(c), "a pre bias")?, Self::epi_of(rq, c)?); + let s = &x.data[c * hw..(c + 1) * hw]; + v.extend(self.chunked(hw, |lo, hi| lines::rq_line(RQ_RESCALE, &s[lo..hi], &[], pre, epi))?); + self.stats.borrow_mut().rq += hw.div_ceil(MAX_N) as u64; + } + one(v) + } + (Params::Chscale { scalar, round, shift, m, .. }, _) => { + let r = self.scalars[*scalar as usize] as usize; + let cn = x.c as usize; + let mut v = Vec::with_capacity(n_in); + for c in 0..cn { + let epi = Epi { round: lines::round_code(*round), shift: *shift as i32, mult: w32(m[r * cn + c], "a table entry")?, post: 0 }; + let s = &x.data[c * hw..(c + 1) * hw]; + v.extend(self.chunked(hw, |lo, hi| lines::rq_line(RQ_RESCALE, &s[lo..hi], &[], 0, epi))?); + self.stats.borrow_mut().rq += hw.div_ceil(MAX_N) as u64; + } + one(v) + } + (Params::Mul(rq), _) => { + let b = ins[1]; + let mut v = Vec::with_capacity(n_in); + for c in 0..x.c as usize { + let epi = Self::epi_of(rq, c)?; + let sa = &x.data[c * hw..(c + 1) * hw]; + let cb = if b.c == x.c { c } else { 0 }; + let sb = &b.data[cb * hw..(cb + 1) * hw]; + v.extend(self.chunked(hw, |lo, hi| lines::rq_line(RQ_MUL, &sa[lo..hi], &sb[lo..hi], 0, epi))?); + self.stats.borrow_mut().rq += hw.div_ceil(MAX_N) as u64; + } + one(v) + } + (Params::Lut16 { t, .. }, _) => { + // Inputs grouped by the table block their entry lies in. + let mut by_block: std::collections::BTreeMap> = Default::default(); + for (e, &v) in x.data.iter().enumerate() { + let u = v + 32768; + if !(0..65536).contains(&u) { + return Err(Fail::Unfit(Unfit(format!("lut16 input {v} is outside the table")))); + } + by_block.entry(u as usize / LUT_BLOCK).or_default().push(e); + } + let mut lines_v = Vec::new(); + let mut who = Vec::new(); + for (blk, es) in &by_block { + let base = blk * LUT_BLOCK; + for chunk in es.chunks(MAX_N) { + let vals: Vec = chunk.iter().map(|&e| x.data[e]).collect(); + lines_v.push(lines::lut_line(&t[base..(base + LUT_BLOCK).min(t.len())], base, &vals)?); + who.push(chunk.to_vec()); + } + } + let outs = self.run_lines(&lines_v)?; + self.stats.borrow_mut().lut += lines_v.len() as u64; + let mut v = vec![0i64; n_in]; + for (o, es) in outs.iter().zip(&who) { + for (k, &e) in es.iter().enumerate() { + v[e] = o[k] as i64; + } + } + one(v) + } + (_, ADD) => { + let b = ins[1]; + let v = self.chunked(n_in, |lo, hi| lines::ew_line(EW_ADD, &x.data[lo..hi], &b.data[lo..hi], 0, 0, 0))?; + self.stats.borrow_mut().ew += n_in.div_ceil(MAX_N) as u64; + one(v) + } + (_, CHUNK_ADD) => { + let half = n_in / 2; + let v = self.chunked(half, |lo, hi| lines::ew_line(EW_ADD, &x.data[lo..hi], &x.data[half + lo..half + hi], 0, 0, 0))?; + self.stats.borrow_mut().ew += half.div_ceil(MAX_N) as u64; + one(v) + } + (Params::Clamp { lo, hi }, _) => { + let (l, h) = (bound32(*lo), bound32(*hi)); + let v = self.chunked(n_in, |a, b| lines::ew_line(EW_CLAMP, &x.data[a..b], &[], 0, l, h))?; + self.stats.borrow_mut().ew += n_in.div_ceil(MAX_N) as u64; + one(v) + } + (_, RELU) | (_, ABS) => { + let sub = if op.code == RELU { EW_RELU } else { EW_ABS }; + let v = self.chunked(n_in, |a, b| lines::ew_line(sub, &x.data[a..b], &[], 0, 0, 0))?; + self.stats.borrow_mut().ew += n_in.div_ceil(MAX_N) as u64; + one(v) + } + (Params::Leaky { a, f }, _) => { + let a = w32(*a, "leaky's slope")?; + let v = self.chunked(n_in, |lo, hi| lines::ew_line(EW_LEAKY, &x.data[lo..hi], &[], 0, a, *f as i32))?; + self.stats.borrow_mut().ew += n_in.div_ceil(MAX_N) as u64; + one(v) + } + (_, CONCAT | SPLIT | PIXEL_SHUFFLE | PIXEL_UNSHUFFLE | PAD_REP | CROP_TO | CROP | SUBPOS_SPLIT | SUBPOS_MERGE | FILL) => { + self.stats.borrow_mut().moved += 1; + return Ok(None); + } + _ => { + self.stats.borrow_mut().declined.push(format!("operator {i} ({}) is outside wai-ops/1-npu", wai_ops::graph::op_name(op.code).unwrap_or("?"))); + return Ok(None); + } + }; + self.stats.borrow_mut().npu_ops += 1; + Ok(r) + } +} + +impl Kernels for NpuKernels<'_> { + fn eval(&self, op: &Op, ins: &[&Tensor], out: &[(u32, u32, u32)]) -> Option> { + let Some(i) = self.graph.graph().ops.iter().position(|o| std::ptr::eq(o, op)) else { + self.stats.borrow_mut().errors.push("an operator of another graph".into()); + return None; + }; + match self.eval_op(i, op, ins, out) { + Ok(r) => r, + Err(Fail::Unfit(u)) => { + self.stats.borrow_mut().declined.push(format!("operator {i}: {}", u.0)); + None + } + Err(Fail::Device(e)) => { + self.stats.borrow_mut().errors.push(format!("operator {i}: {e}")); + None + } + } + } +} + +/// One decode of `l` with its arithmetic operators on the core: the interpreter's +/// result, refusal for refusal, wherever the kernels keep their contract; and what the +/// kernels did. A caller holds the run to the stats' `errors` (and, for a graph in the +/// subset, `declined`) being empty. +pub fn run(npu: &Npu, l: &Loaded, plan: &Plan, inputs: &[Tensor], scalars: &[i64], seq: &mut Sequence) -> (Result, Refusal>, Stats) { + let k = NpuKernels::new(npu, l, plan, scalars); + let r = wai_ops::run_with(l, inputs.to_vec(), scalars, seq, &k); + (r, k.into_stats()) +} diff --git a/wai/wai-ops-npu/src/kernel/design.py b/wai/wai-ops-npu/src/kernel/design.py new file mode 100644 index 000000000..f716a8f9b --- /dev/null +++ b/wai/wai-ops-npu/src/kernel/design.py @@ -0,0 +1,95 @@ +"""The tile array's program for wai-ops-npu. + +One compute core runs the kernel (ops_npu.cc) over a dispatch of LINES lines, +each LIN words in and LOUT words out: the array's data movers bring each line +into the core's memory and take its outputs back to the host's buffer. The +crate runs this script with the open toolchain's Python to build the program, +then loads it itself; nothing here runs on the device. + + python design.py --kernel ops_npu.cc --lin 4096 --lout 1024 --lines 4 --mut 0 \\ + --device npu2 --xclbin final.xclbin --insts insts.bin + +With --versions it prints the toolchain's versions, one per line, and exits. +""" + +import argparse +import importlib.metadata as md +import os +import site +import sys + + +def versions(): + for name in ("mlir_aie", "llvm-aie"): + try: + print(f"{name} {md.version(name)}") + except md.PackageNotFoundError: + print(f"{name} absent") + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--versions", action="store_true") + ap.add_argument("--kernel") + ap.add_argument("--lin", type=int) + ap.add_argument("--lout", type=int) + ap.add_argument("--lines", type=int) + ap.add_argument("--mut", type=int, default=0) + ap.add_argument("--device", default="npu2") + ap.add_argument("--xclbin") + ap.add_argument("--insts") + a = ap.parse_args() + if a.versions: + versions() + return + # The core compiler ships in its own package beside this interpreter's. + if "PEANO_INSTALL_DIR" not in os.environ: + for d in site.getsitepackages(): + if os.path.isdir(os.path.join(d, "llvm-aie", "bin")): + os.environ["PEANO_INSTALL_DIR"] = os.path.join(d, "llvm-aie") + break + + import numpy as np + import aie.iron as iron + from aie.iron import CompileTime, In, ObjectFifo, Out, Program, Runtime, Worker + from aie.iron.device import from_name + from aie.iron.kernel import ExternalFunction + + source = open(a.kernel).read() + lin, lout, lines = a.lin, a.lout, a.lines + flags = [f"-DLIN={lin}", f"-DLOUT={lout}", f"-DMUT={a.mut}"] + + @iron.jit + def design(inp: In, out: Out, *, n: CompileTime[int]): + tin = np.ndarray[(lin,), np.dtype[np.int32]] + tout = np.ndarray[(lout,), np.dtype[np.int32]] + vin = np.ndarray[(n * lin,), np.dtype[np.int32]] + vout = np.ndarray[(n * lout,), np.dtype[np.int32]] + of_in = ObjectFifo(tin, name="in") + of_out = ObjectFifo(tout, name="out") + kernel = ExternalFunction("wo_line", source_string=source, arg_types=[tin, tout], compile_flags=flags) + + def core(of_in, of_out, kernel): + o = of_out.acquire(1) + i = of_in.acquire(1) + kernel(i, o) + of_in.release(1) + of_out.release(1) + + # The scalar unit's 64-bit route calls the compiler's runtime helpers, whose + # stack the toolchain cannot measure; 4 KiB covers them with room to spare. + worker = Worker(core, [of_in.cons(), of_out.prod(), kernel], stack_size=4096) + + def sequence(x, y, in_h, out_h): + in_h.fill(x) + out_h.drain(y, wait=True) + + rt = Runtime(sequence, [vin, vout, of_in.prod(), of_out.cons()]) + return Program(iron.get_current_device(), rt, workers=[worker]).resolve_program() + + iron.set_current_device(from_name(a.device, n_cols=1)) + design.specialize(n=lines).compile(xclbin_path=a.xclbin, inst_path=a.insts) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/wai/wai-ops-npu/src/kernel/host.cpp b/wai/wai-ops-npu/src/kernel/host.cpp new file mode 100644 index 000000000..1857ccf44 --- /dev/null +++ b/wai/wai-ops-npu/src/kernel/host.cpp @@ -0,0 +1,131 @@ +// wai-ops-npu: the host side. The device runtime's interface for a neural +// processor (a hardware context over a registered program) is C++, so the +// crate compiles this file with the system's C++ compiler against that runtime +// and loads it; this is the whole of what it calls. Every function returns 0 +// on success, or 1 with the runtime's message in `err`. +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace { + +struct Npu { + xrt::device device; + xrt::hw_context context; + xrt::kernel kernel; + xrt::bo instructions; + uint32_t count = 0; + xrt::bo a, b; + size_t a_words = 0, b_words = 0; +}; + +void say(char *err, size_t len, const std::string &what) { + if (len == 0) return; + size_t n = what.size() < len - 1 ? what.size() : len - 1; + std::memcpy(err, what.data(), n); + err[n] = 0; +} + +std::vector words_of(const char *path) { + std::ifstream f(path, std::ios::binary); + if (!f) throw std::runtime_error(std::string("cannot read ") + path); + std::vector bytes((std::istreambuf_iterator(f)), std::istreambuf_iterator()); + if (bytes.empty() || bytes.size() % 4 != 0) throw std::runtime_error(std::string(path) + " is not a whole number of words"); + std::vector w(bytes.size() / 4); + std::memcpy(w.data(), bytes.data(), bytes.size()); + return w; +} + +} // namespace + +// The device's name as the runtime gives it. +extern "C" int wo_npu_name(char *name, size_t len, char *err, size_t errlen) { + try { + xrt::device d(0); + say(name, len, d.get_info()); + return 0; + } catch (const std::exception &e) { + say(err, errlen, e.what()); + return 1; + } +} + +// Opens device 0 with the program `xclbin` and its instruction stream +// `insts`, kernel `kernel` (the first whose name starts with it), and the two +// buffers one dispatch moves: `in_words` in and `out_words` out. +extern "C" int wo_npu_open(const char *xclbin, const char *insts, const char *kernel, size_t in_words, size_t out_words, void **out, char *err, size_t errlen) { + try { + auto *n = new Npu(); + n->device = xrt::device(0); + xrt::xclbin x{std::string(xclbin)}; + std::string name; + for (auto &k : x.get_kernels()) { + if (k.get_name().rfind(kernel, 0) == 0) { + name = k.get_name(); + break; + } + } + if (name.empty()) { + delete n; + say(err, errlen, std::string("the program has no kernel named ") + kernel); + return 1; + } + n->device.register_xclbin(x); + n->context = xrt::hw_context(n->device, x.get_uuid()); + n->kernel = xrt::kernel(n->context, name); + auto w = words_of(insts); + n->count = static_cast(w.size()); + n->instructions = xrt::bo(n->device, w.size() * sizeof(uint32_t), XCL_BO_FLAGS_CACHEABLE, n->kernel.group_id(1)); + std::memcpy(n->instructions.map(), w.data(), w.size() * sizeof(uint32_t)); + n->instructions.sync(XCL_BO_SYNC_BO_TO_DEVICE); + n->a_words = in_words; + n->b_words = out_words; + n->a = xrt::bo(n->device, in_words * 4, XRT_BO_FLAGS_HOST_ONLY, n->kernel.group_id(3)); + n->b = xrt::bo(n->device, out_words * 4, XRT_BO_FLAGS_HOST_ONLY, n->kernel.group_id(4)); + *out = n; + return 0; + } catch (const std::exception &e) { + say(err, errlen, e.what()); + return 1; + } +} + +// One dispatch: `in` (the opened in_words) to the program's first buffer, its +// second buffer (out_words) back into `out`. The output buffer is cleared to +// `fill` first, so that a word the core does not write is seen. +extern "C" int wo_npu_run(void *h, const int32_t *in, int32_t *out, int32_t fill, char *err, size_t errlen) { + try { + auto *n = static_cast(h); + std::memcpy(n->a.map(), in, n->a_words * 4); + auto *o = n->b.map(); + for (size_t i = 0; i < n->b_words; i++) o[i] = fill; + n->a.sync(XCL_BO_SYNC_BO_TO_DEVICE); + n->b.sync(XCL_BO_SYNC_BO_TO_DEVICE); + unsigned int opcode = 3; + auto run = n->kernel(opcode, n->instructions, n->count, n->a, n->b); + auto state = run.wait(); + if (state != ERT_CMD_STATE_COMPLETED) { + say(err, errlen, "the dispatch ended in state " + std::to_string(static_cast(state))); + return 1; + } + n->b.sync(XCL_BO_SYNC_BO_FROM_DEVICE); + std::memcpy(out, n->b.map(), n->b_words * 4); + return 0; + } catch (const std::exception &e) { + say(err, errlen, e.what()); + return 1; + } +} + +extern "C" void wo_npu_close(void *h) { + delete static_cast(h); +} diff --git a/wai/wai-ops-npu/src/kernel/ops_npu.cc b/wai/wai-ops-npu/src/kernel/ops_npu.cc new file mode 100644 index 000000000..3b87ee8a7 --- /dev/null +++ b/wai/wai-ops-npu/src/kernel/ops_npu.cc @@ -0,0 +1,333 @@ +// wai-ops/1-npu on one compute core of a neural processor's tile array: the +// integer operators' arithmetic in the core's vector unit, compiled for the +// core by the array's open compiler. +// +// The core runs one *line* at a time: LIN words in, LOUT words out. A line's +// first 16 words are its header; word 0 names its job. The host lays every +// operator out as lines (src/lines.rs is the other half of this layout) and +// reads each line's outputs back. Nothing here is floating point. +// +// Header words: +// 0 job 1-7 the job's own words (below) +// 8 rounding (0 floor, 1 half_up, 2 half_even) 9 shift +// 10 (unused) 11 multiplier 12 post bias 13-15 (unused) +// +// Jobs: +// MAC (1): h1 operand width (8, 16, 32), h2 taps K, h3 positions n, +// h4 final, h5 init (0: the scalar h6; 1: a vector of n), h6 bias. +// Region: [init: RS words when h5 = 1] [weights: K words] +// [patches: K rows of RS elements of the operand width], each +// region at a 32-word boundary; RS is n rounded up to 64. Each +// position's accumulator starts at its init and adds its K +// products: 8- and 16-bit operands into 32-bit accumulator lanes, +// 32-bit operands into 64-bit lanes. A final line requantises +// (the epilogue below); any other line writes the accumulators. +// RQ (2): h1 0 (t = x + h6) or 1 (t = a·b), h3 n. Region: x (or a) [RS], +// then b [RS]. Then the epilogue. +// EW (3): h1 0 (clamp to h5..h6), 1 (a + b), 2 (max(v, 0)), 3 (|v|), +// 4 (v < 0 ? floor(v·h5 / 2^h6) : v), h3 n. Region: a [RS], then +// b [RS]. +// LUT (4): h3 n, h4 the block's first table index, h5 its entries TB. +// Region: TB 16-bit entries, then n indices v; the output is +// T[v + 32768 - h4]. +// PROBE (5): h1 the unit's rounding mode, h2 its saturation mode, h3 n, +// h9 the shift. Region: t [RS], m [RS]; the output is the 64-bit +// product t·m shifted and rounded by the unit to 32 bits, with no +// other step: what the unit itself does. +// +// The epilogue (§5.1 of the integer-operators extension): p = t·m as 64-bit +// accumulator lanes (m = 1 when the operator has no multiplier), q = p +// shifted right by the shift and rounded by the unit's rounding mode set +// from word 8, y = q + post bias. The unit's shift-round is exact for shifts +// 0..VSHIFT_MAX only (measured: it takes a shift of 60..63 as a left shift by +// 64 - s), so a larger shift goes through the core's scalar unit in 64-bit +// arithmetic. `leaky`'s floor shift does the same. +// +// MUT (given by the build, 0 for the conforming kernel) selects one edit of +// the arithmetic, each its own compiled program, which the conformance +// corpus must catch. + +#include + +#include + +#if !defined(LIN) || !defined(LOUT) +#error "LIN and LOUT are given by the build" +#endif +#ifndef MUT +#define MUT 0 +#endif + +namespace { + +constexpr int32_t J_NOP = 0, J_MAC = 1, J_RQ = 2, J_EW = 3, J_LUT = 4, J_PROBE = 5; + +inline int32_t align32(int32_t w) { return (w + 31) & ~31; } +inline int32_t rs_of(int32_t n) { return (n + 63) & ~63; } + +inline aie::rounding_mode mode_of(int32_t r) { + switch (r) { + case 1: +#if MUT == 1 + return aie::rounding_mode::symmetric_inf; +#else + return aie::rounding_mode::positive_inf; +#endif + case 2: +#if MUT == 2 + return aie::rounding_mode::conv_odd; +#else + return aie::rounding_mode::conv_even; +#endif + default: +#if MUT == 3 + return aie::rounding_mode::symmetric_floor; +#else + return aie::rounding_mode::floor; +#endif + } +} + +using v32 = aie::vector; + +// The largest shift the unit's shift-round step takes exactly. +#if MUT == 12 +constexpr int32_t VSHIFT_MAX = 63; +#else +constexpr int32_t VSHIFT_MAX = 59; +#endif + +// R(t·m, s) + post in the scalar unit's 64-bit arithmetic, for 1 <= s <= 63. +inline int32_t rq_scalar(int32_t t, int32_t m, int32_t s, int32_t round, int32_t post) { + const int64_t p = (int64_t)t * (int64_t)m; + int64_t q = p >> s; + const uint64_t r = (uint64_t)p & ((1ull << s) - 1ull); + const uint64_t h = 1ull << (s - 1); + if (round == 1) + q += (r >= h) ? 1 : 0; + else if (round == 2) + q += (r > h || (r == h && (q & 1))) ? 1 : 0; + return (int32_t)(q + post); +} + +// t → R(t·m, s) + post, stored: the requantisation of §5.1. +inline void epilogue(v32 t, const int32_t *h, int32_t *outp) { + const int32_t m = h[11]; +#if MUT == 4 + const int32_t s = h[9] + 1; +#else + const int32_t s = h[9]; +#endif +#if MUT == 5 + const int32_t post = 0; +#else + const int32_t post = h[12]; +#endif + if (s > VSHIFT_MAX) { + for (int32_t i = 0; i < 32; i++) outp[i] = rq_scalar(t.get(i), m, s, h[8], post); + return; + } + aie::accum p = aie::mul(t, m); + v32 q = p.to_vector(s); + aie::store_v(outp, aie::add(q, post)); +} + +template +void mac_narrow(const int32_t *h, const int32_t *d, int32_t *out) { + const int32_t K = h[2], n = h[3], fin = h[4], init = h[5]; + const int32_t RS = rs_of(n); + int32_t off = 0; + const int32_t *iv = d; + if (init == 1) off = align32(RS); + const int32_t *w = d + off; + off = align32(off + K); + const T *x = reinterpret_cast(d + off); +#if MUT == 10 + const int32_t KK = K > 1 ? K - 1 : K; +#else + const int32_t KK = K; +#endif + for (int32_t c = 0; c < RS; c += 64) { + aie::vector a0; + if (init == 1) { + a0 = aie::load_v<64>(iv + c); + } else { +#if MUT == 6 + a0 = aie::broadcast(0); +#else + a0 = aie::broadcast(h[6]); +#endif + } + aie::accum acc; + acc.from_vector(a0, 0); + for (int32_t k = 0; k < KK; k++) { + aie::vector xv = aie::load_v<64>(x + k * RS + c); + acc = aie::mac(acc, xv, (T)w[k]); + } + aie::vector t = acc.template to_vector(0); + if (fin) { + epilogue(t.template extract<32>(0), h, out + c); + epilogue(t.template extract<32>(1), h, out + c + 32); + } else { + aie::store_v(out + c, t); + } + } +} + +void mac_wide(const int32_t *h, const int32_t *d, int32_t *out) { + const int32_t K = h[2], n = h[3], fin = h[4], init = h[5]; + const int32_t RS = rs_of(n); + int32_t off = 0; + const int32_t *iv = d; + if (init == 1) off = align32(RS); + const int32_t *w = d + off; + off = align32(off + K); + const int32_t *x = d + off; +#if MUT == 10 + const int32_t KK = K > 1 ? K - 1 : K; +#else + const int32_t KK = K; +#endif + for (int32_t c = 0; c < RS; c += 32) { + v32 a0; + if (init == 1) { + a0 = aie::load_v<32>(iv + c); + } else { +#if MUT == 6 + a0 = aie::broadcast(0); +#else + a0 = aie::broadcast(h[6]); +#endif + } + aie::accum acc; + acc.from_vector(a0, 0); + for (int32_t k = 0; k < KK; k++) { + v32 xv = aie::load_v<32>(x + k * RS + c); + acc = aie::mac(acc, xv, w[k]); + } + v32 t = acc.to_vector(0); + if (fin) + epilogue(t, h, out + c); + else + aie::store_v(out + c, t); + } +} + +void rq_line(const int32_t *h, const int32_t *d, int32_t *out) { + const int32_t n = h[3]; + const int32_t RS = rs_of(n); + const int32_t *a = d, *b = d + RS; + for (int32_t c = 0; c < RS; c += 32) { + v32 x = aie::load_v<32>(a + c); + v32 t; + if (h[1] == 1) { + aie::accum p = aie::mul(x, aie::load_v<32>(b + c)); + t = p.to_vector(0); + } else { + t = aie::add(x, h[6]); + } + epilogue(t, h, out + c); + } +} + +void ew_line(const int32_t *h, const int32_t *d, int32_t *out) { + const int32_t n = h[3]; + const int32_t RS = rs_of(n); + const int32_t *a = d, *b = d + RS; + const int32_t sub = h[1]; + for (int32_t c = 0; c < RS; c += 32) { + v32 v = aie::load_v<32>(a + c); + v32 y = v; + if (sub == 0) { +#if MUT == 7 + y = aie::min(aie::max(v, h[5]), h[6] - 1); +#else + y = aie::min(aie::max(v, h[5]), h[6]); +#endif + } else if (sub == 1) { + y = aie::add(v, aie::load_v<32>(b + c)); + } else if (sub == 2) { +#if MUT == 11 + y = aie::max(v, (int32_t)-1); +#else + y = aie::max(v, (int32_t)0); +#endif + } else if (sub == 3) { + y = aie::abs(v); + } else if (sub == 4) { + const int32_t f = h[6]; + v32 neg = aie::zeros(); + if (f > VSHIFT_MAX) { + for (int32_t i = 0; i < 32; i++) neg.set(rq_scalar(v.get(i), h[5], f, 0, 0), i); + } else { +#if MUT == 8 + aie::set_rounding(aie::rounding_mode::symmetric_floor); +#else + aie::set_rounding(aie::rounding_mode::floor); +#endif + aie::accum p = aie::mul(v, h[5]); + neg = p.to_vector(f); + } + y = aie::select(v, neg, aie::lt(v, (int32_t)0)); + } + aie::store_v(out + c, y); + } +} + +void lut_line(const int32_t *h, const int32_t *d, int32_t *out) { + const int32_t n = h[3], base = h[4], tb = h[5]; + const int16_t *t = reinterpret_cast(d); + const int32_t *v = d + align32((tb + 1) / 2); + for (int32_t i = 0; i < n; i++) { +#if MUT == 9 + int32_t u = v[i] + 32768 - base + 1; +#else + int32_t u = v[i] + 32768 - base; +#endif + out[i] = (u >= 0 && u < tb) ? (int32_t)t[u] : 0; + } +} + +void probe_line(const int32_t *h, const int32_t *d, int32_t *out) { + const int32_t n = h[3]; + const int32_t RS = rs_of(n); + aie::set_rounding((aie::rounding_mode)h[1]); + aie::set_saturation((aie::saturation_mode)h[2]); + for (int32_t c = 0; c < RS; c += 32) { + aie::accum p = aie::mul(aie::load_v<32>(d + c), aie::load_v<32>(d + RS + c)); + aie::store_v(out + c, p.to_vector(h[9])); + } +} + +} // namespace + +extern "C" void wo_line(const int32_t *in, int32_t *out) { + const int32_t *h = in; + const int32_t *d = in + 16; + aie::set_saturation(aie::saturation_mode::saturate); + aie::set_rounding(mode_of(h[8])); + switch (h[0]) { + case J_MAC: + if (h[1] == 8) + mac_narrow(h, d, out); + else if (h[1] == 16) + mac_narrow(h, d, out); + else + mac_wide(h, d, out); + break; + case J_RQ: + rq_line(h, d, out); + break; + case J_EW: + ew_line(h, d, out); + break; + case J_LUT: + lut_line(h, d, out); + break; + case J_PROBE: + probe_line(h, d, out); + break; + default: + break; + } +} diff --git a/wai/wai-ops-npu/src/lib.rs b/wai/wai-ops-npu/src/lib.rs new file mode 100644 index 000000000..22bf32a31 --- /dev/null +++ b/wai/wai-ops-npu/src/lib.rs @@ -0,0 +1,34 @@ +//! `wai-ops/1-npu` on neural processors: an operator implementation of the integer +//! operator set's accelerator subset (`extensions/integer-operators.md` §9.2, §9.5) as +//! kernels for one compute core of a tile array. +//! +//! The kernel (`kernel/ops_npu.cc`) is compiled for the core by the array's open +//! compiler and runs the arithmetic in the core's vector unit: the multiply-accumulate +//! of `conv` and `conv_transpose` into 32-bit accumulator lanes (8-bit and 16-bit +//! operands) or 64-bit lanes (32-bit operands), the requantisation of §5.1 by the +//! unit's own shift-round step under the rounding mode that is exactly the operator's +//! (`floor`, `half_up` and `half_even` are the unit's `floor`, `positive_inf` and +//! `conv_even`), and the elementwise operators and `lut16`. The unit's shift-round step +//! is exact for shifts up to 59; the measured reason, and the route the kernel takes for +//! a larger shift, are in the kernel's header and in §9.5. +//! +//! The interpreter (`wai_ops::run_with`) runs each graph, and asks these kernels for +//! every operator's outputs ([`exec::NpuKernels`]): the plan, the checks, the range +//! policies, the movement operators, the state of a sequence and every refusal stay the +//! interpreter's. `tests/corpus.rs` holds the result to every graph vector of +//! `ops-conformance/` whose graph is in the subset; `tests/probe.rs` measures the unit's +//! rounding; `tests/mutants.rs` shows that an edited kernel is caught; `tests/width.rs` +//! shows why the certificate decides which graphs run here. +//! +//! - [`device`]: discovery, the program's build, the host side and dispatch; +//! - [`lines`]: the line layout the kernel reads; +//! - [`exec`]: a graph's operators as lines; +//! - [`probe`]: the probes' cases and what §2 says they give. + +pub mod device; +pub mod exec; +pub mod lines; +pub mod probe; + +pub use device::{Discovery, Npu, NpuError, Variant, discover}; +pub use exec::{NpuKernels, Plan, Stats, plan, run}; diff --git a/wai/wai-ops-npu/src/lines.rs b/wai/wai-ops-npu/src/lines.rs new file mode 100644 index 000000000..76af4d728 --- /dev/null +++ b/wai/wai-ops-npu/src/lines.rs @@ -0,0 +1,389 @@ +//! The line layout the core's kernel reads (`kernel/ops_npu.cc` is the other half of +//! it): a line is [`LIN`] words in, [`LOUT`] words out, its first [`HEADER`] words a +//! header whose word 0 names the job. Nothing here touches a device, so all of it is +//! testable on any host. + +/// Words a line carries in. +pub const LIN: usize = 4096; +/// Words a line gives back. +pub const LOUT: usize = 1024; +/// Lines per dispatch. +pub const LINES: usize = 4; +/// Header words. +pub const HEADER: usize = 16; +/// Words of a line after its header. +pub const DATA: usize = LIN - HEADER; +/// The most elements one line computes. +pub const MAX_N: usize = LOUT; + +/// The value the host fills each output word with before a dispatch, so that a word +/// the core does not write is seen. +pub const FILL: i32 = 0x5A5A_5A5A; + +/// The jobs (header word 0). +pub const J_NOP: i32 = 0; +pub const J_MAC: i32 = 1; +pub const J_RQ: i32 = 2; +pub const J_EW: i32 = 3; +pub const J_LUT: i32 = 4; +pub const J_PROBE: i32 = 5; + +/// `EW` sub-jobs (header word 1). +pub const EW_CLAMP: i32 = 0; +pub const EW_ADD: i32 = 1; +pub const EW_RELU: i32 = 2; +pub const EW_ABS: i32 = 3; +pub const EW_LEAKY: i32 = 4; + +/// `RQ` sub-jobs: `t = x + pre`, or `t = a·b`. +pub const RQ_RESCALE: i32 = 0; +pub const RQ_MUL: i32 = 1; + +/// Entries of `lut16`'s table one line carries. +pub const LUT_BLOCK: usize = 4096; + +/// The next multiple of 32 words: where every region of a line starts. +pub fn align32(w: usize) -> usize { + (w + 31) & !31 +} + +/// `n` rounded up to 64: the stride of a line's element regions. +pub fn rs_of(n: usize) -> usize { + (n + 63) & !63 +} + +/// The rounding a line's epilogue applies (header word 8). +pub fn round_code(r: wai_ops::graph::Round) -> i32 { + match r { + wai_ops::graph::Round::Floor => 0, + wai_ops::graph::Round::HalfUp => 1, + wai_ops::graph::Round::HalfEven => 2, + } +} + +/// The epilogue of §5.1 as a line carries it: `y = R(t·m, s) + post`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Epi { + pub round: i32, + pub shift: i32, + pub mult: i32, + pub post: i32, +} + +impl Epi { + /// No change: `R(t·1, 0) + 0 = t`. + pub const IDENTITY: Epi = Epi { round: 0, shift: 0, mult: 1, post: 0 }; +} + +/// One line's input words. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Line(pub Vec); + +impl Line { + /// A line of job `job`, every other word zero. + pub fn new(job: i32) -> Line { + let mut w = vec![0i32; LIN]; + w[0] = job; + Line(w) + } + pub fn nop() -> Line { + Line::new(J_NOP) + } + pub fn set(&mut self, k: usize, v: i32) -> &mut Line { + assert!(k < HEADER); + self.0[k] = v; + self + } + pub fn epi(&mut self, e: Epi) -> &mut Line { + self.set(8, e.round).set(9, e.shift).set(11, e.mult).set(12, e.post) + } + /// The data words, from word [`HEADER`]. + pub fn data(&mut self) -> &mut [i32] { + &mut self.0[HEADER..] + } +} + +/// The operand width of a `MAC` line: 8- and 16-bit operands go into 32-bit accumulator +/// lanes, 32-bit operands into 64-bit lanes. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Width { + W8, + W16, + W32, +} + +impl Width { + pub fn bits(self) -> u32 { + match self { + Width::W8 => 8, + Width::W16 => 16, + Width::W32 => 32, + } + } + /// The narrowest width whose signed type holds both operand widths. + pub fn of(a_bits: u32, w_bits: u32) -> Width { + let b = a_bits.max(w_bits); + if b <= 8 { + Width::W8 + } else if b <= 16 { + Width::W16 + } else { + Width::W32 + } + } + pub fn as_str(self) -> &'static str { + match self { + Width::W8 => "8-bit operands, 32-bit accumulators", + Width::W16 => "16-bit operands, 32-bit accumulators", + Width::W32 => "32-bit operands, 64-bit accumulators", + } + } + /// Data words one patch row of `rs` elements takes. + pub fn row_words(self, rs: usize) -> usize { + rs * self.bits() as usize / 32 + } + fn fits(self, v: i64) -> bool { + match self { + Width::W8 => i8::try_from(v).is_ok(), + Width::W16 => i16::try_from(v).is_ok(), + Width::W32 => i32::try_from(v).is_ok(), + } + } +} + +/// A value that does not fit the words a line carries: a defect of the host or a graph +/// outside the subset, never a result. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Unfit(pub String); + +pub fn w32(v: i64, what: &str) -> Result { + i32::try_from(v).map_err(|_| Unfit(format!("{what} {v} does not fit 32 bits"))) +} + +/// How a `MAC` line's accumulators start. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Init { + /// Every position at one value (a `pre` bias, or 0). + Scalar(i32), + /// Each position at its own value (the partial sums of an earlier line). + Vector(Vec), +} + +/// Taps one `MAC` line holds for `n` positions at `width`, with or without an init +/// vector: at least 1 when `n ≤` [`max_positions`]. +pub fn max_taps(width: Width, n: usize, vector_init: bool) -> usize { + let rs = rs_of(n); + let init = if vector_init { align32(rs) } else { 0 }; + let row = width.row_words(rs); + // init + align32(K) + K·row ≤ DATA; align32(K) ≤ K + 31. + DATA.saturating_sub(init + 31) / (row + 1) +} + +/// The most positions one `MAC` line holds at `width` with one tap and an init vector. +pub fn max_positions(width: Width) -> usize { + let mut n = MAX_N; + while n > 1 && max_taps(width, n, true) < 1 { + n -= 64; + } + n +} + +/// A `MAC` line: `n` positions, `K = w.len()` taps; `x[k]` holds tap `k`'s value at +/// each position (`n` of them). `fin` requantises with `epi`; otherwise the line +/// writes the accumulators. +pub fn mac_line(width: Width, w: &[i64], x: &[Vec], init: &Init, fin: bool, epi: Epi) -> Result { + let k = w.len(); + let n = x.first().map_or(0, |r| r.len()); + assert!(k >= 1 && n >= 1 && n <= MAX_N && x.len() == k && x.iter().all(|r| r.len() == n)); + assert!(k <= max_taps(width, n, matches!(init, Init::Vector(_))), "{k} taps, {n} positions: more than a line holds"); + let rs = rs_of(n); + let mut l = Line::new(J_MAC); + l.set(1, width.bits() as i32).set(2, k as i32).set(3, n as i32).set(4, i32::from(fin)); + if fin { + l.epi(epi); + } + let mut off = match init { + Init::Scalar(b) => { + l.set(5, 0).set(6, *b); + 0 + } + Init::Vector(v) => { + assert_eq!(v.len(), n); + l.set(5, 1); + l.data()[..n].copy_from_slice(v); + align32(rs) + } + }; + let d = l.data(); + for (j, &wv) in w.iter().enumerate() { + if !width.fits(wv) { + return Err(Unfit(format!("weight {wv} does not fit {} bits", width.bits()))); + } + d[off + j] = wv as i32; + } + off = align32(off + k); + let row = width.row_words(rs); + for (j, r) in x.iter().enumerate() { + let at = off + j * row; + for (p, &v) in r.iter().enumerate() { + if !width.fits(v) { + return Err(Unfit(format!("operand {v} does not fit {} bits", width.bits()))); + } + put(&mut d[at..at + row], width, p, v); + } + } + Ok(l) +} + +/// Element `p` of a row of `width`-bit values packed into words, little-endian. +fn put(row: &mut [i32], width: Width, p: usize, v: i64) { + match width { + Width::W32 => row[p] = v as i32, + Width::W16 => { + let (wi, sh) = (p / 2, 16 * (p % 2)); + let m = 0xffffu32 << sh; + row[wi] = ((row[wi] as u32 & !m) | (((v as i16) as u16 as u32) << sh)) as i32; + } + Width::W8 => { + let (wi, sh) = (p / 4, 8 * (p % 4)); + let m = 0xffu32 << sh; + row[wi] = ((row[wi] as u32 & !m) | (((v as i8) as u8 as u32) << sh)) as i32; + } + } +} + +/// An `RQ` line over one channel's `n` elements: `RQ_RESCALE` (`t = a + pre`, with +/// `b` empty) or `RQ_MUL` (`t = a·b`), then the epilogue. +pub fn rq_line(sub: i32, a: &[i64], b: &[i64], pre: i32, epi: Epi) -> Result { + let n = a.len(); + assert!(n >= 1 && n <= MAX_N); + let rs = rs_of(n); + let mut l = Line::new(J_RQ); + l.set(1, sub).set(3, n as i32).set(6, pre).epi(epi); + let d = l.data(); + for (i, &v) in a.iter().enumerate() { + d[i] = w32(v, "an element")?; + } + if sub == RQ_MUL { + assert_eq!(b.len(), n); + for (i, &v) in b.iter().enumerate() { + d[rs + i] = w32(v, "an element")?; + } + } + Ok(l) +} + +/// An `EW` line over `n` elements: `sub` with its words 4–6, `b` read by `EW_ADD` only. +pub fn ew_line(sub: i32, a: &[i64], b: &[i64], h4: i32, h5: i32, h6: i32) -> Result { + let n = a.len(); + assert!(n >= 1 && n <= MAX_N); + let rs = rs_of(n); + let mut l = Line::new(J_EW); + l.set(1, sub).set(3, n as i32).set(4, h4).set(5, h5).set(6, h6); + let d = l.data(); + for (i, &v) in a.iter().enumerate() { + d[i] = w32(v, "an element")?; + } + if sub == EW_ADD { + assert_eq!(b.len(), n); + for (i, &v) in b.iter().enumerate() { + d[rs + i] = w32(v, "an element")?; + } + } + Ok(l) +} + +/// A `LUT` line: the table's entries from index `base` (at most [`LUT_BLOCK`]), and `n` +/// input values whose index `v + 32768` lies in that block. +pub fn lut_line(t: &[i16], base: usize, v: &[i64]) -> Result { + let n = v.len(); + assert!(n >= 1 && n <= MAX_N); + let tb = t.len().min(LUT_BLOCK); + let mut l = Line::new(J_LUT); + l.set(3, n as i32).set(4, base as i32).set(5, tb as i32); + let d = l.data(); + for (j, pair) in t[..tb].chunks(2).enumerate() { + let lo = pair[0] as u16 as u32; + let hi = pair.get(1).map_or(0, |&x| x as u16 as u32); + d[j] = (lo | (hi << 16)) as i32; + } + let off = align32(tb.div_ceil(2)); + for (i, &x) in v.iter().enumerate() { + let u = x + 32768 - base as i64; + if !(0..tb as i64).contains(&u) { + return Err(Unfit(format!("value {x} is outside the table block at {base}"))); + } + d[off + i] = x as i32; + } + Ok(l) +} + +/// A `PROBE` line: the unit's raw shift-round of `t·m` by `shift`, under its rounding +/// mode `mode` and saturation mode `sat` (the unit's own encodings). +pub fn probe_line(mode: i32, sat: i32, shift: i32, t: &[i32], m: &[i32]) -> Line { + let n = t.len(); + assert!(n >= 1 && n <= MAX_N && m.len() == n); + let rs = rs_of(n); + let mut l = Line::new(J_PROBE); + l.set(1, mode).set(2, sat).set(3, n as i32).set(9, shift); + let d = l.data(); + d[..n].copy_from_slice(t); + d[rs..rs + n].copy_from_slice(m); + l +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn regions_fit_the_line() { + for w in [Width::W8, Width::W16, Width::W32] { + let n = max_positions(w); + assert!(n >= 64, "{w:?}: {n}"); + for &n in &[1, 63, 64, 65, n] { + for vi in [false, true] { + let k = max_taps(w, n, vi); + assert!(k >= 1, "{w:?} n={n}"); + let rs = rs_of(n); + let used = if vi { align32(rs) } else { 0 } + align32(k) + k * w.row_words(rs); + assert!(used <= DATA, "{w:?} n={n} k={k}: {used} > {DATA}"); + } + } + } + } + + #[test] + fn packing_is_little_endian_and_signed() { + let x = vec![vec![-1i64, 2, -128, 127, 5]]; + let l = mac_line(Width::W8, &[3], &x, &Init::Scalar(7), true, Epi::IDENTITY).unwrap(); + let off = HEADER + 32; + assert_eq!(l.0[off] as u32, 0x7f80_02ff); + assert_eq!(l.0[off + 1], 5); + assert_eq!((l.0[0], l.0[1], l.0[2], l.0[3], l.0[4], l.0[5], l.0[6]), (J_MAC, 8, 1, 5, 1, 0, 7)); + let x = vec![vec![-2i64, 300]]; + let l = mac_line(Width::W16, &[-4], &x, &Init::Vector(vec![9, 10]), false, Epi::IDENTITY).unwrap(); + assert_eq!(&l.0[HEADER..HEADER + 2], &[9, 10]); + assert_eq!(l.0[HEADER + 64], -4); + assert_eq!(l.0[HEADER + 64 + 32] as u32, (300u32 << 16) | 0xfffe); + assert!(mac_line(Width::W8, &[300], &[vec![1]], &Init::Scalar(0), true, Epi::IDENTITY).is_err()); + assert!(mac_line(Width::W16, &[1], &[vec![40000]], &Init::Scalar(0), true, Epi::IDENTITY).is_err()); + } + + #[test] + fn a_lut_line_holds_its_block() { + let t: Vec = (0..LUT_BLOCK as i32).map(|i| (i - 7) as i16).collect(); + let l = lut_line(&t, 4096, &[-28672, -24577]).unwrap(); + assert_eq!(l.0[HEADER] as u32, ((-6i16 as u16 as u32) << 16) | (-7i16 as u16 as u32)); + assert_eq!(&l.0[HEADER + 2048..HEADER + 2050], &[-28672, -24577]); + assert!(lut_line(&t, 4096, &[-24576]).is_err()); + } + + #[test] + fn widths_follow_the_wider_operand() { + assert_eq!(Width::of(8, 3), Width::W8); + assert_eq!(Width::of(9, 3), Width::W16); + assert_eq!(Width::of(1, 16), Width::W16); + assert_eq!(Width::of(17, 1), Width::W32); + } +} diff --git a/wai/wai-ops-npu/src/probe.rs b/wai/wai-ops-npu/src/probe.rs new file mode 100644 index 000000000..ce2470ec0 --- /dev/null +++ b/wai/wai-ops-npu/src/probe.rs @@ -0,0 +1,153 @@ +//! The cases the arithmetic probes run, and what §2 says each gives. +//! +//! Two probes run on a device (`tests/probe.rs`): +//! +//! - **the unit's own shift-round**, `PROBE` lines: `t·m` formed in 64-bit accumulator +//! lanes and shifted and rounded to 32 bits by the unit alone, under each of its +//! rounding modes, at every shift from 0 to 63. This measures which of the unit's +//! modes is which rounding of §2, at which shifts; +//! - **the kernel's requantisation**, `RQ` lines: `R(t·m, s) + post` as every operator +//! that requantises forms it, for every rounding of §2 and every shift from 0 to 63, +//! over the cases whose result fits 32 bits (the values an `acc32` graph forms). + +use wai_ops::graph::Round; + +/// The unit's rounding modes, by its own encoding (the core compiler's definitions). +pub const UNIT_MODES: [(i32, &str); 10] = [ + (0, "floor"), + (1, "ceil"), + (2, "symmetric_floor"), + (3, "symmetric_ceil"), + (8, "negative_inf"), + (9, "positive_inf"), + (10, "symmetric_zero"), + (11, "symmetric_inf"), + (12, "conv_even"), + (13, "conv_odd"), +]; + +/// The unit's saturation modes, by its own encoding. +pub const SAT_NONE: i32 = 0; +pub const SAT_SATURATE: i32 = 1; + +/// The three roundings of §2. +pub const ROUNDS: [Round; 3] = [Round::Floor, Round::HalfUp, Round::HalfEven]; + +pub fn round_name(r: Round) -> &'static str { + match r { + Round::Floor => "floor", + Round::HalfUp => "half_up", + Round::HalfEven => "half_even", + } +} + +/// `R_round(t·m, s)` (§2), exactly. +pub fn spec(t: i32, m: i32, s: u32, r: Round) -> i128 { + wai_ops::interp::round(t as i128 * m as i128, s, r).expect("a 64-bit product rounds in 128 bits") +} + +/// `v` saturated to 32 bits. +pub fn sat32(v: i128) -> i32 { + v.clamp(i32::MIN as i128, i32::MAX as i128) as i32 +} + +/// A fixed SplitMix64 sequence. +pub struct Rng(u64); + +impl Rng { + pub fn new(seed: u64) -> Rng { + Rng(seed) + } + pub fn next(&mut self) -> u64 { + self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut z = self.0; + z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + z ^ (z >> 31) + } + pub fn i32(&mut self) -> i32 { + self.next() as i32 + } + /// A value of at most `bits` bits, signed. + pub fn bits(&mut self, bits: u32) -> i32 { + let v = self.next() as i64; + (v >> (64 - bits.clamp(1, 32))) as i32 + } +} + +/// `n` cases `(t, m)` for shift `s`: a quarter exact ties of §2 (`t·m` an odd multiple +/// of `2^(s−1)`), an eighth one either side of a tie (for `s ≤ 31`), the rest random at +/// every operand width, with the extreme operands among them. +pub fn cases(s: u32, n: usize, rng: &mut Rng) -> Vec<(i32, i32)> { + const EDGE: [i32; 6] = [i32::MIN, i32::MIN + 1, -1, 0, 1, i32::MAX]; + let mut v = Vec::with_capacity(n); + for k in 0..n { + let c = match k % 8 { + 0 | 1 if s >= 1 => tie(s, rng), + 2 if (1..=31).contains(&s) => { + // One unit either side of a tie: t = odd·2^(s−1) ± 1, m = 1. + let u = rng.bits(33 - s) as i64 >> 1; + let t0 = (2 * u + 1) << (s - 1); + ((t0 + if rng.next() & 1 == 0 { 1 } else { -1 }).clamp(i32::MIN as i64, i32::MAX as i64) as i32, 1) + } + 3 => (EDGE[(rng.next() % 6) as usize], EDGE[(rng.next() % 6) as usize]), + 4 => (rng.bits(8), rng.bits(8)), + 5 => (rng.bits(16), rng.bits(16)), + 6 => { + let (bt, bm) = (1 + (rng.next() % 32) as u32, 1 + (rng.next() % 32) as u32); + (rng.bits(bt), rng.bits(bm)) + } + _ => (rng.i32(), rng.i32()), + }; + v.push(c); + } + v +} + +/// A pair whose product is an odd multiple of `2^(s−1)`: an exact tie at shift `s ≥ 1`. +fn tie(s: u32, rng: &mut Rng) -> (i32, i32) { + let b = (s - 1).min(30); + let a = s - 1 - b; + if a <= 29 { + let u = rng.bits(31 - a) as i64 >> 1; + let t = ((2 * u + 1) << a) as i32; + let m = if rng.next() & 1 == 0 { 1i32 << b } else { -(1i32 << b) }; + (t, m) + } else { + // s = 61..63: 2^30·(±1)·(−2^31) = ∓2^61, a tie at s = 62 only; at s = 61 and 63 + // these are the extreme products. + (if rng.next() & 1 == 0 { 1 << 30 } else { -(1 << 30) }, i32::MIN) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ties_are_ties() { + let mut rng = Rng::new(7); + for s in 1..=60u32 { + for _ in 0..200 { + let (t, m) = tie(s, &mut rng); + let p = t as i128 * m as i128; + let h = 1i128 << (s - 1); + assert_eq!(p.rem_euclid(2 * h), h, "s={s} t={t} m={m}"); + } + } + let (t, m) = tie(62, &mut rng); + assert_eq!((t as i128 * m as i128).rem_euclid(1 << 62), 1 << 61); + } + + #[test] + fn the_roundings_differ_where_they_should() { + // A negative tie: floor, half_up and half_even each give their own answer. + assert_eq!(spec(-3, 1, 1, Round::Floor), -2); + assert_eq!(spec(-3, 1, 1, Round::HalfUp), -1); + assert_eq!(spec(-3, 1, 1, Round::HalfEven), -2); + assert_eq!(spec(-5, 1, 1, Round::HalfEven), -2); + assert_eq!(spec(i32::MIN, i32::MIN, 63, Round::Floor), 0); + assert_eq!(spec(i32::MIN, i32::MAX, 63, Round::Floor), -1); + assert_eq!(sat32(1 << 40), i32::MAX); + } +} diff --git a/wai/wai-ops-npu/tests/corpus.rs b/wai/wai-ops-npu/tests/corpus.rs new file mode 100644 index 000000000..dd7995379 --- /dev/null +++ b/wai/wai-ops-npu/tests/corpus.rs @@ -0,0 +1,86 @@ +//! Every graph vector of `ops-conformance/` whose graph is in `wai-ops/1-npu` (§9.2), +//! with its arithmetic operators on the device: each decode's output integers, or its +//! refusal code, must be the committed ones, which are the CPU interpreter's (checked +//! here too, decode by decode). +//! +//! **No device is not a pass.** With no device the test prints `ops-npu corpus: +//! SKIPPED` and the reason, and fails if `WAI_OPS_NPU_REQUIRE` is set. With +//! `WAI_OPS_NPU_EXPECT` (the device's key, or `none`), the device found must be that +//! one. The result is one line, `ops-npu corpus: key=…`, also appended to +//! `WAI_OPS_NPU_REPORT` when it is set; a CI step requires that line with its counts. + +#[path = "support/corpus.rs"] +mod corpus; + +use std::collections::BTreeMap; + +use corpus::report; + +#[test] +fn every_vector_in_the_subset_is_the_interpreters_on_the_device() { + let vs = corpus::vectors(); + let (rs, load_refused) = corpus::ready(&vs); + // Where the corpus stands against the subset, whether or not a device is here. + let mut outside: BTreeMap = BTreeMap::new(); + for r in &rs { + for why in &r.plan.reasons { + *outside.entry(why.clone()).or_default() += 1; + } + } + let inside: Vec<&corpus::Ready<'_>> = rs.iter().filter(|r| r.plan.in_subset()).collect(); + let mut flags: BTreeMap = BTreeMap::new(); + for r in &inside { + for f in &r.plan.flags { + *flags.entry(f.clone()).or_default() += 1; + } + } + report(&format!( + "ops-npu subset: vectors={} loaded={} load_refused={load_refused} in_subset={} outside={}; reasons {:?}; flags in the subset {:?}", + vs.len(), + rs.len(), + inside.len(), + rs.len() - inside.len(), + outside, + flags + )); + assert!(inside.len() > 500, "too few vectors in the subset: {}", inside.len()); + let Some(npu) = corpus::device("corpus") else { return }; + let t0 = std::time::Instant::now(); + let t = corpus::run_all(&npu, &inside, 20); + for m in &t.mismatches { + println!("MISMATCH {m}"); + } + for e in &t.stats.errors { + println!("DEVICE ERROR {e}"); + } + for d in t.stats.declined.iter().take(20) { + println!("DECLINED {d}"); + } + report(&format!( + "ops-npu corpus: key={} {}; vectors={} decodes={} refusals={} movement_only={} npu_ops={} moved={} lines mac8={} mac16={} mac32={} rq={} ew={} lut={} dispatches={} declined={} mismatches={} errors={} secs={:.1}", + npu.key(), + npu.identity().describe(), + t.vectors, + t.decodes, + t.refusals, + t.movement_only, + t.stats.npu_ops, + t.stats.moved, + t.stats.mac[0], + t.stats.mac[1], + t.stats.mac[2], + t.stats.rq, + t.stats.ew, + t.stats.lut, + npu.dispatches(), + t.stats.declined.len(), + t.mismatched, + t.stats.errors.len(), + t0.elapsed().as_secs_f64() + )); + assert!(t.stats.errors.is_empty(), "{}: device errors: {:?}", npu.key(), &t.stats.errors[..t.stats.errors.len().min(5)]); + assert!(t.stats.declined.is_empty(), "{}: operators of the subset declined: {:?}", npu.key(), &t.stats.declined[..t.stats.declined.len().min(5)]); + assert_eq!(t.mismatched, 0, "{}: {} vectors differ, the first: {:?}", npu.key(), t.mismatched, t.mismatches.first()); + // Every path ran: each MAC width, and each other job. + assert!(t.stats.mac.iter().all(|&n| n > 0) && t.stats.rq > 0 && t.stats.ew > 0 && t.stats.lut > 0, "{}: a path never ran: {:?}", npu.key(), t.stats); +} diff --git a/wai/wai-ops-npu/tests/mutants.rs b/wai/wai-ops-npu/tests/mutants.rs new file mode 100644 index 000000000..63e401ab2 --- /dev/null +++ b/wai/wai-ops-npu/tests/mutants.rs @@ -0,0 +1,49 @@ +//! Each edit of the kernel (`MUT` in `kernel/ops_npu.cc`, [`Variant::MUTANTS`]) is its +//! own program, built and run on the device; the conformance corpus must catch every +//! one, or, for the one edit no committed graph reaches, the requantisation probe must. +//! A result equal to the interpreter's under an edited kernel would mean the device did +//! not compute it. +//! +//! With no device the test prints `ops-npu mutants: SKIPPED`, and fails under +//! `WAI_OPS_NPU_REQUIRE`. + +#[path = "support/corpus.rs"] +mod corpus; +#[path = "support/rq.rs"] +mod rq; + +use corpus::report; +use wai_ops_npu::Variant; + +#[test] +fn every_edit_of_the_kernel_is_caught() { + let Some(npu) = corpus::device("mutants") else { return }; + let vs = corpus::vectors(); + let (rs, _) = corpus::ready(&vs); + let inside: Vec<&corpus::Ready<'_>> = rs.iter().filter(|r| r.plan.in_subset()).collect(); + let mut caught = 0; + for (v, what) in Variant::MUTANTS { + let m = npu.with_variant(v).unwrap_or_else(|e| panic!("{}: {e}", v.label())); + let t = corpus::run_all(&m, &inside, usize::MAX); + assert!(t.stats.errors.is_empty(), "{}: device errors: {:?}", v.label(), t.stats.errors.first()); + let by = if t.mismatched > 0 { + // Which corpus files the differing vectors come from. + let mut files: std::collections::BTreeMap = Default::default(); + for m in &t.mismatches { + if let Some(f) = m.split(" (").nth(1).and_then(|r| r.split(')').next()) { + *files.entry(f.to_string()).or_default() += 1; + } + } + format!("the corpus ({} vectors differ, by file {files:?}; the first: {})", t.mismatched, t.mismatches[0]) + } else { + let (bad, first) = rq::run(&m, 1, 0x6d75_7461_6e74); + if bad > 0 { format!("the requantisation probe only ({bad} cases differ; the first: {first})") } else { String::new() } + }; + report(&format!("ops-npu mutant: key={} {} ({what}): {}", npu.key(), v.label(), if by.is_empty() { "SURVIVED" } else { &by })); + if !by.is_empty() { + caught += 1; + } + } + report(&format!("ops-npu mutants: key={} killed {caught} of {}", npu.key(), Variant::MUTANTS.len())); + assert_eq!(caught, Variant::MUTANTS.len(), "an edited kernel gave the interpreter's results"); +} diff --git a/wai/wai-ops-npu/tests/probe.rs b/wai/wai-ops-npu/tests/probe.rs new file mode 100644 index 000000000..8aaf7a5d8 --- /dev/null +++ b/wai/wai-ops-npu/tests/probe.rs @@ -0,0 +1,142 @@ +//! The device's arithmetic, measured against §2 before any graph: which of the unit's +//! rounding modes is which rounding of §2 and at which shifts, what its saturation +//! does, how wide a 32-bit accumulator lane is, and that the kernel's requantisation, +//! as every operator forms it, is exact at every shift and rounding. +//! +//! With no device each test prints `ops-npu : SKIPPED`, and fails under +//! `WAI_OPS_NPU_REQUIRE`. Each result is a line `ops-npu : key=…`, appended to +//! `WAI_OPS_NPU_REPORT` when it is set. + +#[path = "support/corpus.rs"] +mod corpus; +#[path = "support/rq.rs"] +mod rq; + +use corpus::report; +use wai_ops::graph::Round; +use wai_ops_npu::lines::{Epi, Init, MAX_N, Width, mac_line, probe_line}; +use wai_ops_npu::probe::{ROUNDS, Rng, SAT_NONE, SAT_SATURATE, UNIT_MODES, cases, round_name, sat32, spec}; + +/// Shifts written as ranges: `0..59, 61`. +fn ranges(v: &[u32]) -> String { + let mut out: Vec = Vec::new(); + let mut k = 0; + while k < v.len() { + let mut j = k; + while j + 1 < v.len() && v[j + 1] == v[j] + 1 { + j += 1; + } + out.push(if j == k { v[k].to_string() } else { format!("{}..{}", v[k], v[j]) }); + k = j + 1; + } + if out.is_empty() { "none".into() } else { out.join(", ") } +} + +#[test] +fn the_requantisation_is_exact_at_every_shift_and_rounding() { + let Some(npu) = corpus::device("requantisation probe") else { return }; + let (cases, bad, first) = rq::run_counted(&npu, 4, 0x7271_7072_6f62); + report(&format!("ops-npu requantisation: key={} {cases} cases over shifts 0..63 and {:?}, {bad} differ{}", npu.key(), ROUNDS.map(round_name), if bad > 0 { format!("; the first: {first}") } else { String::new() })); + assert!(cases > 500_000, "too few cases: {cases}"); + assert_eq!(bad, 0, "the kernel's requantisation differs from §2: {first}"); +} + +#[test] +fn the_units_rounding_modes_against_section_2() { + let Some(npu) = corpus::device("rounding probe") else { return }; + let mut rng = Rng::new(0x726f_756e_6473); + // The same cases for every mode: per shift, MAX_N of them. + let per_shift: Vec> = (0..=63u32).map(|s| cases(s, MAX_N, &mut rng)).collect(); + let mut lines = Vec::new(); + for &(mode, _) in &UNIT_MODES { + for s in 0..=63u32 { + let (t, m): (Vec, Vec) = per_shift[s as usize].iter().copied().unzip(); + lines.push(probe_line(mode, SAT_SATURATE, s as i32, &t, &m)); + } + } + let outs = npu.dispatch(&lines).expect("the probe's dispatches"); + let mut exact_modes = Vec::new(); + for (mi, &(mode, name)) in UNIT_MODES.iter().enumerate() { + let mut desc = Vec::new(); + for r in ROUNDS { + // Shifts at which the mode equals R_r on every case whose result fits 32 bits. + let mut eq = Vec::new(); + for s in 0..=63u32 { + let o = &outs[mi * 64 + s as usize]; + let ok = per_shift[s as usize].iter().enumerate().all(|(i, &(t, m))| { + let q = spec(t, m, s, r); + i32::try_from(q).is_err() || o[i] as i128 == q + }); + if ok { + eq.push(s); + } + } + if !eq.is_empty() { + desc.push(format!("{} at shifts {}", round_name(r), ranges(&eq))); + } + if eq.len() == 64 || eq == (0..=59).collect::>() { + exact_modes.push((name, r, eq.len())); + } + } + // What the unit gives at the shifts it does not take: a left shift by 64 − s? + let left: Vec = (60..=63u32) + .filter(|&s| { + let o = &outs[mi * 64 + s as usize]; + per_shift[s as usize].iter().enumerate().all(|(i, &(t, m))| o[i] == sat32((t as i128 * m as i128) << (64 - s))) + }) + .collect(); + report(&format!( + "ops-npu unit rounding: key={} mode {name} ({mode}): {}; at shifts {} it gives t·m shifted left by 64 − s, saturated", + npu.key(), + if desc.is_empty() { "equals no rounding of §2 at any shift but 0".into() } else { desc.join("; ") }, + ranges(&left) + )); + } + // The three modes the kernel uses are exact at shifts 0..59 and at no larger shift. + for (name, r) in [("floor", Round::Floor), ("positive_inf", Round::HalfUp), ("conv_even", Round::HalfEven)] { + assert!(exact_modes.iter().any(|&(n, rr, len)| n == name && rr == r && len == 60), "{name} is not {} at exactly the shifts 0..59: {exact_modes:?}", round_name(r)); + } + // And no mode is exact at shifts 60..63, which is why the kernel takes them elsewhere. + assert!(exact_modes.iter().all(|&(_, _, len)| len < 64), "a mode is exact at every shift: {exact_modes:?}"); +} + +#[test] +fn saturation_and_the_accumulator_lanes() { + let Some(npu) = corpus::device("lane probe") else { return }; + // q past 32 bits: shift 0 of products past i32, under each saturation mode. + let mut rng = Rng::new(0x7361_7475_7261); + let (t, m): (Vec, Vec) = (0..MAX_N).map(|_| (rng.i32() | 0x10000, rng.i32() | 0x10000)).unzip(); + let outs = npu.dispatch(&[probe_line(0, SAT_SATURATE, 0, &t, &m), probe_line(0, SAT_NONE, 0, &t, &m)]).unwrap(); + let past = |i: usize| i32::try_from(t[i] as i64 * m[i] as i64).is_err(); + let n_past = (0..MAX_N).filter(|&i| past(i)).count(); + let sat_ok = (0..MAX_N).all(|i| outs[0][i] == sat32(t[i] as i128 * m[i] as i128)); + let wrap_ok = (0..MAX_N).all(|i| outs[1][i] == (t[i] as i64 * m[i] as i64) as i32); + report(&format!("ops-npu saturation: key={} {n_past} of {MAX_N} products past 32 bits: the saturating mode clamps them ({sat_ok}); the non-saturating mode keeps their low 32 bits ({wrap_ok})", npu.key())); + assert!(n_past > 100 && sat_ok); + + // Sums past 32 bits in a 32-bit accumulator lane (8- and 16-bit operands), and past + // 64 bits in a 64-bit lane (32-bit operands): each lane starts at `init` and adds k + // equal products; what comes back is the lane as the unit narrows it to 32 bits. + for (width, v, init) in [(Width::W8, -128i64, i32::MAX - 10), (Width::W16, -32768, 0), (Width::W32, i32::MIN as i64, 0)] { + let mut res = Vec::new(); + for k in [1usize, 2, 3, 4] { + let w = vec![v; k]; + let x = vec![vec![v; 64]; k]; + let o = &npu.dispatch(&[mac_line(width, &w, &x, &Init::Scalar(init), false, Epi::IDENTITY).unwrap()]).unwrap()[0]; + let exact = init as i128 + k as i128 * v as i128 * v as i128; + let kind = if o[0] as i128 == exact { + "exact" + } else if o[0] == exact as i64 as i32 { + "its low 32 bits" + } else if o[0] == sat32(exact) { + "saturated to 32 bits" + } else if exact != exact as i64 as i128 && o[0] == sat32(exact as i64 as i128) { + "wrapped at 64 bits, then saturated" + } else { + "other" + }; + res.push(format!("{init} + {k}×{}: {} ({kind})", v * v, o[0])); + } + report(&format!("ops-npu accumulator: key={} {}: {}", npu.key(), width.as_str(), res.join("; "))); + } +} diff --git a/wai/wai-ops-npu/tests/support/corpus.rs b/wai/wai-ops-npu/tests/support/corpus.rs new file mode 100644 index 000000000..e0407275c --- /dev/null +++ b/wai/wai-ops-npu/tests/support/corpus.rs @@ -0,0 +1,227 @@ +//! The graph vectors of `ops-conformance/` (`vectors/` and `graphs/`), run with their +//! arithmetic on a device and held to the committed expectations and to the CPU +//! interpreter; and the device tests' shared reporting. + +#![allow(dead_code)] + +use std::io::Write; +use std::path::PathBuf; + +use wai_ops::json::{self, Value, unhex}; +use wai_ops::vectors::read_tensor; +use wai_ops::{Loaded, Profile, Sequence, Tensor}; +use wai_ops_npu::{Npu, Plan, Stats, discover, plan}; + +pub fn conformance() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../ops-conformance") +} + +/// A report line: printed, and appended to `WAI_OPS_NPU_REPORT` when it is set. +pub fn report(line: &str) { + println!("{line}"); + if let Ok(p) = std::env::var("WAI_OPS_NPU_REPORT") { + let mut f = std::fs::OpenOptions::new().create(true).append(true).open(&p).unwrap_or_else(|e| panic!("{p}: {e}")); + writeln!(f, "{line}").unwrap(); + } +} + +/// The device this host has, held to `WAI_OPS_NPU_EXPECT` (its key, or `none`); or +/// `None` with SKIPPED reported, which fails under `WAI_OPS_NPU_REQUIRE`. +pub fn device(test: &str) -> Option { + let d = discover(); + for r in &d.refused { + report(&format!("ops-npu device refused: key={} {} reason={}", r.key, r.device, r.error)); + } + let mut keys: Vec = d.found.iter().map(|n| n.key()).collect(); + keys.sort(); + if let Ok(want) = std::env::var("WAI_OPS_NPU_EXPECT") { + let got = if keys.is_empty() { "none".to_string() } else { keys.join(",") }; + assert_eq!(got, want, "the devices found are not the ones WAI_OPS_NPU_EXPECT declares"); + report(&format!("ops-npu devices: as declared ({got})")); + } + let mut found = d.found; + if found.is_empty() { + let why = d.unavailable.clone().unwrap_or_else(|| format!("{} device(s) refused, above", d.refused.len())); + report(&format!("ops-npu {test}: SKIPPED: no device ran it: {why}")); + assert!(std::env::var_os("WAI_OPS_NPU_REQUIRE").is_none(), "WAI_OPS_NPU_REQUIRE is set and no device ran the {test}"); + return None; + } + Some(found.remove(0)) +} + +/// Every graph-vector file: `vectors/*.jsonl`, then `graphs/*.jsonl`, each sorted by name. +pub fn files() -> Vec<(String, String)> { + let mut out = Vec::new(); + for dir in ["vectors", "graphs"] { + let d = conformance().join(dir); + let mut names: Vec = std::fs::read_dir(&d) + .unwrap_or_else(|e| panic!("{}: {e}", d.display())) + .filter_map(|e| e.ok().map(|e| e.file_name().to_string_lossy().into_owned())) + .filter(|n| n.ends_with(".jsonl")) + .collect(); + names.sort(); + for n in names { + let text = std::fs::read_to_string(d.join(&n)).unwrap(); + out.push((format!("{dir}/{n}"), text)); + } + } + assert!(out.len() > 30, "the corpus was not found"); + out +} + +/// A vector's expectation for one decode: the outputs, or a refusal code. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Expect { + Outputs(Vec), + Refuse(String), +} + +pub struct Decode { + pub inputs: Vec, + pub scalars: Vec, + pub expect: Expect, +} + +pub struct Vector { + pub file: String, + pub id: String, + pub graph: Vec, + pub load: String, + pub decodes: Vec, +} + +pub fn vectors() -> Vec { + files().iter().flat_map(|(file, text)| parse(file, text)).collect() +} + +pub fn parse(file: &str, text: &str) -> Vec { + let mut out = Vec::new(); + for line in text.split_terminator('\n') { + let v = json::parse(line.as_bytes()).expect("a canonical line"); + let id = v.get("id").and_then(Value::as_str).unwrap().to_string(); + let graph = unhex(v.get("graph").and_then(Value::as_str).unwrap()).unwrap(); + let load = v.get("load").and_then(Value::as_str).unwrap().to_string(); + let decodes = v + .get("decodes") + .and_then(Value::as_arr) + .unwrap() + .iter() + .map(|d| { + let inputs = d.get("inputs").and_then(Value::as_arr).unwrap().iter().map(|t| read_tensor(t).unwrap()).collect(); + let scalars = d.get("scalars").and_then(Value::as_arr).unwrap().iter().map(|x| x.as_dec_i64().expect("an i64")).collect(); + let e = d.get("expect").unwrap(); + let expect = match e.get("refuse") { + Some(c) => Expect::Refuse(c.as_str().unwrap().to_string()), + None => Expect::Outputs(e.get("outputs").and_then(Value::as_arr).unwrap().iter().map(|t| read_tensor(t).unwrap()).collect()), + }; + Decode { inputs, scalars, expect } + }) + .collect(); + out.push(Vector { file: file.to_string(), id, graph, load, decodes }); + } + out +} + +/// A loaded vector and its plan. +pub struct Ready<'a> { + pub v: &'a Vector, + pub l: Loaded, + pub plan: Plan, +} + +/// Every vector that loads, with its plan; and the count refused at load. +pub fn ready(vs: &[Vector]) -> (Vec>, u32) { + let mut out = Vec::new(); + let mut refused = 0; + for v in vs { + match wai_ops::graph::load(&v.graph, Profile::Acc64) { + Ok(l) => { + assert_eq!(v.load, "ok", "{}", v.id); + let p = plan(&l).unwrap_or_else(|e| panic!("{}: {e}", v.id)); + out.push(Ready { v, l, plan: p }); + } + Err(r) => { + assert_eq!(r.code.as_str(), v.load, "{}: the load the vector records", v.id); + refused += 1; + } + } + } + (out, refused) +} + +fn got_of(r: Result, wai_ops::Refusal>) -> Expect { + match r { + Ok(t) => Expect::Outputs(t), + Err(r) => Expect::Refuse(r.code.as_str().to_string()), + } +} + +/// What one device did with a set of vectors. +#[derive(Clone, Debug, Default)] +pub struct Tally { + pub vectors: u32, + pub decodes: u32, + pub refusals: u32, + /// Vectors whose graph is in the subset but moves data only: nothing for the core. + pub movement_only: u32, + pub stats: Stats, + /// Vectors with a decode whose result differs, and the first `limit` of them. + pub mismatched: u32, + pub mismatches: Vec, +} + +/// Run every decode of `rs` with its arithmetic on `npu`, against the committed +/// expectation and the interpreter, stopping a vector at its first mismatch. `limit` +/// caps the mismatches recorded. +pub fn run_all(npu: &Npu, rs: &[&Ready<'_>], limit: usize) -> Tally { + let mut t = Tally::default(); + for r in rs { + t.vectors += 1; + let mut seq = Sequence::new(); + let mut cpu = Sequence::new(); + let before = t.stats.npu_ops; + for (k, d) in r.v.decodes.iter().enumerate() { + let want = got_of(wai_ops::interp::run(&r.l, &d.inputs, &d.scalars, &mut cpu)); + assert_eq!(want, d.expect, "{}: decode {k}: the CPU interpreter's result is the committed one", r.v.id); + t.decodes += 1; + if matches!(d.expect, Expect::Refuse(_)) { + t.refusals += 1; + } + let (res, st) = wai_ops_npu::run(npu, &r.l, &r.plan, &d.inputs, &d.scalars, &mut seq); + let erred = !st.errors.is_empty(); + t.stats.add(&st); + if erred { + break; + } + let got = got_of(res); + if got != d.expect { + t.mismatched += 1; + if t.mismatches.len() < limit { + t.mismatches.push(format!("{} ({}) decode {k}: {}", r.v.id, r.v.file, diff(&d.expect, &got))); + } + break; + } + } + if t.stats.npu_ops == before { + t.movement_only += 1; + } + } + t +} + +pub fn diff(want: &Expect, got: &Expect) -> String { + match (want, got) { + (Expect::Outputs(a), Expect::Outputs(b)) => { + for (k, (x, y)) in a.iter().zip(b).enumerate() { + if (x.c, x.h, x.w) != (y.c, y.h, y.w) { + return format!("output {k} shape {:?} vs {:?}", (x.c, x.h, x.w), (y.c, y.h, y.w)); + } + if let Some(i) = (0..x.data.len()).find(|&i| x.data[i] != y.data[i]) { + return format!("output {k} element {i}: want {} got {}", x.data[i], y.data[i]); + } + } + "output count".into() + } + _ => format!("want {want:?} got {got:?}").chars().take(300).collect(), + } +} diff --git a/wai/wai-ops-npu/tests/support/rq.rs b/wai/wai-ops-npu/tests/support/rq.rs new file mode 100644 index 000000000..c23208a85 --- /dev/null +++ b/wai/wai-ops-npu/tests/support/rq.rs @@ -0,0 +1,93 @@ +//! The requantisation probe: `R(t·m, s) + post` (§2, §5.1) through the kernel's +//! epilogue, as every operator that requantises forms it, for each rounding of §2 and +//! every shift from 0 to 63, over cases whose result fits 32 bits (the values an `acc32` +//! graph forms). Each line holds one multiplier and post bias, as one channel does: +//! powers of two (so that a quarter of its cases are exact ties and some are one unit +//! off a tie), random values at every width, and the extremes. + +#![allow(dead_code)] + +use wai_ops_npu::Npu; +use wai_ops_npu::lines::{Epi, MAX_N, RQ_RESCALE, round_code, rq_line}; +use wai_ops_npu::probe::{ROUNDS, Rng, round_name, spec}; + +/// What one probe run gave: the cases run, those that differ, and the first that did. +pub fn run(npu: &Npu, reps: usize, seed: u64) -> (u64, String) { + let (_, bad, first) = run_counted(npu, reps, seed); + (bad, first) +} + +pub fn run_counted(npu: &Npu, reps: usize, seed: u64) -> (u64, u64, String) { + let mut rng = Rng::new(seed); + let mut lines = Vec::new(); + let mut want: Vec<(Vec, i32, u32, &'static str, Vec)> = Vec::new(); + for s in 0..=63u32 { + for r in ROUNDS { + for _ in 0..reps { + let class = rng.next() % 6; + let (m, pow_b) = match class { + 0 | 1 => { + let b = if s == 0 { 0 } else { (rng.next() % s.min(31) as u64) as u32 }; + let v = 1i64 << b; + ((if class == 0 { v } else { -v }).clamp(i32::MIN as i64, i32::MAX as i64) as i32, Some(b)) + } + 2 => (rng.i32(), None), + 3 => (rng.bits(16), None), + 4 => ([i32::MIN, i32::MAX, -1, 1][(rng.next() % 4) as usize], None), + _ => (rng.bits(8), None), + }; + let post = if rng.next() % 3 == 0 { 0 } else { rng.bits(20) }; + let mut ts = Vec::with_capacity(MAX_N); + let mut ys = Vec::with_capacity(MAX_N); + let mut tries = 0; + while ts.len() < MAX_N && tries < 200 * MAX_N { + tries += 1; + let k = ts.len(); + let t = match pow_b { + Some(b) if s >= 1 && k % 4 < 2 && s - 1 - b <= 29 => { + let a = s - 1 - b; + let u = rng.bits(31 - a) as i64 >> 1; + let t = ((2 * u + 1) << a) as i32; + if k % 4 == 1 { t.saturating_add(if rng.next() & 1 == 0 { 1 } else { -1 }) } else { t } + } + _ => match rng.next() % 4 { + 0 => rng.i32(), + 1 => { + let bt = 1 + (rng.next() % 32) as u32; + rng.bits(bt) + } + 2 => [i32::MIN, i32::MAX, -1, 0, 1][(rng.next() % 5) as usize], + _ => rng.bits(12), + }, + }; + let q = spec(t, m, s, r); + let y = q + post as i128; + if i32::try_from(q).is_ok() && i32::try_from(y).is_ok() { + ts.push(t); + ys.push(y as i64); + } + } + if ts.is_empty() { + continue; + } + let x: Vec = ts.iter().map(|&t| t as i64).collect(); + lines.push(rq_line(RQ_RESCALE, &x, &[], 0, Epi { round: round_code(r), shift: s as i32, mult: m, post }).unwrap()); + want.push((ts, m, s, round_name(r), ys)); + } + } + } + let outs = npu.dispatch(&lines).expect("the probe's dispatches"); + let (mut cases, mut bad, mut first) = (0u64, 0u64, String::new()); + for ((ts, m, s, r, ys), o) in want.iter().zip(&outs) { + for (i, (&t, &y)) in ts.iter().zip(ys).enumerate() { + cases += 1; + if o[i] as i64 != y { + if bad == 0 { + first = format!("{r} s={s} t={t} m={m}: want {y}, got {}", o[i]); + } + bad += 1; + } + } + } + (cases, bad, first) +} diff --git a/wai/wai-ops-npu/tests/width.rs b/wai/wai-ops-npu/tests/width.rs new file mode 100644 index 000000000..291fa4362 --- /dev/null +++ b/wai/wai-ops-npu/tests/width.rs @@ -0,0 +1,38 @@ +//! Why the certificate decides which graphs run here: the graphs of the corpus that use +//! only the subset's operators but whose verdict does not meet `acc32` (§9.2 item 1), +//! run with their arithmetic on the device anyway, give results that differ from the +//! interpreter's, or carry values past the words a line holds. On every graph the +//! subset admits, the device agrees (`tests/corpus.rs`). +//! +//! With no device the test prints `ops-npu width: SKIPPED`, and fails under +//! `WAI_OPS_NPU_REQUIRE`. + +#[path = "support/corpus.rs"] +mod corpus; + +use corpus::report; + +#[test] +fn graphs_the_certificate_keeps_out_are_not_exact_here() { + let vs = corpus::vectors(); + let (rs, _) = corpus::ready(&vs); + let only_acc32: Vec<&corpus::Ready<'_>> = rs.iter().filter(|r| r.plan.reasons == ["acc32"]).collect(); + assert!(only_acc32.len() > 100, "too few graphs outside the subset by acc32 alone: {}", only_acc32.len()); + let Some(npu) = corpus::device("width") else { return }; + let t = corpus::run_all(&npu, &only_acc32, 3); + // A graph whose values leave the words a line carries is declined operator by + // operator; those ran on the CPU and say nothing about the device. + report(&format!( + "ops-npu width: key={} graphs outside the subset by acc32 alone={}: on the device {} differ from the interpreter; operators declined (values past 32 bits)={}; errors={}", + npu.key(), + only_acc32.len(), + t.mismatched, + t.stats.declined.len(), + t.stats.errors.len() + )); + for m in &t.mismatches { + println!("DIFFERS {m}"); + } + assert!(t.stats.errors.is_empty(), "device errors: {:?}", t.stats.errors.first()); + assert!(t.mismatched > 0, "every graph the certificate keeps from the subset agreed on the device"); +} -- 2.54.0 (Apple Git-157) From cb1515d96d36326ba56c5f41f2dc5fafa2af0782 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:12:32 -0400 Subject: [PATCH 002/242] ops-conformance: npu.jsonl, each graph vector's place in wai-ops/1-npu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One line per graph vector of vectors/ and graphs/ whose graph loads, with the npu, npu_flags and npu_reasons of its graph's own verdict (§8.5): the vectors an operator implementation of the accelerator subset must reproduce, and the features each exercises. The generator (wai-verify vectors gen) writes it, so vectors verify requires its bytes; the independent checker computes it from its own bounds pass and requires the same bytes. 733 of the 1635 graph vectors that load are in the subset. Co-Authored-By: Claude Opus 5.5 --- wai/ops-conformance/npu.jsonl | 1635 +++++++++++++++++++++++++++++++ wai/tools/wai_ops_check.py | 35 +- wai/wai-ops/src/generate/mod.rs | 30 + 3 files changed, 1696 insertions(+), 4 deletions(-) create mode 100644 wai/ops-conformance/npu.jsonl diff --git a/wai/ops-conformance/npu.jsonl b/wai/ops-conformance/npu.jsonl new file mode 100644 index 000000000..0296919e4 --- /dev/null +++ b/wai/ops-conformance/npu.jsonl @@ -0,0 +1,1635 @@ +{"file":"graphs/load.jsonl","id":"load/ok/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/load.jsonl","id":"load/step5/56","npu":true,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult"],"npu_reasons":[]} +{"file":"graphs/load.jsonl","id":"load/step5/58","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"graphs/load.jsonl","id":"load/crop_plan/59","npu":false,"npu_flags":[],"npu_reasons":["crop_scalar"]} +{"file":"graphs/load.jsonl","id":"load/step8/61","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"graphs/lower_int_synth.jsonl","id":"lower_int_synth/synth/synth/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/lower_int_synth.jsonl","id":"lower_int_synth/synth/synth/range/1","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/lower_jpegai.jsonl","id":"lower_jpegai/jpegai/hs_y/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/lower_jpegai.jsonl","id":"lower_jpegai/jpegai/hs_y/range/1","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/lower_jpegai.jsonl","id":"lower_jpegai/jpegai/hs_uv/2","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/lower_jpegai.jsonl","id":"lower_jpegai/jpegai/hs_uv/range/3","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/lower_jpegai.jsonl","id":"lower_jpegai/jpegai/rec_y/4","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"graphs/lower_jpegai.jsonl","id":"lower_jpegai/jpegai/rec_y/range/5","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"graphs/lower_jpegai.jsonl","id":"lower_jpegai/jpegai/rec_uv/6","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"graphs/lower_jpegai.jsonl","id":"lower_jpegai/jpegai/rec_uv/range/7","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"graphs/lower_jpegai.jsonl","id":"lower_jpegai/jpegai/syn_y/8","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","op:apply","op:elu","op:gram","op:l2norm","op:layernorm_c","op:sigmoid","op:softmax"]} +{"file":"graphs/lower_jpegai.jsonl","id":"lower_jpegai/jpegai/syn_y/range/9","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","op:apply","op:elu","op:gram","op:l2norm","op:layernorm_c","op:sigmoid","op:softmax"]} +{"file":"graphs/lower_jpegai.jsonl","id":"lower_jpegai/jpegai/syn_uv/10","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","op:apply","op:elu","op:gram","op:l2norm","op:layernorm_c","op:sigmoid","op:softmax"]} +{"file":"graphs/lower_jpegai.jsonl","id":"lower_jpegai/jpegai/syn_uv/range/11","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","op:apply","op:elu","op:gram","op:l2norm","op:layernorm_c","op:sigmoid","op:softmax"]} +{"file":"graphs/lower_wam1.jsonl","id":"lower_wam1/audio/g_s/0","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","crop_scalar","op:igdn"]} +{"file":"graphs/lower_wam1.jsonl","id":"lower_wam1/audio/g_s/range/1","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","crop_scalar","op:igdn"]} +{"file":"graphs/lower_wam1.jsonl","id":"lower_wam1/audio/g_s/t_out/2","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","crop_scalar","op:igdn"]} +{"file":"graphs/lower_wam1.jsonl","id":"lower_wam1/audio/g_s/t_out/3","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","crop_scalar","op:igdn"]} +{"file":"graphs/lower_wam1.jsonl","id":"lower_wam1/audio/g_s/t_out/4","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","crop_scalar","op:igdn"]} +{"file":"graphs/lower_wam1.jsonl","id":"lower_wam1/audio/g_s/t_out/5","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","crop_scalar","op:igdn"]} +{"file":"graphs/lower_whm1.jsonl","id":"lower_whm1/hyper/h_s/0","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"graphs/lower_whm1.jsonl","id":"lower_whm1/hyper/h_s/range/1","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"graphs/lower_whm1.jsonl","id":"lower_whm1/hyper/g_s/2","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","op:igdn"]} +{"file":"graphs/lower_whm1.jsonl","id":"lower_whm1/hyper/g_s/range/3","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","op:igdn"]} +{"file":"graphs/lower_wmb1.jsonl","id":"lower_wmb1/mlicpp/h_s/0","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"graphs/lower_wmb1.jsonl","id":"lower_wmb1/mlicpp/h_s/range/1","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"graphs/lower_wmb1.jsonl","id":"lower_wmb1/mlicpp/entropy/2","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"graphs/lower_wmb1.jsonl","id":"lower_wmb1/mlicpp/entropy/range/3","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"graphs/lower_wmb1.jsonl","id":"lower_wmb1/mlicpp/g_s/4","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","op:igdn"]} +{"file":"graphs/lower_wmb1.jsonl","id":"lower_wmb1/mlicpp/g_s/range/5","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","op:igdn"]} +{"file":"graphs/lower_world.jsonl","id":"lower_world/world/T/0","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"graphs/lower_world.jsonl","id":"lower_world/world/g_s/1","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","op:igdn"]} +{"file":"graphs/lower_world.jsonl","id":"lower_world/world/g_s/range/2","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32","op:igdn"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/0","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/1","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/2","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/3","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/4","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/5","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/6","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/7","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/8","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/9","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/10","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/11","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/12","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/13","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/14","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/15","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/16","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/phase.jsonl","id":"phase/phase/17","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/ok/0","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/step1/1","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/step1/2","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/step1/3","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/order/4","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/step2/5","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/step2/6","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/order/7","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/step3/8","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/step3/9","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/order/10","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/step4/11","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/step4/12","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/step4/13","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"graphs/run.jsonl","id":"run/step1/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/run.jsonl","id":"run/step3/15","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/run.jsonl","id":"run/step1/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/run.jsonl","id":"run/step1/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/run.jsonl","id":"run/caps/18","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/run.jsonl","id":"run/caps/19","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/run.jsonl","id":"run/caps/20","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/run.jsonl","id":"run/caps/21","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/run.jsonl","id":"run/order/22","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"graphs/run.jsonl","id":"run/step3/23","npu":false,"npu_flags":[],"npu_reasons":["crop_scalar"]} +{"file":"graphs/state.jsonl","id":"state/carry/0","npu":true,"npu_flags":["state_add"],"npu_reasons":[]} +{"file":"graphs/state.jsonl","id":"state/carry/1","npu":true,"npu_flags":["state_add"],"npu_reasons":[]} +{"file":"graphs/state.jsonl","id":"state/check/2","npu":true,"npu_flags":["state_add"],"npu_reasons":[]} +{"file":"graphs/state.jsonl","id":"state/shape/3","npu":true,"npu_flags":["state_add"],"npu_reasons":[]} +{"file":"graphs/state.jsonl","id":"state/carry/4","npu":true,"npu_flags":["rq_half_even","state_add"],"npu_reasons":[]} +{"file":"graphs/state.jsonl","id":"state/refused/5","npu":false,"npu_flags":["state_add"],"npu_reasons":["acc32"]} +{"file":"graphs/state.jsonl","id":"state/refused/6","npu":true,"npu_flags":["state_add"],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/1","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/2","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/3","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/4","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/5","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/abs.jsonl","id":"abs/random/6","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/7","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/8","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/9","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/10","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/11","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/12","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/abs.jsonl","id":"abs/random/13","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/15","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/abs.jsonl","id":"abs/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/18","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/19","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/abs.jsonl","id":"abs/random/20","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/21","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/22","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/23","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/24","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/abs.jsonl","id":"abs/random/25","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/abs.jsonl","id":"abs/random/26","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/abs.jsonl","id":"abs/random/27","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/abs.jsonl","id":"abs/random/28","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/29","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/30","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/abs.jsonl","id":"abs/random/31","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/abs.jsonl","id":"abs/random/32","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/abs.jsonl","id":"abs/random/33","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/34","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/35","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/36","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/37","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/abs.jsonl","id":"abs/random/38","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/abs.jsonl","id":"abs/random/39","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/abs.jsonl","id":"abs/edge/40","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/abs.jsonl","id":"abs/edge32/41","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/0","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/1","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/2","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/3","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/4","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/6","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/7","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/8","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/9","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/10","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/11","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/12","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/13","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/14","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/15","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/18","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/19","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/20","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/21","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/22","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/23","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/24","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/25","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/26","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/27","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/28","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/29","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/30","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/31","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/32","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/33","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/34","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/35","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/36","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/37","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/38","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/39","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/40","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/41","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/42","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/43","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/44","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/45","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/46","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/47","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/48","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/49","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/50","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/51","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/52","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/53","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/54","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/55","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/56","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/57","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/random/58","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/random/59","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/edge/60","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/edge/61","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/edge/62","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/add.jsonl","id":"add/saturating/63","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/geometry/64","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/exponent/65","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/add.jsonl","id":"add/exponent/66","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/apply.jsonl","id":"apply/random/0","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/1","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/2","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/3","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/4","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/5","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/6","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/7","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/8","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/9","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/10","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/11","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/12","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/13","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/14","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/15","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/16","npu":false,"npu_flags":[],"npu_reasons":["op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/17","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/18","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/19","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/20","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/21","npu":false,"npu_flags":[],"npu_reasons":["op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/22","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/23","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/24","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/25","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/26","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/27","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/28","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/29","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/30","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/31","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/32","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/33","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/34","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/35","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/36","npu":false,"npu_flags":[],"npu_reasons":["op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/37","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/38","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/random/39","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32","op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/geometry/40","npu":false,"npu_flags":[],"npu_reasons":["op:apply"]} +{"file":"vectors/apply.jsonl","id":"apply/geometry/41","npu":false,"npu_flags":[],"npu_reasons":["op:apply"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/0","npu":true,"npu_flags":["chscale","rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/1","npu":true,"npu_flags":["chscale","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/2","npu":false,"npu_flags":["chscale","rq_floor_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/3","npu":true,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/4","npu":true,"npu_flags":["chscale","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/5","npu":true,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/6","npu":true,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/7","npu":true,"npu_flags":["chscale","rq_half_even","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/8","npu":true,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/9","npu":true,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/10","npu":true,"npu_flags":["chscale","rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/11","npu":true,"npu_flags":["chscale","rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/12","npu":false,"npu_flags":["chscale","rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/13","npu":false,"npu_flags":["chscale","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/14","npu":false,"npu_flags":["chscale","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/15","npu":true,"npu_flags":["chscale","rq_half_even","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/16","npu":true,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/17","npu":false,"npu_flags":["chscale","rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/18","npu":false,"npu_flags":["chscale","rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/19","npu":false,"npu_flags":["chscale","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/20","npu":false,"npu_flags":["chscale","rq_half_even","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/21","npu":false,"npu_flags":["chscale","rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/22","npu":true,"npu_flags":["chscale","rq_half_even","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/23","npu":true,"npu_flags":["chscale","rq_floor_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/24","npu":false,"npu_flags":["chscale","rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/25","npu":true,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/26","npu":true,"npu_flags":["chscale","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/27","npu":false,"npu_flags":["chscale","rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/28","npu":false,"npu_flags":["chscale","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/29","npu":false,"npu_flags":["chscale","rq_half_even","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/30","npu":false,"npu_flags":["chscale","rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/31","npu":false,"npu_flags":["chscale","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/32","npu":true,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/33","npu":false,"npu_flags":["chscale","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/34","npu":true,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/35","npu":false,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/36","npu":false,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/37","npu":true,"npu_flags":["chscale"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/38","npu":true,"npu_flags":["chscale","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/39","npu":true,"npu_flags":["chscale","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/40","npu":false,"npu_flags":["chscale","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/41","npu":true,"npu_flags":["chscale","rq_half_even","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/42","npu":false,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/43","npu":false,"npu_flags":["chscale","rq_half_even","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/44","npu":false,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/45","npu":true,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/46","npu":false,"npu_flags":["chscale","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/47","npu":false,"npu_flags":["chscale","rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/48","npu":false,"npu_flags":["chscale","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/49","npu":false,"npu_flags":["chscale","rq_half_even","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/50","npu":false,"npu_flags":["chscale","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/51","npu":false,"npu_flags":["chscale","rq_half_even","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/52","npu":true,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/53","npu":true,"npu_flags":["chscale","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/54","npu":false,"npu_flags":["chscale","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/55","npu":true,"npu_flags":["chscale","rq_floor_mult","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/56","npu":true,"npu_flags":["chscale","rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/57","npu":true,"npu_flags":["chscale","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/58","npu":false,"npu_flags":["chscale","rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/random/59","npu":false,"npu_flags":["chscale","rq_half_even","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/scalar/60","npu":true,"npu_flags":["chscale"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/scalar/61","npu":true,"npu_flags":["chscale"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/scalar/62","npu":true,"npu_flags":["chscale"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/scalar/63","npu":true,"npu_flags":["chscale"],"npu_reasons":[]} +{"file":"vectors/chscale.jsonl","id":"chscale/acc32_param/65","npu":false,"npu_flags":["chscale","rq_floor_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/chscale.jsonl","id":"chscale/acc32_param/66","npu":false,"npu_flags":["chscale","rq_floor_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/1","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/2","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/3","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/4","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/6","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/7","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/8","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/9","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/10","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/11","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/12","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/13","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/15","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/18","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/19","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/20","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/21","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/22","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/23","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/24","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/25","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/26","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/27","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/28","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/29","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/30","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/31","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/32","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/33","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/34","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/35","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/36","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/37","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/38","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/random/39","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/edge32/40","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/chunk_add.jsonl","id":"chunk_add/edge32/41","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/1","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/2","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/3","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/4","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/6","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/7","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/8","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/9","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/10","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/11","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/12","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/13","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/15","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/18","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/19","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/20","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/21","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/22","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/23","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/24","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/25","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/26","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/27","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/28","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/29","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/30","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/31","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/32","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/33","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/34","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/35","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/36","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/37","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/38","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/39","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/40","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/41","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/42","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/43","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/44","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/45","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/46","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/47","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/48","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/random/49","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/param/50","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/param/51","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/clamp.jsonl","id":"clamp/param/52","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/1","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/2","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/3","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/4","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/6","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/concat.jsonl","id":"concat/random/7","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/concat.jsonl","id":"concat/random/8","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/9","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/concat.jsonl","id":"concat/random/10","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/11","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/12","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/concat.jsonl","id":"concat/random/13","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/15","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/18","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/19","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/20","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/concat.jsonl","id":"concat/random/21","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/22","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/concat.jsonl","id":"concat/random/23","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/concat.jsonl","id":"concat/random/24","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/concat.jsonl","id":"concat/random/25","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/26","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/27","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/concat.jsonl","id":"concat/random/28","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/concat.jsonl","id":"concat/random/29","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/30","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/31","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/32","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/33","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/concat.jsonl","id":"concat/random/34","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/35","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/36","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/37","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/38","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/random/39","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/concat.jsonl","id":"concat/geometry/40","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/0","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/1","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/2","npu":true,"npu_flags":["rq_half_even","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/3","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/4","npu":false,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/5","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/6","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/7","npu":true,"npu_flags":["rq_half_even","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/8","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/9","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/10","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/11","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/12","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/13","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/14","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/15","npu":false,"npu_flags":[],"npu_reasons":["conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/16","npu":true,"npu_flags":["rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/17","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/18","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/19","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/20","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/21","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/22","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/23","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/24","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/25","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/26","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/27","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/28","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/29","npu":false,"npu_flags":["rq_half_even","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/30","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/31","npu":false,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/32","npu":true,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/33","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/34","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/35","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/36","npu":false,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/37","npu":true,"npu_flags":["rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/38","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/39","npu":false,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/40","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/41","npu":true,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/42","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/43","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/44","npu":false,"npu_flags":["rq_half_even","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/45","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/46","npu":false,"npu_flags":["rq_floor_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/47","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/48","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/49","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/50","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/51","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/52","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/53","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/54","npu":false,"npu_flags":["rq_half_even","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/55","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/56","npu":false,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/57","npu":false,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/58","npu":false,"npu_flags":["rq_half_even","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/59","npu":false,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/60","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/61","npu":true,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/62","npu":false,"npu_flags":[],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/63","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/64","npu":true,"npu_flags":["rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/65","npu":false,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/66","npu":false,"npu_flags":["rq_half_even","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/67","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/68","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/69","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/70","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/71","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/72","npu":false,"npu_flags":["rq_half_even","rq_post_bias"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/73","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/74","npu":true,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/75","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/76","npu":false,"npu_flags":[],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/77","npu":true,"npu_flags":["rq_floor_mult"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/78","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/79","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/80","npu":true,"npu_flags":["rq_half_even","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/81","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/82","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/83","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/random/84","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32","conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/85","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/86","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/87","npu":false,"npu_flags":[],"npu_reasons":["conv_groups"]} +{"file":"vectors/conv.jsonl","id":"conv/random/88","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/random/89","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/bound/90","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/bound/91","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/bound/92","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/bound/93","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/bound/94","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/bound/95","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/bound/96","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/bound/97","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/bound/98","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/bound/99","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/bound/100","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/bound/101","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/geometry/102","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/geometry/103","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/geometry/104","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/geometry/105","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/geometry/106","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/geometry/107","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/geometry/108","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/geometry/109","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/geometry/110","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/edge32/112","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/edge32/113","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/edge32/114","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/edge32/115","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/check/116","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/check/117","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/check/118","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/check/119","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/check/120","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/check/121","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv.jsonl","id":"conv/step8_q/123","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv.jsonl","id":"conv/acc32_param/124","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/0","npu":false,"npu_flags":["rq_floor_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/1","npu":false,"npu_flags":["rq_half_even","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/3","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/4","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/5","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/6","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/7","npu":true,"npu_flags":["rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/8","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/9","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/10","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/11","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/12","npu":false,"npu_flags":["rq_half_even","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/13","npu":true,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/14","npu":false,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/15","npu":true,"npu_flags":["rq_floor_mult"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/17","npu":false,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/18","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/19","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/20","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/21","npu":true,"npu_flags":["rq_half_even","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/22","npu":false,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/23","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/24","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/25","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/26","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/27","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/28","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/29","npu":false,"npu_flags":["rq_neg_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/30","npu":true,"npu_flags":["rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/31","npu":true,"npu_flags":["rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/32","npu":false,"npu_flags":["rq_half_even","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/33","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/34","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/35","npu":true,"npu_flags":["rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/36","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/37","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/38","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/39","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/40","npu":false,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/41","npu":false,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/42","npu":false,"npu_flags":["rq_half_even","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/43","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/44","npu":false,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/45","npu":false,"npu_flags":["rq_floor_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/46","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/47","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/48","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/49","npu":true,"npu_flags":["rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/50","npu":true,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/51","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/52","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/53","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/54","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/55","npu":false,"npu_flags":["rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/56","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/57","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/58","npu":true,"npu_flags":["rq_half_even","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/59","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/60","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/62","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/63","npu":true,"npu_flags":["rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/64","npu":true,"npu_flags":["rq_floor_mult"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/65","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/66","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/67","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/68","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/69","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/70","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/71","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/72","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/73","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/74","npu":false,"npu_flags":["rq_half_even","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/75","npu":true,"npu_flags":["rq_half_even","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/76","npu":true,"npu_flags":["rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/77","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/78","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/79","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/80","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/81","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/82","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/83","npu":true,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/84","npu":false,"npu_flags":["rq_half_even","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/85","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/86","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/87","npu":true,"npu_flags":["rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/88","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/random/89","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/bound/90","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/bound/91","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/bound/92","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/bound/93","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/bound/94","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/bound/95","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/bound/96","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/bound/97","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/bound/98","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/bound/99","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/bound/100","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/bound/101","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/edge32/102","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/edge32/103","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/edge32/104","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/edge32/105","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/geometry/106","npu":true,"npu_flags":["rq_neg_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/geometry/107","npu":true,"npu_flags":["rq_neg_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/geometry/108","npu":true,"npu_flags":["rq_neg_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/geometry/109","npu":true,"npu_flags":["rq_neg_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/geometry/110","npu":true,"npu_flags":["rq_neg_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/geometry/111","npu":true,"npu_flags":["rq_neg_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/geometry/112","npu":true,"npu_flags":["rq_neg_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/step8_q/114","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/conv_transpose.jsonl","id":"conv_transpose/acc32_param/115","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/crop.jsonl","id":"crop/random/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/1","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/2","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/3","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/4","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/6","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/7","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/8","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/9","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/10","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/crop.jsonl","id":"crop/random/11","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/12","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/13","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/15","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/18","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/crop.jsonl","id":"crop/random/19","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/20","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/21","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/22","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/23","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/24","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/25","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/crop.jsonl","id":"crop/random/26","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/27","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/random/28","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/crop.jsonl","id":"crop/random/29","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop.jsonl","id":"crop/scalar/30","npu":false,"npu_flags":[],"npu_reasons":["crop_scalar"]} +{"file":"vectors/crop.jsonl","id":"crop/scalar/31","npu":false,"npu_flags":[],"npu_reasons":["crop_scalar"]} +{"file":"vectors/crop.jsonl","id":"crop/scalar/32","npu":false,"npu_flags":[],"npu_reasons":["crop_scalar"]} +{"file":"vectors/crop.jsonl","id":"crop/scalar/33","npu":false,"npu_flags":[],"npu_reasons":["crop_scalar"]} +{"file":"vectors/crop.jsonl","id":"crop/scalar/34","npu":false,"npu_flags":[],"npu_reasons":["crop_scalar"]} +{"file":"vectors/crop.jsonl","id":"crop/scalar/35","npu":false,"npu_flags":[],"npu_reasons":["crop_scalar"]} +{"file":"vectors/crop.jsonl","id":"crop/scalar/36","npu":false,"npu_flags":[],"npu_reasons":["crop_scalar"]} +{"file":"vectors/crop.jsonl","id":"crop/scalar/37","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"vectors/crop.jsonl","id":"crop/scalar/38","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"vectors/crop.jsonl","id":"crop/scalar/39","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"vectors/crop.jsonl","id":"crop/scalar/40","npu":false,"npu_flags":[],"npu_reasons":["acc32","crop_scalar"]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/1","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/2","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/3","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/4","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/6","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/7","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/8","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/9","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/10","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/11","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/12","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/13","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/15","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/18","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/19","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/20","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/21","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/22","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/23","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/24","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/25","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/26","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/27","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/28","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/random/29","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/crop_to.jsonl","id":"crop_to/geometry/30","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/elu.jsonl","id":"elu/random/0","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/1","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/2","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/3","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/4","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/5","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/6","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/7","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/8","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/9","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/10","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/11","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/12","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/13","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/14","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/15","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/16","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/17","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/18","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/19","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/20","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/21","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/22","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/23","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/24","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/25","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/26","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/27","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/28","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/random/29","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/zero/30","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/zero/31","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/zero/32","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/zero/33","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/elu.jsonl","id":"elu/zero/34","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:elu"]} +{"file":"vectors/fill.jsonl","id":"fill/random/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/1","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/2","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/3","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/4","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/6","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/7","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/fill.jsonl","id":"fill/random/8","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/9","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/fill.jsonl","id":"fill/random/10","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/11","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/12","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/fill.jsonl","id":"fill/random/13","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/fill.jsonl","id":"fill/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/15","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/18","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/19","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/20","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/21","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/22","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/23","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/24","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/25","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/fill.jsonl","id":"fill/random/26","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/27","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/28","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/random/29","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/scalar/30","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/scalar/31","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/scalar/32","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/fill.jsonl","id":"fill/scalar/33","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/gram.jsonl","id":"gram/random/0","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/1","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/2","npu":false,"npu_flags":[],"npu_reasons":["op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/3","npu":false,"npu_flags":[],"npu_reasons":["op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/4","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/5","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/6","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/7","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/8","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/9","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/10","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/11","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/12","npu":false,"npu_flags":[],"npu_reasons":["op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/13","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/14","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/15","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/16","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/17","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/18","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/19","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/20","npu":false,"npu_flags":[],"npu_reasons":["op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/21","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/22","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/23","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/24","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/25","npu":false,"npu_flags":[],"npu_reasons":["op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/26","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/27","npu":false,"npu_flags":[],"npu_reasons":["op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/28","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/29","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/30","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/31","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/32","npu":false,"npu_flags":[],"npu_reasons":["op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/33","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/34","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/35","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/36","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/37","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/38","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32","op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/random/39","npu":false,"npu_flags":[],"npu_reasons":["op:gram"]} +{"file":"vectors/gram.jsonl","id":"gram/geometry/40","npu":false,"npu_flags":[],"npu_reasons":["op:gram"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/0","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/1","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/2","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/3","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/4","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/5","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/6","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/7","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/8","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/9","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/10","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/11","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/12","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/13","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/14","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/15","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/16","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/17","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/18","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/19","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/20","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/21","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/22","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/23","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/24","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/25","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/26","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/27","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/28","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/29","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/30","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/31","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/32","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/33","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/34","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/35","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/36","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/37","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/38","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/39","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/40","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/41","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/42","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/43","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/44","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/45","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/46","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/47","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/48","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/49","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/50","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/51","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/52","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/53","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/54","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/55","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/56","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/57","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/58","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/random/59","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/floor/60","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/floor/61","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/floor/62","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/edge/63","npu":false,"npu_flags":[],"npu_reasons":["op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/edge/64","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/edge/65","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/acc32_param/66","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/igdn.jsonl","id":"igdn/acc32_param/67","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:igdn"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/0","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/1","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/2","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/3","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/4","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/5","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/6","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/7","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/8","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/9","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/10","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/11","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/12","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/13","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/14","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/15","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/16","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/17","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/18","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/19","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/20","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/21","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/22","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/23","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/24","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/25","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/26","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/27","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/28","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/29","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/30","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/31","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/32","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/33","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/34","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/35","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/36","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/37","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/38","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/random/39","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/l2norm.jsonl","id":"l2norm/eps/41","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:l2norm"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/0","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/1","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/2","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/3","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/4","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/5","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/6","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/7","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/8","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/9","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/10","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/11","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/12","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/13","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/14","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/15","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/16","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/17","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/18","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/19","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/20","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/21","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/22","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/23","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/24","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/25","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/26","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/27","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/28","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/29","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/30","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/31","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/32","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/33","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/34","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/35","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/36","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/37","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/38","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/39","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/40","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/41","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/42","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/43","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/44","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/45","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/46","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/47","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/48","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/random/49","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/mean/50","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/mean/51","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/mean/52","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/layernorm_c.jsonl","id":"layernorm_c/mean/53","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:layernorm_c"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/0","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/1","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/2","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/3","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/4","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/6","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/7","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/8","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/9","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/10","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/11","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/12","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/13","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/15","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/16","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/18","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/19","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/20","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/21","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/22","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/23","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/24","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/25","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/26","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/27","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/28","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/29","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/30","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/31","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/32","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/33","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/34","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/35","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/36","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/37","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/38","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/39","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/40","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/41","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/42","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/43","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/44","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/45","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/46","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/47","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/48","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/leaky.jsonl","id":"leaky/random/49","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/floor/50","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/floor/51","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/floor/52","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/floor/53","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/leaky.jsonl","id":"leaky/acc32_param/55","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/lut16.jsonl","id":"lut16/table/0","npu":true,"npu_flags":["lut16"],"npu_reasons":[]} +{"file":"vectors/lut16.jsonl","id":"lut16/table/1","npu":true,"npu_flags":["lut16"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/0","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/1","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/2","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/3","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/4","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/5","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/6","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/7","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/8","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/9","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/10","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/11","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/12","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/13","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/14","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/15","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/16","npu":true,"npu_flags":["rq_half_even","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/17","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/18","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/19","npu":true,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/20","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/21","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/22","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/23","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/24","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/25","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/26","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/27","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/28","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/29","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/30","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/31","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/32","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/33","npu":true,"npu_flags":["rq_floor_mult"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/34","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/35","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/36","npu":true,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/37","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/38","npu":false,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/39","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/40","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/41","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/42","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/43","npu":false,"npu_flags":["rq_half_even","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/44","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/45","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/46","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/47","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/48","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/49","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/50","npu":true,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/51","npu":true,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/52","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/53","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/54","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/55","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/56","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/57","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/58","npu":true,"npu_flags":["rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/59","npu":true,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/60","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/61","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/62","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/63","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/64","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/65","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/random/66","npu":true,"npu_flags":["rq_half_even","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/67","npu":true,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/68","npu":true,"npu_flags":["rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/random/69","npu":true,"npu_flags":["rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/edge32/70","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/edge32/71","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/mul.jsonl","id":"mul/edge32/72","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/geometry/73","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/mul.jsonl","id":"mul/geometry/74","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/1","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/2","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/3","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/4","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/6","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/7","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/8","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/9","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/10","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/11","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/12","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/13","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/15","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/16","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/18","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/19","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/20","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/21","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/22","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/23","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/24","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/25","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/26","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/27","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/28","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/random/29","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/multiple/30","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/multiple/31","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/multiple/32","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pad_rep.jsonl","id":"pad_rep/multiple/33","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/1","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/2","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/3","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/4","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/6","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/7","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/8","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/9","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/10","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/11","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/12","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/13","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/15","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/18","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/19","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/20","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/21","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/22","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/23","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/24","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/25","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/26","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/27","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/28","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/29","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/30","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/31","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/32","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/33","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/34","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/35","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/36","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/37","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/38","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_shuffle.jsonl","id":"pixel_shuffle/random/39","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/1","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/3","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/4","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/6","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/7","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/8","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/9","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/10","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/11","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/13","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/15","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/19","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/20","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/22","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/24","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/25","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/26","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/27","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/28","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/29","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/30","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/31","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/32","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/34","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/35","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/36","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/37","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/38","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/random/39","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/pixel_unshuffle.jsonl","id":"pixel_unshuffle/geometry/40","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/1","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/2","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/3","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/relu.jsonl","id":"relu/random/4","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/relu.jsonl","id":"relu/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/6","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/7","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/8","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/9","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/10","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/11","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/12","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/13","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/15","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/17","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/relu.jsonl","id":"relu/random/18","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/19","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/20","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/21","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/22","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/23","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/24","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/25","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/26","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/relu.jsonl","id":"relu/random/27","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/28","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/relu.jsonl","id":"relu/random/29","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/30","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/31","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/32","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/relu.jsonl","id":"relu/random/33","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/34","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/35","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/36","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/37","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/38","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/relu.jsonl","id":"relu/random/39","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/relu.jsonl","id":"relu/edge/40","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/relu.jsonl","id":"relu/edge32/41","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/0","npu":true,"npu_flags":["rq_half_even","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/1","npu":true,"npu_flags":["rq_half_even","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/2","npu":false,"npu_flags":["rq_half_even"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/3","npu":true,"npu_flags":["rq_floor_mult"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/4","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/5","npu":true,"npu_flags":["rq_neg_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/6","npu":true,"npu_flags":["rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/7","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/8","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/9","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/10","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/11","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/12","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/13","npu":false,"npu_flags":["rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/14","npu":false,"npu_flags":["rq_half_even","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/15","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/16","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/18","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/19","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/20","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/21","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/22","npu":true,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/23","npu":false,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/24","npu":true,"npu_flags":["rq_floor_mult","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/25","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/26","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/27","npu":true,"npu_flags":["rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/28","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/29","npu":false,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/30","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/31","npu":true,"npu_flags":["rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/32","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/33","npu":true,"npu_flags":["rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/34","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/35","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/36","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/37","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/38","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/39","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/40","npu":true,"npu_flags":["rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/41","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/42","npu":false,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/43","npu":false,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/44","npu":true,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/45","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/46","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/47","npu":false,"npu_flags":["rq_half_even","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/48","npu":false,"npu_flags":["rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/49","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/50","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/51","npu":true,"npu_flags":["rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/52","npu":true,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/53","npu":false,"npu_flags":["rq_half_even","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/54","npu":true,"npu_flags":["rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/55","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/56","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/57","npu":false,"npu_flags":["rq_half_even","rq_neg_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/58","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/59","npu":true,"npu_flags":["rq_floor_mult","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/60","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/61","npu":false,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/62","npu":true,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/63","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/64","npu":false,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/65","npu":true,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/66","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/67","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/68","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/random/69","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/70","npu":true,"npu_flags":["rq_floor_mult"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/71","npu":true,"npu_flags":["rq_floor_mult"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/72","npu":true,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/73","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/74","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/75","npu":true,"npu_flags":["rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/76","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/77","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/78","npu":true,"npu_flags":["rq_half_even","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/79","npu":true,"npu_flags":["rq_floor_mult"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/80","npu":true,"npu_flags":["rq_floor_mult"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/81","npu":true,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/82","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/83","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/84","npu":true,"npu_flags":["rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/85","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/86","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/87","npu":true,"npu_flags":["rq_half_even","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/88","npu":true,"npu_flags":["rq_floor_mult"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/89","npu":true,"npu_flags":["rq_floor_mult"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/90","npu":true,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/91","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/92","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/93","npu":true,"npu_flags":["rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/94","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/95","npu":true,"npu_flags":["rq_half_even"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/96","npu":true,"npu_flags":["rq_half_even","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/97","npu":true,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/98","npu":true,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/99","npu":true,"npu_flags":["rq_floor_mult","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/100","npu":true,"npu_flags":["rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/101","npu":true,"npu_flags":["rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/102","npu":true,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/103","npu":true,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/104","npu":true,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/105","npu":true,"npu_flags":["rq_half_even","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/106","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/107","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/108","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/109","npu":true,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/110","npu":true,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/111","npu":true,"npu_flags":["rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/112","npu":true,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/113","npu":true,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/114","npu":true,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/115","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/116","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/117","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/118","npu":false,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/119","npu":false,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/120","npu":false,"npu_flags":["rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/121","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/122","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/123","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/124","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/125","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/126","npu":false,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/127","npu":false,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/128","npu":false,"npu_flags":["rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/129","npu":false,"npu_flags":["rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/130","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/131","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/132","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/133","npu":false,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/134","npu":false,"npu_flags":["rq_floor_mult","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/135","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/136","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/137","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/138","npu":false,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/139","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/140","npu":false,"npu_flags":["rq_half_even","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/141","npu":false,"npu_flags":["rq_half_even","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/edge32/142","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/edge32/143","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/edge32/144","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/edge32/145","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/146","npu":true,"npu_flags":["rq_floor_mult","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/147","npu":true,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/148","npu":true,"npu_flags":["rq_half_even","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/149","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/150","npu":true,"npu_flags":["rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/151","npu":true,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/152","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/153","npu":false,"npu_flags":["rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/154","npu":false,"npu_flags":["rq_half_even","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/155","npu":true,"npu_flags":["rq_floor_mult","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/156","npu":false,"npu_flags":["rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/157","npu":false,"npu_flags":["rq_half_even","rq_neg_mult","rq_post_bias","rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/158","npu":true,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/159","npu":true,"npu_flags":["rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/product32/160","npu":true,"npu_flags":["rq_half_even","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/rounding/161","npu":true,"npu_flags":["rq_half_even","rq_post_bias"],"npu_reasons":[]} +{"file":"vectors/rescale.jsonl","id":"rescale/step8_q/163","npu":false,"npu_flags":["rq_floor_mult","rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/acc32_param/164","npu":false,"npu_flags":["rq_floor_mult"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/acc32_param/165","npu":false,"npu_flags":["rq_shift_range"],"npu_reasons":["acc32"]} +{"file":"vectors/rescale.jsonl","id":"rescale/acc32_param/166","npu":false,"npu_flags":["rq_post_bias"],"npu_reasons":["acc32"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/0","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/1","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/2","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/3","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/4","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/5","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/6","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/7","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/8","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/9","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/10","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/11","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/12","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/13","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/14","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/15","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/16","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/17","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/18","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/19","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/20","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/21","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/22","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/23","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/24","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/25","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/26","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/27","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/28","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/random/29","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/sigmoid.jsonl","id":"sigmoid/edge/30","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:sigmoid"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/0","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/1","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/2","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/3","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/4","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/5","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/6","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/7","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/8","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/9","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/10","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/11","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/12","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/13","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/14","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/15","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/16","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/17","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/18","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/19","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/20","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/21","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/22","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/23","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/24","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/25","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/26","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/27","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/28","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/29","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/30","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/31","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/32","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/33","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/34","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/35","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/36","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/37","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/38","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/random/39","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/rows/40","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/rows/41","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/softmax.jsonl","id":"softmax/rows/42","npu":false,"npu_flags":[],"npu_reasons":["acc32","op:softmax"]} +{"file":"vectors/split.jsonl","id":"split/random/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/1","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/2","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/3","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/split.jsonl","id":"split/random/4","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/6","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/7","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/split.jsonl","id":"split/random/8","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/9","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/10","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/11","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/split.jsonl","id":"split/random/12","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/13","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/15","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/18","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/split.jsonl","id":"split/random/19","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/20","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/21","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/22","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/23","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/24","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/25","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/split.jsonl","id":"split/random/26","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/27","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/28","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/split.jsonl","id":"split/random/29","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/1","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/2","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/3","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/4","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/6","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/7","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/8","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/9","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/10","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/11","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/12","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/13","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/15","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/18","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/19","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/20","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/21","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/22","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/23","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/24","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/25","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/26","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/27","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/28","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/random/29","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_merge.jsonl","id":"subpos_merge/geometry/30","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/0","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/1","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/2","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/3","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/4","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/5","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/6","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/7","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/8","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/9","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/10","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/11","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/12","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/13","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/14","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/15","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/16","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/17","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/18","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/19","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/20","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/21","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/22","npu":false,"npu_flags":[],"npu_reasons":["acc32"]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/23","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/24","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/25","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/26","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/27","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/28","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/random/29","npu":true,"npu_flags":[],"npu_reasons":[]} +{"file":"vectors/subpos_split.jsonl","id":"subpos_split/geometry/30","npu":true,"npu_flags":[],"npu_reasons":[]} diff --git a/wai/tools/wai_ops_check.py b/wai/tools/wai_ops_check.py index 463f6229b..bcc0cafea 100644 --- a/wai/tools/wai_ops_check.py +++ b/wai/tools/wai_ops_check.py @@ -10,7 +10,8 @@ extension's text (and, for the source forms it lowers, integer-payloads), it: and each decode's `expect` (the run of section 4, every graph operator of section 5), and each stage vector's `expect` (section 5E); it writes each file again canonically (sections 8.5 and 14.1) and requires the bytes of the - committed file; + committed file; and writes npu.jsonl (section 14.5), each graph vector's place in + wai-ops/1-npu from its own verdict, and requires the committed bytes; - re-lowers every parameter set certificates/index.json lists, by the canonical lowerings of section 13, and requires each lowered graph's bytes, each set's digest (SPEC 3.1), and that every lowered graph loads; and requires the graph @@ -1509,16 +1510,20 @@ def tensor_out(t): return {"data": list(t.d), "shape": [t.c, t.h, t.w]} -def graph_line(v): +def graph_line(v, npu=None): """A graph vector, recomputed: `load` (steps 1-8), `profiles` (section 14.1: both when the load is refused, else exactly the profiles the verdict meets) and every - decode's `expect`.""" + decode's `expect`. When the graph loads and `npu` is a list, its line of npu.jsonl + (section 14.5) is appended to it, less the file.""" b = bytes.fromhex(v["graph"]) out = {"graph": b.hex(), "id": v["id"], "decodes": []} try: g = load(b) out["load"] = "ok" out["profiles"] = [p for p in ("acc32", "acc64") if g["verdict"][p]] + if npu is not None: + vd = g["verdict"] + npu.append({"id": v["id"], "npu": vd["npu"], "npu_flags": vd["npu_flags"], "npu_reasons": vd["npu_reasons"]}) except Refuse as e: out["load"] = e.code out["profiles"] = ["acc32", "acc64"] @@ -2962,6 +2967,7 @@ def check(root, write, external=None): wai = os.path.dirname(os.path.abspath(root)) failures = 0 nfiles = nvec = 0 + npu = [] for sub, fn in (("vectors", graph_line), ("stage", stage_line), ("graphs", graph_line)): d = os.path.join(root, sub) for name in sorted(os.listdir(d)): @@ -2969,13 +2975,20 @@ def check(root, write, external=None): continue raw = open(os.path.join(d, name), "rb").read() out = [] + entries = [] try: for line in raw.decode("ascii").split("\n")[:-1]: - out.append(cw(fn(json.loads(line))) + "\n") + if fn is graph_line: + out.append(cw(graph_line(json.loads(line), entries)) + "\n") + else: + out.append(cw(fn(json.loads(line))) + "\n") except Exception as e: failures += 1 print("FAIL %s/%s: %s: %s" % (sub, name, type(e).__name__, e)) continue + for e in entries: + e["file"] = "%s/%s" % (sub, name) + npu += entries got = "".join(out).encode() nfiles += 1 nvec += len(out) @@ -2988,6 +3001,20 @@ def check(root, write, external=None): k = next((k for k in range(min(len(a), len(b))) if a[k] != b[k]), min(len(a), len(b))) print("DIFF %s/%s: line %d differs" % (sub, name, k + 1)) print("rewrote %d vector files, %d vectors" % (nfiles, nvec)) + # npu.jsonl (section 14.5): each graph vector that loads, by its file's path in byte + # order, then in file order, with its graph's place in wai-ops/1-npu (section 9.2). + npu.sort(key=lambda e: e["file"].encode()) + got = "".join(cw(e) + "\n" for e in npu).encode() + if write: + open(os.path.join(write, "npu.jsonl"), "wb").write(got) + try: + raw = open(os.path.join(root, "npu.jsonl"), "rb").read() + except OSError: + raw = None + if got != raw: + failures += 1 + print("DIFF npu.jsonl: %s" % ("missing" if raw is None else "the subset index differs")) + print("rewrote npu.jsonl: %d graph vectors, %d of them in wai-ops/1-npu" % (len(npu), sum(1 for e in npu if e["npu"]))) nsets = ngraphs = ncerts = 0 lowered = {} # The sets index.json lists, then the falsification sets of section 14.3. Each is diff --git a/wai/wai-ops/src/generate/mod.rs b/wai/wai-ops/src/generate/mod.rs index a608f8cf8..a6b1ae7e6 100644 --- a/wai/wai-ops/src/generate/mod.rs +++ b/wai/wai-ops/src/generate/mod.rs @@ -105,5 +105,35 @@ pub fn all(read: &dyn Fn(&str) -> Option>) -> Result<(Vec<(String, Vec)]) -> Result, String> { + use crate::json::{self, C, Value}; + let mut vf: Vec<&(String, Vec)> = files.iter().filter(|(p, _)| (p.starts_with("vectors/") || p.starts_with("graphs/")) && p.ends_with(".jsonl")).collect(); + vf.sort_by(|a, b| a.0.as_bytes().cmp(b.0.as_bytes())); + let mut out = String::new(); + for (path, text) in vf { + for line in text.split(|&b| b == b'\n').filter(|l| !l.is_empty()) { + let v = json::parse(line).map_err(|_| format!("{path}: a line that does not parse"))?; + let id = v.get("id").and_then(Value::as_str).ok_or_else(|| format!("{path}: a vector with no id"))?; + let g = v.get("graph").and_then(Value::as_str).and_then(json::unhex).ok_or_else(|| format!("{path} {id}: no graph"))?; + if crate::graph::load(&g, crate::Profile::Acc64).is_err() { + continue; + } + let cert = crate::bounds::certify(None, "whg1", None, &[("g", &g)]).map_err(|u| format!("{path} {id}: {:?}", u.refusal))?; + let vd = cert.verdict(); + let strs = |v: &[String]| C::Arr(v.iter().map(|s| C::s(s)).collect()); + let c = C::obj(alloc::vec![("file", C::s(path)), ("id", C::s(id)), ("npu", C::Bool(vd.npu())), ("npu_flags", strs(&vd.npu_flags)), ("npu_reasons", strs(&vd.npu_reasons))]); + out.push_str(&json::to_string(&c)); + out.push('\n'); + } + } + Ok(out.into_bytes()) +} -- 2.54.0 (Apple Git-157) From d7425f89a20b113cd1911545364616ca3195f08f Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:12:32 -0400 Subject: [PATCH 003/242] wai-ops-npu: the bench, CI, and the corpus held to npu.jsonl - ops-npu-bench: time and energy of the corpus and of an int_synth graph on the device and on the CPU, every device result compared with the interpreter's first; the package's energy counter (a vendor counter) and the device runtime's own estimate (a model), each named as what it is. - wai-ops-npu.yml: on the linux-amd runner, the probes, the corpus, the twelve kernel edits and the width test on the device, with the device declared and required; on linux-arm64, the crate builds and an absent device is SKIPPED without WAI_OPS_NPU_REQUIRE and a failure with it. - The corpus test requires the vectors it runs to be exactly those npu.jsonl lists in the subset. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/wai-ops-npu.yml | 182 +++++++++++++++++++++++ wai/wai-ops-npu/Cargo.toml | 5 + wai/wai-ops-npu/src/bin/bench.rs | 236 ++++++++++++++++++++++++++++++ wai/wai-ops-npu/tests/corpus.rs | 12 ++ 4 files changed, 435 insertions(+) create mode 100644 .github/workflows/wai-ops-npu.yml create mode 100644 wai/wai-ops-npu/src/bin/bench.rs diff --git a/.github/workflows/wai-ops-npu.yml b/.github/workflows/wai-ops-npu.yml new file mode 100644 index 000000000..1185b69fa --- /dev/null +++ b/.github/workflows/wai-ops-npu.yml @@ -0,0 +1,182 @@ +name: wai-ops-npu integer operators on neural processors + +# wai-ops/1-npu on neural processors (wai/wai-ops-npu): the integer operator set's +# accelerator subset (extensions/integer-operators.md §9.2, §9.5) with its arithmetic +# on one compute core of a tile array. The crate builds the core's program with the +# array's open toolchain (Apache-2.0 with the LLVM exception, pinned below) and its +# host side with the system's C++ compiler against the device runtime, both at run +# time, into a cache keyed by their sources and the toolchain. +# +# - npu-hw (`linux-amd`, framework-128gb, its neural processor): the probes (the +# unit's rounding modes at every shift, its saturation, its accumulator lanes, +# and the kernel's requantisation on 786,432 cases); every graph vector of +# wai/ops-conformance whose graph is in the subset, against the committed results +# and the interpreter; the twelve kernel edits, each its own program, every one of +# which the corpus must catch; and the graphs the certificate keeps out of the +# subset by acc32 alone, which must not all agree. WAI_OPS_NPU_EXPECT declares the +# one device the host must have, and WAI_OPS_NPU_REQUIRE makes an absent device a +# failure, never a skip; the step then requires the report lines with their +# counts, so a filtered or empty run fails too. +# - checks (`linux-arm64`, no such device): the crate and its tests build, its unit +# tests pass, the device tests print SKIPPED and pass, and the same tests under +# WAI_OPS_NPU_REQUIRE=1 fail: an absent device is not a pass. +# +# Time and energy (`ops-npu-bench`) are measured by hand and recorded in +# integer-operators §9.5; this workflow does not run the bench. + +# One run per branch at a time; a push supersedes the run before it. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + pull_request: + paths: + - 'wai/wai-ops-npu/**' + - 'wai/wai-ops/**' + - 'wai/ops-conformance/**' + - 'rust-toolchain.toml' + - '.github/workflows/wai-ops-npu.yml' + schedule: + - cron: '41 7 * * *' + +env: + # The tile array's toolchain: the compiler and its core back end, the build the + # compiler's release pins (its utils/peano-requirements.txt). The same environment + # as the wai-quantum npu-cores member's, at the same path. + TOOLCHAIN_VERSION: 1.4.3 + CORE_COMPILER: llvm-aie==22.0.0.2026090701+3e93bf7b + +jobs: + # Does this pull request's own diff touch the paths below? This forge matches a + # workflow's `paths:` against the push, so a merge of main into a branch fires the + # workflow whatever the pull request changes; every job here waits on this answer. + # A push to main, a schedule, or anything the script cannot determine answers + # `true` (scripts/pr-touches-paths.py). + relevant: + name: does this change touch it + runs-on: linux-arm64 + outputs: + run: ${{ steps.gate.outputs.run }} + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - id: gate + run: | + set -euo pipefail + r=$(python3 scripts/pr-touches-paths.py .github/workflows/wai-ops-npu.yml \ + "${{ github.event.pull_request.base.sha }}" \ + "${{ github.event.pull_request.head.sha }}") + echo "this change touches wai-ops-npu: $r" + echo "run=$r" >> "$GITHUB_OUTPUT" + + npu-hw: + name: neural processor (framework-128gb silicon) + needs: relevant + if: needs.relevant.outputs.run == 'true' + runs-on: linux-amd + timeout-minutes: 90 + env: + CARGO_PROFILE_DEV_DEBUG: '0' + CARGO_BUILD_JOBS: '12' + steps: + - name: Fetch the source + shell: bash + run: | + set -euo pipefail + want="${{ github.event.pull_request.head.sha || github.sha }}" + rm -rf src && mkdir src && cd src + git init -q + # Fetch the commit itself, not the ref. The host names its own route to the + # forge (WAI_FORGE_URL). + git fetch -q --depth 1 "${WAI_FORGE_URL:-http://100.64.224.0:3400}/Transaction-Science/open-standards.git" "$want" + git checkout -q FETCH_HEAD + got="$(git rev-parse HEAD)" + [ "$got" = "$want" ] || { echo "::error::fetched $got, not $want"; exit 1; } + - name: The toolchain (cached) + shell: bash + run: | + set -euo pipefail + venv="$HOME/aie-$TOOLCHAIN_VERSION" + if ! "$venv/bin/python" -I -c 'import aie.iron' 2>/dev/null; then + rm -rf "$venv" + python3 -m venv "$venv" + tag="$(python3 -c 'import sys; print(f"cp{sys.version_info.major}{sys.version_info.minor}")')" + "$venv/bin/pip" install -q "https://github.com/Xilinx/mlir-aie/releases/download/v$TOOLCHAIN_VERSION/mlir_aie-$TOOLCHAIN_VERSION-$tag-$tag-manylinux_2_35_x86_64.whl" # allow-name: the toolchain's release URL + "$venv/bin/pip" install -q -f https://github.com/Xilinx/llvm-aie/releases/expanded_assets/nightly "$CORE_COMPILER" # allow-name: the core compiler's release index + "$venv/bin/pip" install -q "numpy>=2.5.2,<3.0" rich ml_dtypes cloudpickle aiofiles + "$venv/bin/pip" install -q -f https://llvm.github.io/eudsl "eudsl-python-extras==0.1.0.20260801.905+68a0d7a" --config-settings=EUDSL_PYTHON_EXTRAS_HOST_PACKAGE_PREFIX=aie + fi + echo "WAI_OPS_NPU_PYTHON=$venv/bin/python" >> "$GITHUB_ENV" + - name: The probes, the corpus, the kernel edits and the width, on the device + shell: bash + working-directory: src/wai/wai-ops-npu + run: | + set -euo pipefail + export PATH="$HOME/.cargo/bin:$PATH" + export WAI_OPS_NPU_CACHE="$HOME/ops-npu-cache" + # The device this host has, declared here (or by the runner). + k="${WAI_OPS_NPU_HOST_EXPECT:-npu-1022-17f0}" + log="$RUNNER_TEMP/ops-npu.log" + export WAI_OPS_NPU_REPORT="$RUNNER_TEMP/ops-npu.report" + : > "$WAI_OPS_NPU_REPORT" + WAI_OPS_NPU_EXPECT="$k" WAI_OPS_NPU_REQUIRE=1 CARGO_TARGET_DIR="$HOME/hw-target/wai-ops-npu" \ + nice -n 10 cargo test --locked --release -- --nocapture --test-threads=1 2>&1 | tee "$log" + # Fourteen tests in five binaries: the library's eight, corpus's one, probe's + # three, mutants' one and width's one, none filtered, ignored or failed. + ran=$(awk '/^test result: ok\. [0-9]+ passed; 0 failed; 0 ignored; 0 measured; 0 filtered out/ { n += $4 } END { print n + 0 }' "$log") + [ "$ran" = 14 ] || { echo "::error::$ran tests ran and passed, not 14"; exit 1; } + ! grep -q '^test result: FAILED' "$log" || { echo "::error::a test failed"; exit 1; } + rep="$WAI_OPS_NPU_REPORT" + grep -qxF "ops-npu devices: as declared ($k)" "$rep" || { echo "::error::the device found is not $k"; exit 1; } + grep -qE "^ops-npu requantisation: key=$k [0-9]+ cases over shifts 0\.\.63 .*, 0 differ$" "$rep" || { echo "::error::the requantisation probe did not agree"; exit 1; } + grep -qE "^ops-npu corpus: key=$k .* vectors=[1-9][0-9]* decodes=[1-9][0-9]* .* declined=0 mismatches=0 errors=0 " "$rep" \ + || { echo "::error::no equal corpus run on $k"; exit 1; } + grep -qxF "ops-npu mutants: key=$k killed 12 of 12" "$rep" || { echo "::error::a kernel edit survived on $k, or the mutant test did not run"; exit 1; } + grep -qE "^ops-npu width: key=$k .* on the device [1-9][0-9]* differ " "$rep" || { echo "::error::the width test did not run"; exit 1; } + cat "$rep" + + checks: + name: builds · no device is not a pass (linux-arm64) + needs: relevant + if: needs.relevant.outputs.run == 'true' + runs-on: linux-arm64 + timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + - name: Install Rust 1.98.0 + shell: bash + run: | + export PATH="$HOME/.cargo/bin:$PATH" + command -v rustup >/dev/null 2>&1 || \ + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0 + rustup toolchain install 1.98.0 --profile minimal + rustup default 1.98.0 + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + # This host has no such device. The device tests must say so and pass; under + # WAI_OPS_NPU_REQUIRE=1 the same tests must fail. + - name: The crate's tests, and an absent device under WAI_OPS_NPU_REQUIRE + shell: bash + working-directory: wai/wai-ops-npu + env: + WAI_OPS_NPU_EXPECT: none + run: | + set -euo pipefail + log="$RUNNER_TEMP/ops-npu.log" + rep="$RUNNER_TEMP/ops-npu.report" + : > "$rep" + WAI_OPS_NPU_REPORT="$rep" cargo test --locked --release -- --nocapture --test-threads=1 2>&1 | tee "$log" + ran=$(awk '/^test result: ok\. [0-9]+ passed; 0 failed; 0 ignored; 0 measured; 0 filtered out/ { n += $4 } END { print n + 0 }' "$log") + [ "$ran" = 14 ] || { echo "::error::$ran tests ran and passed, not 14"; exit 1; } + grep -qxF 'ops-npu devices: as declared (none)' "$rep" || { echo "::error::the absent case was not established"; exit 1; } + for t in corpus mutants width 'requantisation probe' 'rounding probe' 'lane probe'; do + grep -qF "ops-npu $t: SKIPPED" "$rep" || { echo "::error::the $t did not say SKIPPED"; exit 1; } + done + grep -qE '^ops-npu subset: vectors=[1-9][0-9]* .* in_subset=[1-9][0-9]* ' "$rep" || { echo "::error::the corpus was not read"; exit 1; } + if WAI_OPS_NPU_REQUIRE=1 cargo test --locked --release --test corpus --test probe --test mutants --test width > "$RUNNER_TEMP/require.log" 2>&1; then + echo "::error::with no device, the device tests passed under WAI_OPS_NPU_REQUIRE=1"; exit 1 + fi + grep -q 'WAI_OPS_NPU_REQUIRE is set' "$RUNNER_TEMP/require.log" \ + || { echo "::error::the tests failed under WAI_OPS_NPU_REQUIRE=1, but not for the absent device"; cat "$RUNNER_TEMP/require.log"; exit 1; } + echo "no device: SKIPPED without WAI_OPS_NPU_REQUIRE, a failure with it" diff --git a/wai/wai-ops-npu/Cargo.toml b/wai/wai-ops-npu/Cargo.toml index 659e77b39..458cdcb11 100644 --- a/wai/wai-ops-npu/Cargo.toml +++ b/wai/wai-ops-npu/Cargo.toml @@ -25,5 +25,10 @@ path = "src/lib.rs" [dependencies] wai-ops = { path = "../wai-ops" } +# Time and energy of graphs on the device and on the CPU (src/bin/bench.rs). +[[bin]] +name = "ops-npu-bench" +path = "src/bin/bench.rs" + [profile.release] debug = false diff --git a/wai/wai-ops-npu/src/bin/bench.rs b/wai/wai-ops-npu/src/bin/bench.rs new file mode 100644 index 000000000..39847437c --- /dev/null +++ b/wai/wai-ops-npu/src/bin/bench.rs @@ -0,0 +1,236 @@ +//! `ops-npu-bench`: time, and energy where the host exposes it, of graphs run with their +//! arithmetic on the neural processor and on the CPU interpreter, every result compared +//! with the interpreter's first. +//! +//! ops-npu-bench [--secs S] [--rounds R] +//! +//! Workloads: +//! - `corpus`: one pass over every decode of every `ops-conformance/` graph vector in +//! `wai-ops/1-npu`; tiny tensors, so this measures dispatch more than arithmetic; +//! - `synth`: an `int_synth` graph (§13.1) of three transposed convolutions on a +//! `[8, 32, 32]` latent to `[3, 64, 64]`, with seeded weights, which the certificate +//! admits to the subset. +//! +//! For each round it reads three windows of `S` seconds: idle, the CPU looping the +//! workload, and the device looping it. The energy each window reads, by source and +//! class: +//! - the package's energy counter (`/sys/class/powercap/intel-rapl:0`, `package-0`): a +//! vendor counter read while it counts, `HwShunt` in the family's classes; it covers +//! the whole package and everything running on it, other users' work included; +//! - the device's own power figure, sampled from its runtime's report ("Estimated +//! Power"), integrated over the window: the runtime calls it an estimate, so +//! `ModelBased`. + +use std::path::PathBuf; +use std::process::Command; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +use wai_ops::json::{Value, unhex}; +use wai_ops::vectors::read_tensor; +use wai_ops::{Loaded, Profile, Sequence, Tensor}; +use wai_ops_npu::{Npu, Plan, discover, plan}; + +struct Work { + name: &'static str, + graphs: Vec<(Loaded, Plan, Vec<(Vec, Vec)>)>, +} + +fn corpus() -> Work { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../ops-conformance"); + let mut graphs = Vec::new(); + for dir in ["vectors", "graphs"] { + let mut names: Vec<_> = std::fs::read_dir(root.join(dir)).unwrap().filter_map(|e| e.ok().map(|e| e.path())).filter(|p| p.extension().is_some_and(|x| x == "jsonl")).collect(); + names.sort(); + for p in names { + for line in std::fs::read_to_string(&p).unwrap().split_terminator('\n') { + let v = wai_ops::json::parse(line.as_bytes()).unwrap(); + let g = unhex(v.get("graph").and_then(Value::as_str).unwrap()).unwrap(); + let Ok(l) = wai_ops::graph::load(&g, Profile::Acc64) else { continue }; + let pl = plan(&l).unwrap(); + if !pl.in_subset() { + continue; + } + let decodes = v + .get("decodes") + .and_then(Value::as_arr) + .unwrap() + .iter() + .map(|d| { + let ins = d.get("inputs").and_then(Value::as_arr).unwrap().iter().map(|t| read_tensor(t).unwrap()).collect(); + let sc = d.get("scalars").and_then(Value::as_arr).unwrap().iter().map(|x| x.as_dec_i64().unwrap()).collect(); + (ins, sc) + }) + .collect(); + graphs.push((l, pl, decodes)); + } + } + } + Work { name: "corpus", graphs } +} + +fn synth() -> Work { + let mut s = 0x7379_6e74_6831u64; + let mut next = move || { + s = s.wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut z = s; + 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 layer = |i: u32, o: u32, k: u32, stride: u32, pad: u32, out_pad: u32, shift: u32, relu: bool| { + let w: Vec = (0..i * o * k * k).map(|_| ((next() % 15) as i64 - 7).to_string()).collect(); + let b: Vec = (0..o).map(|_| ((next() % 64) as i64 - 32).to_string()).collect(); + format!("{{\"in_ch\":{i},\"out_ch\":{o},\"k\":{k},\"stride\":{stride},\"pad\":{pad},\"out_pad\":{out_pad},\"shift\":{shift},\"relu\":{relu},\"weight\":[{}],\"bias\":[{}]}}", w.join(","), b.join(",")) + }; + let j = format!("{{\"layers\":[{},{},{}]}}", layer(8, 16, 4, 2, 1, 0, 6, true), layer(16, 16, 3, 1, 1, 0, 6, true), layer(16, 3, 3, 1, 1, 0, 4, false)); + let set = wai_ops::lower::int_synth(j.as_bytes()).expect("the synthesis lowers"); + let l = wai_ops::graph::load(&set.get("synth").unwrap().to_bytes(), Profile::Acc64).expect("it loads"); + let pl = plan(&l).unwrap(); + assert!(pl.in_subset(), "the synthesis graph is outside the subset: {:?}", pl.reasons); + let x: Vec = (0..8 * 32 * 32).map(|_| (next() % 256) as i64 - 128).collect(); + Work { name: "synth", graphs: vec![(l, pl, vec![(vec![Tensor::new(8, 32, 32, x)], vec![])])] } +} + +/// One pass of the workload; on the device when `npu` is given. +fn pass(w: &Work, npu: Option<&Npu>) -> Vec, wai_ops::Refusal>> { + let mut out = Vec::new(); + for (l, pl, ds) in &w.graphs { + let mut seq = Sequence::new(); + for (ins, sc) in ds { + out.push(match npu { + Some(n) => { + let (r, st) = wai_ops_npu::run(n, l, pl, ins, sc, &mut seq); + assert!(st.errors.is_empty() && st.declined.is_empty(), "{:?} {:?}", st.errors.first(), st.declined.first()); + r + } + None => wai_ops::interp::run(l, ins, sc, &mut seq), + }); + } + } + out +} + +fn rapl() -> Option<(u64, u64)> { + let p = "/sys/class/powercap/intel-rapl:0"; + let e = std::fs::read_to_string(format!("{p}/energy_uj")).ok()?.trim().parse().ok()?; + let m = std::fs::read_to_string(format!("{p}/max_energy_range_uj")).ok()?.trim().parse().ok()?; + Some((e, m)) +} + +fn device_power() -> Option { + let out = Command::new("xrt-smi").args(["examine", "-r", "platform"]).output().ok()?; + let s = String::from_utf8_lossy(&out.stdout); + let l = s.lines().find(|l| l.contains("Estimated Power"))?; + l.split(':').nth(1)?.split_whitespace().next()?.parse().ok() +} + +/// Reads one window: runs `f` repeatedly for `secs` (or sleeps when it is `None`), and +/// returns the passes, the seconds, the package's joules and the device's estimated +/// joules. +fn window(secs: f64, mut f: Option<&mut dyn FnMut()>) -> (u32, f64, Option, Option) { + let stop = Arc::new(AtomicBool::new(false)); + let samples = Arc::new(Mutex::new(Vec::<(f64, f64)>::new())); + let t0 = Instant::now(); + let sampler = { + let (stop, samples) = (stop.clone(), samples.clone()); + std::thread::spawn(move || { + while !stop.load(Ordering::Relaxed) { + if let Some(w) = device_power() { + samples.lock().unwrap().push((t0.elapsed().as_secs_f64(), w)); + } + std::thread::sleep(Duration::from_millis(250)); + } + }) + }; + let r0 = rapl(); + let start = Instant::now(); + let mut passes = 0; + while start.elapsed().as_secs_f64() < secs { + match f.as_mut() { + Some(f) => { + f(); + passes += 1; + } + None => std::thread::sleep(Duration::from_millis(50)), + } + } + let wall = start.elapsed().as_secs_f64(); + let r1 = rapl(); + stop.store(true, Ordering::Relaxed); + sampler.join().unwrap(); + let pkg = match (r0, r1) { + (Some((a, m)), Some((b, _))) => Some(if b >= a { b - a } else { b + m - a } as f64 / 1e6), + _ => None, + }; + let s = samples.lock().unwrap(); + let dev = (!s.is_empty()).then(|| s.iter().map(|x| x.1).sum::() / s.len() as f64 * wall); + (passes, wall, pkg, dev) +} + +fn main() { + let mut secs = 3.0f64; + let mut rounds = 3u32; + let a: Vec = std::env::args().collect(); + let mut k = 1; + while k < a.len() { + match a[k].as_str() { + "--secs" => { + secs = a[k + 1].parse().unwrap(); + k += 1; + } + "--rounds" => { + rounds = a[k + 1].parse().unwrap(); + k += 1; + } + other => panic!("unknown argument {other}"), + } + k += 1; + } + let d = discover(); + let Some(npu) = d.found.into_iter().next() else { + eprintln!("no device: {:?} {:?}", d.unavailable, d.refused.iter().map(|r| r.error.to_string()).collect::>()); + std::process::exit(2); + }; + println!("ops-npu-bench: {} {}", npu.key(), npu.identity().describe()); + let j = |v: Option| v.map_or("none".to_string(), |x| format!("{x:.2} J")); + for w in [corpus(), synth()] { + // Every result the device gives is the interpreter's before anything is timed. + let (a, b) = (pass(&w, None), pass(&w, Some(&npu))); + assert_eq!(a, b, "{}: the device's results are not the interpreter's", w.name); + let decodes = a.len(); + let ops: usize = w.graphs.iter().map(|g| g.0.graph().ops.len() * g.2.len()).sum(); + println!("workload {}: {} graphs, {decodes} decodes, {ops} operator runs per pass; device results equal the interpreter's", w.name, w.graphs.len()); + for r in 0..rounds { + let idle = window(secs, None); + let mut fc = || { + std::hint::black_box(pass(&w, None)); + }; + let cpu = window(secs, Some(&mut fc)); + let d0 = npu.dispatches(); + let mut fn_ = || { + std::hint::black_box(pass(&w, Some(&npu))); + }; + let dev = window(secs, Some(&mut fn_)); + let disp = npu.dispatches() - d0; + println!( + " round {r}: idle {:.1} s: package {}, device estimate {}; cpu {} passes in {:.2} s ({:.3} ms/pass): package {}, device estimate {}; npu {} passes in {:.2} s ({:.3} ms/pass, {} dispatches): package {}, device estimate {}", + idle.1, + j(idle.2), + j(idle.3), + cpu.0, + cpu.1, + cpu.1 * 1e3 / cpu.0.max(1) as f64, + j(cpu.2), + j(cpu.3), + dev.0, + dev.1, + dev.1 * 1e3 / dev.0.max(1) as f64, + disp, + j(dev.2), + j(dev.3) + ); + } + } +} diff --git a/wai/wai-ops-npu/tests/corpus.rs b/wai/wai-ops-npu/tests/corpus.rs index dd7995379..46939a3c6 100644 --- a/wai/wai-ops-npu/tests/corpus.rs +++ b/wai/wai-ops-npu/tests/corpus.rs @@ -44,6 +44,18 @@ fn every_vector_in_the_subset_is_the_interpreters_on_the_device() { flags )); assert!(inside.len() > 500, "too few vectors in the subset: {}", inside.len()); + // The vectors run here are exactly those npu.jsonl lists in the subset (§14.5). + let listed: std::collections::BTreeSet<(String, String)> = std::fs::read_to_string(corpus::conformance().join("npu.jsonl")) + .expect("ops-conformance/npu.jsonl") + .split_terminator('\n') + .filter_map(|l| { + let v = wai_ops::json::parse(l.as_bytes()).expect("a canonical line"); + let on = v.get("npu").and_then(wai_ops::json::Value::as_bool).expect("npu"); + on.then(|| (v.get("file").and_then(wai_ops::json::Value::as_str).unwrap().to_string(), v.get("id").and_then(wai_ops::json::Value::as_str).unwrap().to_string())) + }) + .collect(); + let ran: std::collections::BTreeSet<(String, String)> = inside.iter().map(|r| (r.v.file.clone(), r.v.id.clone())).collect(); + assert_eq!(ran, listed, "the vectors in the subset are not the ones npu.jsonl lists"); let Some(npu) = corpus::device("corpus") else { return }; let t0 = std::time::Instant::now(); let t = corpus::run_all(&npu, &inside, 20); -- 2.54.0 (Apple Git-157) From de9a6a10d28e716e793625a52daefa5270100510 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:17:34 -0400 Subject: [PATCH 004/242] =?UTF-8?q?integer-operators=20=C2=A79.5=20and=20?= =?UTF-8?q?=C2=A714.5:=20wai-ops/1-npu=20on=20a=20neural=20processor,=20me?= =?UTF-8?q?asured?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - §9.5: how wai/wai-ops-npu runs the subset; the unit's arithmetic as measured (which of its rounding modes is which rounding of §2 and at which shifts, its shifts of 60 to 63, its saturation and its accumulator lanes); the results by neural processor; time and energy with each reading's class; and, per operator, what the Web Neural Network API's Candidate Recommendation Draft of 21 October 2026 allows exactly, and why not through its convolution, matrix and quantize operators. - §14.5: npu.jsonl, the subset's vectors. - determinism-tiers §5 and §6: the device run; no decoder runs its graphs on a neural processor yet, so the tier is not claimed for a decode there. - SPEC, CHANGELOG, the corpus README with the command that runs it by hand. Co-Authored-By: Claude Opus 5.5 --- wai/CHANGELOG.md | 15 +++++ wai/SPEC.md | 3 +- wai/extensions/determinism-tiers.md | 19 ++++-- wai/extensions/integer-operators.md | 73 ++++++++++++++++++++- wai/ops-conformance/README.md | 29 +++++++- wai/wai-verify/src/bin/wai-verify/corpus.rs | 3 +- 6 files changed, 132 insertions(+), 10 deletions(-) diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index 39e1210ff..96431f58b 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -6,6 +6,21 @@ wire format may change and such changes are recorded here. ## v0.4.0 — current +### `wai-ops/1-npu` on a neural processor, and where its arithmetic is not exact + +- **`wai/wai-ops-npu`**, an operator implementation of `wai-ops/1-npu` (integer-operators §9.2) for neural processors: a kernel for one compute core of a tile array, built at run time with the array's open toolchain, with a host side built against the device runtime. The interpreter runs each graph and asks the core for every operator that computes values: + - `conv` and `conv_transpose` as sums over taps, into 32-bit accumulator lanes for operands that fit 8 or 16 bits and 64-bit lanes for 32-bit operands, the width the certificate's `mac` record allows; + - `rescale`, `chscale` and `mul` through the vector unit's multiply and shift-round step, under the mode that is the operator's rounding; + - the elementwise operators, and `lut16` a table block at a time. + + The plan, the checks, the range policies, the movement operators and the state stay the interpreter's, so the device adds no refusal. +- **Measured: where the unit is not exact.** The unit's shift-round step is `R_floor`, `R_half_up` and `R_half_even` (§2) under three of its modes, at shifts 0 to 59 only. At 60 to 63 every mode shifts left by `64 − s`. 36 vectors of the subset have such a shift, and the kernel forms those in the core's scalar unit. A 32-bit accumulator lane keeps the low 32 bits of a sum past i32. 110 of the 458 graphs that fail the subset by `acc32` alone gave other results on the device. Integer-operators §9.5 has the table, with time and energy, each energy reading named by class: the package counter is `HwShunt`, the runtime's own estimate is `ModelBased`. No speed or energy claim is made: the device path is bound by its dispatches. +- **Results by neural processor (§9.5):** on PCI 0x1022:0x17f0, every graph vector in the subset gave the committed result and the interpreter's: 733 vectors, 1,005 decodes. Every one of twelve kernel edits was caught by the corpus. No other neural processor was run. +- **The Web Neural Network API (§9.5):** a per-operator account from its Candidate Recommendation Draft of 21 October 2026. Its convolution, matrix and quantize operators are floating point, so the subset is exact there only when built from its `"int32"` element-wise operations and reductions; a multiplier needs `"int64"`, which the draft allows but does not require. No browser leg was run. +- **`ops-conformance/npu.jsonl` (§14.5):** each graph vector's place in the subset, from its graph's own verdict. The generator writes it and the independent checker writes it again, and both must give the committed bytes. The device test runs exactly the vectors it lists. +- **determinism-tiers §5 and §6** record the device run. No decoder runs its graphs on a neural processor yet, so the tier is not claimed for a decode there. +- **CI:** `wai-ops-npu.yml` runs the probes, the corpus, the kernel edits and the width test on the `linux-amd` runner's device, which must be present. On `linux-arm64` it requires that the device tests skip without `WAI_OPS_NPU_REQUIRE` and fail with it. + ### `wai.media.upscale`: super-resolution as an exact derivation, residuals against it, and an account of sink-made pictures - **The extension** ([exact-upscale](extensions/exact-upscale.md)) registers the record diff --git a/wai/SPEC.md b/wai/SPEC.md index 18d124311..b162f4eec 100644 --- a/wai/SPEC.md +++ b/wai/SPEC.md @@ -1984,7 +1984,8 @@ WebAssembly, or a neural decoder. can hash ([determinism-tiers](extensions/determinism-tiers.md) §2), on the fabrics determinism-tiers §5 lists: native CPU and WebAssembly in this revision, and, for a decoder's `wai-ops/1-i32` graphs, the GPUs and backends listed there, with no NPU -decoder. It never covers output the application cannot read. A sink MUST NOT state +decoder: on a neural processor only the operator vectors of `wai-ops/1-npu` have run +(integer-operators §9.5). It never covers output the application cannot read. A sink MUST NOT state a tier, an output digest, a reconstruction binding or an energy binding for output decoded inside a protected media path, and a `reconstruct-session` claim (jwp-receipts §2.4.12), whose every presentation carries the SHA-256 of its output, diff --git a/wai/extensions/determinism-tiers.md b/wai/extensions/determinism-tiers.md index 42615920c..c857e6e8f 100644 --- a/wai/extensions/determinism-tiers.md +++ b/wai/extensions/determinism-tiers.md @@ -556,9 +556,18 @@ shows, by fabric: interpreter's outputs, so its `entropy-consistency` claim (§5) holds with its scale path there. Its synthesis graphs use operators outside `wai-ops/1-i32` and ran on the CPU. -- **NPU.** No decoder of these capabilities runs on one in this revision, and the tier is - not claimed there. A network evaluated in floating point on a GPU or NPU does not meet - it (§1). +- **NPU.** `wai/wai-ops-npu` runs a graph of `wai-ops/1-npu` + ([integer-operators](integer-operators.md) §9.2) with its arithmetic operators on one + compute core of a neural processor. The interpreter runs the rest: the plan, the + checks, the range policies and the movement operators. On the device of PCI vendor + 0x1022, device 0x17f0 (firmware 1.1.2.65, device runtime 2.21.75), a run reproduced + every graph vector of integer-operators §14.1 and §14.2 whose graph is in the subset + (§6). Integer-operators §9.5 records that run, and where the device's own arithmetic + is not exact: its shift-round step at shifts of 60 to 63, and its accumulator lanes + past their width. No decoder of these capabilities runs its graphs on a neural + processor in this revision, so the tier is not claimed for a decode there. Nothing is + claimed for any other neural processor. A network evaluated in floating point on a GPU + or NPU does not meet it (§1). The tier covers the medium a sink emits and can hash (§2, *What a tier covers*). A sink that hands a payload to a protected media path whose output the application @@ -604,6 +613,7 @@ task list has no Windows task after that day. The Windows results below are hist | `wai.audio.int_codec`, `wai.splat.int_codec` | committed vectors that never skip. Their frozen hashes are the output of an independent Python integer reference, computed when the vectors were exported (`tools/wai_int_audio_export.py`, `tools/wai_int_splat_export.py`); no Python runs in CI. The conformance workflow's fast-kernel steps enable `audio_int` and `splat_int` and, in debug and release builds, decode both vectors against those hashes under the reference kernels and every fast-kernel policy (`tests/int_kernels_vectors.rs`); that passed on the forge's Linux x86_64, Linux arm64 and macOS arm64 runners for the landing chain's head `7cfcecfa` (run 745, 2026-09-30). The same steps also name `tests/int_audio.rs` and `tests/int_splat.rs`, which check each vector against the same hash and against its own metadata (sample count; attribute count, tensor size and grid side); Cargo declares each with its feature as a required feature, so a step that names it without that feature fails instead of running nothing. This row records no forge run of those two | `wai-rs/tests/int_audio.rs`, `int_splat.rs`, `int_kernels_vectors.rs`; `.github/workflows/wai-conformance.yml` | | `wai.neural.int_mlicpp` on its committed vector (synthetic seeded weights; a 64×64 image, 8 latent channels in 4 slices): the decode reproduces the export tool's independent integer reference byte for byte — the RGB, and every scale bucket and symbol of the latent stream in coding order (512 symbols over all 16 buckets of the scale table, 47 of the symbols coded by the rANS escape) — under the reference kernels and under the fast kernels on 1, 2, 3, 5 and all available threads; one integer unit changed in each of four chosen parameters, one on each path that feeds a value (a hyper-decoder weight, an entropy-network weight, a mean-head bias, a synthesis weight), changes the image; a header whose shapes the model cannot produce, or on which a network would form a buffer over the cap, is refused before anything is allocated (`tests/int_mlicpp_preflight.rs`, which counts allocations); damaged payloads are refused or decode, and never panic in an overflow-checked build | measured 2026-09-29 with rustc 1.98.0 on the source of the change that added this row: macOS arm64 native (the wai-rs tests, with overflow checks on and off; the `byte-exact-conformance` golden, release) and wasm32-wasip2 under wasmtime 49.0.1 (the same tests and golden, release; on wasm the fast kernels run single-threaded as portable scalar code, and `usize` is 32-bit). All pinned hashes matched on both. Not measured: x86_64 — the development machine has no x86_64 translation layer installed, and an x86_64-apple-darwin build of the golden compiles but cannot execute there — nor Linux or Windows. The golden runs on every leg of the byte-exact matrix (`wai-conformance.yml`, `wai-byte-equality.yml`) and `tests/int_mlicpp.rs` on every leg of `wai-conformance.yml` in both profiles. Both passed on the forge's Linux x86_64, Linux arm64 and macOS arm64 runners for the landing chain's head `7cfcecfa` (run 745, 2026-09-30); there is no Windows leg. The dispatch through `decode_envelope_strict` (the prior pinning the one-file model bundle, the strict gate) runs in `wai-neural-gates.yml`. The weights are synthetic, so nothing here measures compression quality | `wai-rs/tests/int_mlicpp.rs`, `wai-rs/tests/int_mlicpp_preflight.rs`, `byte-exact-conformance/src/int_mlicpp_golden.rs`, `wai-rs/tests/neural_native_decode.rs`; `tools/wai_int_mlicpp_export.py` | | `wai-ops/1-i32` graphs on GPUs (`wai/wai-ops-gpu`, §5): each device's arithmetic agrees with 128-bit host arithmetic on every probe case; every graph vector of `ops-conformance/` that the subset covers gives the interpreter's outputs or refusal code, at `acc32` and at `acc64`; the integer decoders, with their graphs on the device, give the CPU decode's bytes or refusal codes on every committed vector and refusal fixture, and every graph the device runs gives the interpreter's outputs | measured 2026-10-20 and 2026-10-21 with rustc 1.98.0 and wgpu 30.0.1, release builds, on the source of the change that added this row, on the adapters §5 lists: Metal on macOS 27.0.1 (arm64); headless Chromium 148, and a Chromium-based browser at 155, on that GPU; Vulkan on Linux x86_64 with the 0x10de and 0x8086 adapters (driver 595.84, Mesa 25.2.8) and, on another host, with the 0x1002 adapter (Mesa 26.0.8); and llvmpipe on both Linux hosts. On each, the probe (`tests/arith.rs`) agreed on every word of 1,051,757 cases, and the corpus (`tests/corpus.rs`) ran 813 vectors (1,104 decodes) on the 32-bit kernels and 1,426 (1,921 decodes) on the 64-bit ones, 212 of the decodes refusals, with no difference; 209 graphs use an operator outside the subset and ran on the CPU, and 95 vectors are refused at load. Run on the 32-bit kernels, 330 of the 613 graphs whose certificate does not meet `acc32` gave other results, so the width a graph runs at is the certificate's. Eighteen edits of one kernel expression each (a rounding shift or its addend off by one, a tie rule, a clamp bound moved by one, a dropped carry or cross term, a sign rule, a post bias, an index rule; `tests/mutants.rs`) were each caught by the corpus on every adapter, and an edited product by the probe. `wai-rs/tests/ops_gpu.rs` decoded the committed vectors and refusal fixtures of the six capabilities §5 names, the world renderer's two vectors and `wai.image.jpegai`'s `jpegai/v1` codestream, comparing every graph the device ran with the interpreter as well as each decode's bytes; none differed. The browser leg ran the corpus, not the decoders. `wai-ops-gpu.yml` runs the Metal and browser legs on the `macos` runner, where an absent adapter fails; this row records no forge run of it yet. No other workflow runs a GPU leg of these tests | `wai-ops-gpu/tests/`, `wai-ops-gpu/browser/`, `wai-rs/tests/ops_gpu.rs`; `.github/workflows/wai-ops-gpu.yml` | +| `wai-ops/1-npu` graphs on a neural processor (`wai/wai-ops-npu`, §5): every graph vector of `ops-conformance/` that `npu.jsonl` lists in the subset gives the committed outputs or refusal code, and the interpreter's, with its arithmetic operators on the device | measured 2026-10-22 with rustc 1.98.0, release builds, on the source of the change that added this row, on the device §5 names (one compute core; the tile array's open toolchain 1.4.3 with its core compiler 22.0.0, Linux 7.0 x86_64). The corpus (`tests/corpus.rs`) ran 733 vectors, 1,005 decodes, 32 of them refusals, with no difference; 302 of the vectors move data only. Each operand width (8-, 16- and 32-bit), the requantisation, the elementwise operators and `lut16` ran on the device. The requantisation probe (`tests/probe.rs`) agreed with §2 on 786,432 cases at every shift from 0 to 63. Twelve edits of the kernel (`tests/mutants.rs`) were each caught by the corpus, among them each of the three roundings mapped to a neighbouring mode of the unit, and the shifts of 60 to 63 left to the unit's shift-round step, which 36 vectors catch. Of the 458 graphs that fail the subset by `acc32` alone, 110 gave other results on the device (`tests/width.rs`). `wai-ops-npu.yml` runs these tests on the `linux-amd` runner, which has this device, and fails when it is absent; this row records no forge run of it yet | `wai-ops-npu/tests/`; `ops-conformance/npu.jsonl`; `.github/workflows/wai-ops-npu.yml` | So at `entropy-consistency`, WAI's JPEG AI symbol decode is demonstrated across architectures (x86_64 against arm64) once the distribution indices are given; the @@ -625,7 +635,8 @@ legs in run 745. On GPUs, the committed vectors of `int_hyper` (and its video ke `int_codec` audio and splat, `int_synth` and `int_mlicpp` decode with their graphs on the device to the CPU decode's bytes on each adapter §5 lists, measured by hand on small vectors; the `wai-ops-gpu` workflow repeats it on one of them, with no forge run -recorded yet. +recorded yet. On a neural processor, only the operator vectors have run +(`wai-ops-npu`); no decoder runs its graphs there yet. For the JPEG AI exact decoder's pinned reconstruction (§5), identity across machines rests on construction, and on one comparison: a single codestream on the diff --git a/wai/extensions/integer-operators.md b/wai/extensions/integer-operators.md index c0092f793..8455c8b2b 100644 --- a/wai/extensions/integer-operators.md +++ b/wai/extensions/integer-operators.md @@ -14,7 +14,7 @@ > > **What a reader can build from this text.** With the payload extensions it cites, an implementation of this text decodes `wai.video.int_motion`, `wai.audio.int_codec`, `wai.splat.int_codec`, `wai.neural.int_synth`, `wai.neural.int_hyper`, `wai.video.int_hyper`, `wai.neural.int_mlicpp` and `wai.{image,video,audio,splat}.int_refine`, and reproduces the integers of `wai.image.jpegai`'s integer stages: the symbols and scale indices it entropy-decodes, and what its integer networks give for their inputs (§13.6). The payload text four of these depend on, `entropy.json`, the `int_synth` payload and the `WIM1` payload with its `WMB1` bundle, is in [integer-payloads](integer-payloads.md), and §15.2 says where. The integer renderer of `wai.world.model` is informative (§13.5). > -> Reference implementation: the `wai-ops` crate (`wai/wai-ops`): parse, lowering, interpreter, stage operators and bounds. Tool: `wai-verify bounds`. Conformance: `wai/ops-conformance/`. An operator implementation of `wai-ops/1-i32` for GPUs, at both profiles, is `wai/wai-ops-gpu` (WGSL); [determinism-tiers](determinism-tiers.md) §5 lists the adapters on which it has reproduced the vectors. A capability's reference decoder decodes through this operator set from the revision that registers the capability's `ops` member (§11); in this revision, `wai.neural.int_synth`, `wai.splat.int_codec`, `wai.audio.int_codec`, `wai.neural.int_hyper`, `wai.video.int_hyper` and `wai.neural.int_mlicpp`, and the integer stages of `wai.image.jpegai` (§13.6): its scale path and entropy decodes on both of its reference decode paths, and its integer networks on its exact decoder; and, through their stage operators alone with no graph (§13.5), `wai.video.int_motion`, the P-frames of `wai.video.int_hyper`, and `wai.{image,video,audio,splat}.int_refine`. The integer renderer of `wai.world.model` renders through it too, by `world/1` (§13.5), and stays informative. Until then, a capability's reference implementation is the decoder its payload extension names, and §15.3 lists where this text and that decoder differ. +> Reference implementation: the `wai-ops` crate (`wai/wai-ops`): parse, lowering, interpreter, stage operators and bounds. Tool: `wai-verify bounds`. Conformance: `wai/ops-conformance/`. An operator implementation of `wai-ops/1-i32` for GPUs, at both profiles, is `wai/wai-ops-gpu` (WGSL); [determinism-tiers](determinism-tiers.md) §5 lists the adapters on which it has reproduced the vectors. An operator implementation of `wai-ops/1-npu` for neural processors is `wai/wai-ops-npu`; §9.5 lists the devices on which it has reproduced the vectors. A capability's reference decoder decodes through this operator set from the revision that registers the capability's `ops` member (§11); in this revision, `wai.neural.int_synth`, `wai.splat.int_codec`, `wai.audio.int_codec`, `wai.neural.int_hyper`, `wai.video.int_hyper` and `wai.neural.int_mlicpp`, and the integer stages of `wai.image.jpegai` (§13.6): its scale path and entropy decodes on both of its reference decode paths, and its integer networks on its exact decoder; and, through their stage operators alone with no graph (§13.5), `wai.video.int_motion`, the P-frames of `wai.video.int_hyper`, and `wai.{image,video,audio,splat}.int_refine`. The integer renderer of `wai.world.model` renders through it too, by `world/1` (§13.5), and stays informative. Until then, a capability's reference implementation is the decoder its payload extension names, and §15.3 lists where this text and that decoder differ. > > The keywords **MUST**, **MUST NOT**, **SHOULD** and **MAY** are as defined in RFC 2119 and RFC 8174. @@ -975,7 +975,7 @@ A node also records, in `mac`, the exact bit width of every multiply operand: ea - dilation; - shapes that depend on values, other than a `crop` from scalars. -**Membership is not a claim of exactness.** It says that a graph uses only what integer accelerators are built to compute. Whether an accelerator computes it *exactly* is shown only by the vectors of §14, run on that accelerator (§12). +**Membership is not a claim of exactness.** It says that a graph uses only what integer accelerators are built to compute. Whether an accelerator computes it *exactly* is shown only by the vectors of §14, run on that accelerator (§12). §9.5 records the neural processors on which those vectors have run, and what each one's arithmetic does where it is not exact. ### 9.3 Transposed convolution as phase convolutions (informative) @@ -1005,6 +1005,66 @@ These rewrites are exact: each gives the same integers as the operator it replac - **Replicate padding.** Not every target pads by replication. `pad_rep` can be built from `crop` and `concat` of edge rows and columns. - **Storage limits.** A tensor of `2^26` elements at 4 bytes each is 256 MiB, more than some fabrics allow in one storage binding (128 MiB is a common default). An implementation tiles such a tensor across bindings (§6). +### 9.5 `wai-ops/1-npu` on neural processors (informative) + +An operator implementation of `wai-ops/1-npu` for neural processors is `wai/wai-ops-npu`. It is a kernel for one compute core of a tile array, compiled for the core by the array's open toolchain. Its host side is built against the device runtime. The interpreter runs each graph and asks the core for the outputs of every operator that computes values (§6): +- `conv` and `conv_transpose`, as sums over taps. The host gathers each position's taps, with zero where padding, or a transposed convolution's stride, leaves no input. The core accumulates them from the `pre` bias and requantises the sum. The operand width is read from the certificate's `mac` record (§8.5). Operands that fit 8 or 16 bits go into the vector unit's 32-bit accumulator lanes, and 32-bit operands into its 64-bit lanes. A sum of more taps than one dispatch holds is split, and each part's accumulators carry into the next. +- `rescale`, `chscale` and `mul`, through the vector unit's multiply and its shift-round step. +- `add`, `chunk_add`, `clamp`, `relu`, `abs` and `leaky`. +- `lut16`. The table goes to the core one block of 4,096 entries at a time, with the inputs whose entries lie in that block. + +The host keeps the rest: the plan, every check of §4's run, each tensor's range policy, the movement operators (which compute no value), and the state of a sequence. So a device adds no refusal. Every refusal of a graph in the subset is the interpreter's, made on the host before or after the device's work. A graph in the subset has no `check` tensor inside it (§7.2). + +**The unit's arithmetic, measured.** The vector unit forms `t·m` exactly in a 64-bit accumulator lane, from two 32-bit operands. Its shift-round step then narrows the lane to 32 bits under one of its own rounding modes. A probe ran `t·m` through that step under each mode at every shift from 0 to 63. The cases were exact ties of §2, a value one unit either side of a tie, random operands at every width, and the extreme operands. It compared each result with §2 wherever the rounded value fits 32 bits: + +| the unit's mode | equals | at shifts | +|---|---|---| +| round toward −∞ | `R_floor` | 0 to 59 | +| round to nearest, ties toward +∞ | `R_half_up` | 0 to 59 | +| round to nearest, ties to even | `R_half_even` | 0 to 59 | +| round to nearest, ties away from zero | no rounding of §2 | it agrees with `R_half_up` on non-negative products only (§9.4) | +| round toward +∞; round to nearest, ties toward −∞ | `R_half_up` and `R_floor` respectively, at shift 1 only | | +| toward zero; away from zero; ties toward zero; ties to odd | no rounding of §2 at any shift but 0 | | + +- **Shifts of 60 to 63.** At these shifts every mode gave `t·m` shifted *left* by `64 − s` and saturated, on every case. The unit cannot form `R(t·m, s)` for those shifts in one step, and an `acc32` graph may use them (§7.2 allows shifts up to 63). Of the graph vectors in the subset, 36 have such a shift (`chscale` 5, `conv` 6, `conv_transpose` 3, `leaky` 2, `mul` 4, `rescale` 16). The implementation forms those in the core's scalar unit, in 64-bit arithmetic. A kernel that left them to the shift-round step gave other results on all 36. +- **Saturation.** When the rounded value passes 32 bits, the step's saturating mode clamps it, and its non-saturating mode keeps its low 32 bits. Neither is `R(t·m, s)`. `acc32` keeps every `rq.q` within i32 (§7.1), so no graph in the subset reaches this. +- **Accumulator lanes.** A 32-bit lane (8- and 16-bit operands) keeps the low 32 bits of a sum that passes i32, and a 64-bit lane (32-bit operands) wraps at 64 bits. So the narrow paths are exact only where every partial sum fits i32, which `acc32` certifies (`conv.acc`, `convT.acc`). They also need the operands to fit the narrower width (`mac`). +- **Outside the subset.** The corpus has 458 graphs that use only the subset's operators but whose verdict fails `acc32`. Run on the device anyway, 110 of them gave other results than the interpreter's, and 226 operators held a value past the 32-bit words the device takes. Membership is decided by the certificate (§9.2), and these runs show why. + +The vector unit's requantisation, with the scalar route for shifts of 60 to 63, agreed with §2 on 786,432 cases. Those cases covered every rounding of §2, every shift from 0 to 63, each with one multiplier and one `post` bias per channel, and every case whose result fits 32 bits. + +**Results by neural processor.** A run reproduced a graph vector when every decode gave the committed outputs, or the committed refusal code, and the interpreter's. The vectors are those `npu.jsonl` lists in the subset (§14.5). + +| neural processor | toolchain and runtime | result | +|---|---|---| +| PCI vendor 0x1022, device 0x17f0, revision 0x11: its tile array's second generation, firmware 1.1.2.65; one compute core used | the tile array's open toolchain 1.4.3 with its core compiler 22.0.0, device runtime 2.21.75, Linux 7.0 x86_64 | every graph vector in the subset: 733 vectors and 1,005 decodes, 32 of them refusals, with no difference. 302 of the vectors move data only, so the device ran nothing for them. Each operand width and each kind of work ran: 77 sums at 8 bits, 4,160 at 16 bits and 34 at 32 bits, 687 requantisations, 458 elementwise operations and 23 table blocks. Every one of twelve edits of the kernel was caught by the corpus. Measured 2026-10-22, release builds, rustc 1.98.0 | +| any other neural processor, including the neural engine of an arm64 desktop | | not run; nothing is claimed | + +*Time and energy* (`ops-npu-bench --secs 3 --rounds 3`, the same host, 2026-10-22). Every device result was first compared with the interpreter's. Each figure below is the range over three windows of 3 s: + +| workload | CPU (interpreter) | the device | +|---|---|---| +| one pass over the subset's vectors | 1.12–1.14 ms | 192.6–193.0 ms (2,172 dispatches) | +| an `int_synth` graph (§13.1), `[8, 32, 32]` to `[3, 64, 64]`, three transposed convolutions | 7.22–7.26 ms | 89.1–89.2 ms (778 dispatches) | + +- The device path is bound by its dispatches. It uses one core, four work units per dispatch and a host round trip per operator, and the host gathers every tap. No speed claim is made. +- The package's energy counter (powercap `package-0`) is a vendor counter, a reading of class `HwShunt` while it counts. It covers the whole package and everything running on it, including other users' work on that host. Its readings per window were 26.1–29.0 J idle; 90.7–94.6 J with the CPU looping either workload; 74.7–75.4 J with the device looping the vectors; and 76.4–78.1 J with the device looping the synthesis. +- The device runtime also reports a power figure that it calls an estimate. Integrated over a window, that is a model, class `ModelBased`. It gave 2.0–2.6 J per device window. In idle windows it gave 0.3–1.2 J, higher right after device work, so it lags. +- Per pass, the device path costs more energy than the CPU on these workloads. No energy claim is made for the device. + +**The Web Neural Network API.** Whether a browser could run the subset exactly through this API was decided from the W3C Candidate Recommendation Draft of 21 October 2026 (`CRD-webnn-20261021`, section numbers below). No implementation of it was run. The API has no integer multiply-accumulate operator, and its requantisation operator rounds in floating point. So the subset is exact there only when each operator is built from the API's integer element-wise operations and reductions. Then every value stays within i32, which `acc32` certifies. The CR states no result for an integer value that leaves its type, and the certificate excludes one. + +| operator | through the API | why | +|---|---|---| +| `conv`, `conv_transpose` | not through `conv2d` (§8.9.10), `convTranspose2d` (§8.9.11), `matmul` (§8.9.35) or `gemm` (§8.9.24) | Each accepts only `"float32"`, `"float16"` and `"bfloat16"` operands. A float32 sum of integer products is exact only while every partial sum stays within 2^24, and the CR does not fix the precision of intermediate results. Exact by composition: gather the taps (`slice` §8.9.47, `reshape` §8.9.42, `transpose` §8.9.54), then `mul` (§8.9.13) and `reduceSum` (§8.9.39), all on `"int32"`, the type each is required to support. 8-bit inputs widen first by `cast` (§8.9.7), which keeps an in-range integer's value. | +| `rescale`, `chscale`, `mul` (the requantisation of §5.1) | not through `quantizeLinear` (§8.9.17) or `dequantizeLinear` (§8.9.16) | `quantizeLinear` divides in floating point and rounds by `roundEven`. That gives `R_half_even` only for a power-of-two scale and a value within 2^24, and no form of `R_floor` or `R_half_up`. Its required outputs are 8-bit. `dequantizeLinear` gives a floating-point result. Exact by composition: the API has no shift. Its integer `div` truncates toward zero (§8.9.13), so a floor needs a correction built from `lesser` (§8.9.14) and `where` (§8.9.56). A multiplier forms a product up to 2^62, which needs `"int64"`: the CR allows that type for `mul` but does not require it, and `opSupportLimits()` (§8.3.9) says whether an implementation has it. A shift of 63 has no `"int64"` divisor. | +| `lut16` | exact through `gather` (§8.9.20) | The table is an `"int32"` input, and the index `v + 32768` is an `"int32"` `add`. Every index lies in the table (§5.6). | +| `add`, `chunk_add` | exact through `add` (§8.9.13), with `slice` for `chunk_add` | `"int32"` is a required type. | +| `abs` | exact (§8.9.15) | `"int32"` is a required type. | +| `relu`, `clamp` | exact through `max` and `min` (§8.9.13) on `"int32"` | `relu` (§8.9.40) and `clamp` (§8.9.8) allow `"int32"` but require only floating-point types. | +| `leaky` | not through `leakyRelu` (§8.9.31) | It takes floating-point operands and a floating-point slope. Exact by composition: `mul`, then a floor division by `2^f` built as for the requantisation, then `where`. | +| the movement operators | exact through `concat` (§8.9.9), `slice`, `reshape`, `transpose`, `pad` in its `"edge"` mode (§8.9.36) and `expand` (§8.9.19) | They move values and compute none. `pad` allows `"int32"` but requires only floating-point types. | + ## 10. `WHG1`: the graph form A `WHG1` file holds one graph. A parameter set that carries graphs names each one by role, in its capability's registration. Every multi-byte field is little-endian, and every reserved field is zero. @@ -1610,6 +1670,15 @@ A conforming certifier reproduces every certificate byte for byte. A conforming Each capability of §13 is decoded through its lowering. It MUST reproduce every committed output digest and every refusal code of its existing corpora, as listed in integer-payloads §12, staged-delivery and SPEC §5. +### 14.5 The accelerator subset's vectors + +`wai/ops-conformance/npu.jsonl` gives each graph vector's place in `wai-ops/1-npu` (§9.2). It has one line per vector of `vectors/` and `graphs/` whose graph loads. The files are taken in the byte order of their paths, and each file's vectors in its own order. Each line is a canonical JSON object (§8.5's rules) with these members: +- `file`: the vector file's path below `ops-conformance/`; +- `id`: the vector's id; +- `npu`, `npu_flags` and `npu_reasons`: those of the verdict (§8.5) of the vector's graph, certified on its own (lowering `"whg1"`). + +An operator implementation of `wai-ops/1-npu` conforms on a fabric when it reproduces every vector whose line has `npu` true, refusals included (§1). The flags tell its toolchain which features each vector exercises. The generator writes this file, and the independent checker writes it again from its own bounds pass. Both MUST give the committed bytes. + ## 15. Changes to other documents ### 15.1 Text changes diff --git a/wai/ops-conformance/README.md b/wai/ops-conformance/README.md index 4f68e4f95..d621019bc 100644 --- a/wai/ops-conformance/README.md +++ b/wai/ops-conformance/README.md @@ -36,6 +36,10 @@ lowering conforms when it writes every lowered graph byte for byte. - `certificates//.json` — each set's bounds certificate (§8.5, §14.3), and beside it `certificates///.whg1`, each lowered graph. +- `npu.jsonl` — each graph vector's place in `wai-ops/1-npu` (§9.2, §14.5): one line per + vector of `vectors/` and `graphs/` whose graph loads, with the `npu`, `npu_flags` and + `npu_reasons` of its graph's own verdict. An operator implementation of the subset + conforms when it reproduces every vector whose line has `npu` true. - `certificates/index.json` — every set lowered here: its capability, its lowering, its members (the name in the set and the file, relative to `wai/`, that holds it), its digest, and the path of each lowered graph, in §13.0's role order. @@ -118,7 +122,7 @@ lowering's latent channel count, which the initial latent fixes, is the entry's cargo run --release --features cli --bin wai-verify -- vectors gen .. writes every file here but this README, `.gitattributes`, and `external.json` with -the certificates it lists. +the certificates it lists. `npu.jsonl` is among them. cargo run --release --features cli --bin wai-verify -- vectors verify .. @@ -140,7 +144,7 @@ is the independent checker: Python standard library only, sharing no code with `wai-ops`, and held to the extension's text: where the two disagree, the text decides, and the disagreement is a bug in one of them. It rewrites every vector file from its inputs, `load` (steps 1–8) and `profiles` from its own bounds pass and -verdict (§8.1–§8.5) included; re-lowers every set and requires each lowered graph, +verdict (§8.1–§8.5) included, and `npu.jsonl` from the same verdicts; re-lowers every set and requires each lowered graph, and that each loads; and requires each lowering vector's graph to be the role its id names. It is also a second certifier (§1): from each set's lowered graphs it writes the set's certificate (§8.5) and requires the committed bytes, for every set of @@ -155,3 +159,24 @@ certify an external set from the directory that holds its files, and one set (or `WHG1` graphs on their own, with `--whg1 =`), with the exit statuses of `wai-verify bounds`: 0, 1, 3 as above; 2 when a graph is malformed or refused at load; 4 when the lowering refuses the set; 5 for usage. + +## On a neural processor + +`wai/wai-ops-npu` runs every vector `npu.jsonl` lists in the subset with its arithmetic +operators on a neural processor's compute core, and holds each decode to the committed +result and to the interpreter (integer-operators §9.5). `wai-ops-npu.yml` runs it on the +`linux-amd` runner. By hand, on a Linux host with the device, its driver and the device +runtime, and a Python environment with the tile array's open toolchain (the workflow +names the release and the core compiler it pins): + + cd wai/wai-ops-npu + WAI_OPS_NPU_PYTHON=/bin/python WAI_OPS_NPU_REQUIRE=1 \ + WAI_OPS_NPU_EXPECT=npu-1022-17f0 WAI_OPS_NPU_REPORT=/tmp/ops-npu.report \ + nice -n 10 cargo test --locked --release -- --nocapture --test-threads=1 + +The crate builds the core's program and its host side on first use, into +`WAI_OPS_NPU_CACHE` (else the system's temporary directory). With no device every +device test prints SKIPPED and passes, and fails under `WAI_OPS_NPU_REQUIRE`. The report +names the device and gives, per test, the counts a run must show: `ops-npu corpus: … +declined=0 mismatches=0 errors=0`, `ops-npu mutants: … killed 12 of 12`, and the +probes' lines. diff --git a/wai/wai-verify/src/bin/wai-verify/corpus.rs b/wai/wai-verify/src/bin/wai-verify/corpus.rs index 6b5233044..cc31b5b41 100644 --- a/wai/wai-verify/src/bin/wai-verify/corpus.rs +++ b/wai/wai-verify/src/bin/wai-verify/corpus.rs @@ -1,7 +1,8 @@ //! `wai-verify vectors`: the conformance corpus of `wai/ops-conformance/` (§14). //! //! - `gen ` writes every file of `/ops-conformance` but its README -//! and `.gitattributes`: the vector files, the lowered graphs and the certificates. +//! and `.gitattributes`: the vector files, the lowered graphs, the certificates and +//! `npu.jsonl` (§14.5). //! - `verify ` recomputes every vector file from its inputs; re-lowers every //! set that `certificates/index.json` and `certificates/falsification.json` list, //! recomputes its certificate from the committed lowered graphs, and checks that a -- 2.54.0 (Apple Git-157) From af8479a7c891fe01241e78d70dc690163576afb2 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:19:23 -0400 Subject: [PATCH 005/242] wai-verify vectors verify: recompute only the files of vectors/, stage/ and graphs/ npu.jsonl is a .jsonl file that is not a vector file; step 4 already checks it as the generator writes it. Co-Authored-By: Claude Opus 5.5 --- wai/wai-verify/src/bin/wai-verify/corpus.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/wai/wai-verify/src/bin/wai-verify/corpus.rs b/wai/wai-verify/src/bin/wai-verify/corpus.rs index cc31b5b41..d6875de42 100644 --- a/wai/wai-verify/src/bin/wai-verify/corpus.rs +++ b/wai/wai-verify/src/bin/wai-verify/corpus.rs @@ -20,11 +20,19 @@ use wai_ops::generate::{self, sets}; use wai_ops::json; use wai_ops::vectors::{self, Kind}; +/// The kind of a vector file, by its path below `ops-conformance/`; `None` for any other +/// file, `npu.jsonl` among them, which step 4 checks as the generator writes it. fn kind_of(rel: &str) -> Option { if !rel.ends_with(".jsonl") { return None; } - if rel.starts_with("stage/") { Some(Kind::Stage) } else { Some(Kind::Graph) } + if rel.starts_with("stage/") { + Some(Kind::Stage) + } else if rel.starts_with("vectors/") || rel.starts_with("graphs/") { + Some(Kind::Graph) + } else { + None + } } fn reader(wai: &Path) -> impl Fn(&str) -> Option> + '_ { -- 2.54.0 (Apple Git-157) From 6265e742621a3dddf27a8412d457d8adc53a5aab Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:20:27 -0400 Subject: [PATCH 006/242] wai-ops-npu: clippy's notes Co-Authored-By: Claude Opus 5.5 --- wai/wai-ops-npu/src/bin/bench.rs | 5 ++++- wai/wai-ops-npu/src/lines.rs | 10 +++++----- wai/wai-ops-npu/src/probe.rs | 16 ++++++++-------- wai/wai-ops-npu/tests/support/rq.rs | 22 +++++++++++++--------- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/wai/wai-ops-npu/src/bin/bench.rs b/wai/wai-ops-npu/src/bin/bench.rs index 39847437c..d9a70cb21 100644 --- a/wai/wai-ops-npu/src/bin/bench.rs +++ b/wai/wai-ops-npu/src/bin/bench.rs @@ -32,9 +32,12 @@ use wai_ops::vectors::read_tensor; use wai_ops::{Loaded, Profile, Sequence, Tensor}; use wai_ops_npu::{Npu, Plan, discover, plan}; +/// Each decode's data inputs and scalars. +type Decodes = Vec<(Vec, Vec)>; + struct Work { name: &'static str, - graphs: Vec<(Loaded, Plan, Vec<(Vec, Vec)>)>, + graphs: Vec<(Loaded, Plan, Decodes)>, } fn corpus() -> Work { diff --git a/wai/wai-ops-npu/src/lines.rs b/wai/wai-ops-npu/src/lines.rs index 76af4d728..66381f874 100644 --- a/wai/wai-ops-npu/src/lines.rs +++ b/wai/wai-ops-npu/src/lines.rs @@ -194,7 +194,7 @@ pub fn max_positions(width: Width) -> usize { pub fn mac_line(width: Width, w: &[i64], x: &[Vec], init: &Init, fin: bool, epi: Epi) -> Result { let k = w.len(); let n = x.first().map_or(0, |r| r.len()); - assert!(k >= 1 && n >= 1 && n <= MAX_N && x.len() == k && x.iter().all(|r| r.len() == n)); + assert!(k >= 1 && (1..=MAX_N).contains(&n) && x.len() == k && x.iter().all(|r| r.len() == n)); assert!(k <= max_taps(width, n, matches!(init, Init::Vector(_))), "{k} taps, {n} positions: more than a line holds"); let rs = rs_of(n); let mut l = Line::new(J_MAC); @@ -256,7 +256,7 @@ fn put(row: &mut [i32], width: Width, p: usize, v: i64) { /// `b` empty) or `RQ_MUL` (`t = a·b`), then the epilogue. pub fn rq_line(sub: i32, a: &[i64], b: &[i64], pre: i32, epi: Epi) -> Result { let n = a.len(); - assert!(n >= 1 && n <= MAX_N); + assert!((1..=MAX_N).contains(&n)); let rs = rs_of(n); let mut l = Line::new(J_RQ); l.set(1, sub).set(3, n as i32).set(6, pre).epi(epi); @@ -276,7 +276,7 @@ pub fn rq_line(sub: i32, a: &[i64], b: &[i64], pre: i32, epi: Epi) -> Result Result { let n = a.len(); - assert!(n >= 1 && n <= MAX_N); + assert!((1..=MAX_N).contains(&n)); let rs = rs_of(n); let mut l = Line::new(J_EW); l.set(1, sub).set(3, n as i32).set(4, h4).set(5, h5).set(6, h6); @@ -297,7 +297,7 @@ pub fn ew_line(sub: i32, a: &[i64], b: &[i64], h4: i32, h5: i32, h6: i32) -> Res /// input values whose index `v + 32768` lies in that block. pub fn lut_line(t: &[i16], base: usize, v: &[i64]) -> Result { let n = v.len(); - assert!(n >= 1 && n <= MAX_N); + assert!((1..=MAX_N).contains(&n)); let tb = t.len().min(LUT_BLOCK); let mut l = Line::new(J_LUT); l.set(3, n as i32).set(4, base as i32).set(5, tb as i32); @@ -322,7 +322,7 @@ pub fn lut_line(t: &[i16], base: usize, v: &[i64]) -> Result { /// mode `mode` and saturation mode `sat` (the unit's own encodings). pub fn probe_line(mode: i32, sat: i32, shift: i32, t: &[i32], m: &[i32]) -> Line { let n = t.len(); - assert!(n >= 1 && n <= MAX_N && m.len() == n); + assert!((1..=MAX_N).contains(&n) && m.len() == n); let rs = rs_of(n); let mut l = Line::new(J_PROBE); l.set(1, mode).set(2, sat).set(3, n as i32).set(9, shift); diff --git a/wai/wai-ops-npu/src/probe.rs b/wai/wai-ops-npu/src/probe.rs index ce2470ec0..6d72e3fb7 100644 --- a/wai/wai-ops-npu/src/probe.rs +++ b/wai/wai-ops-npu/src/probe.rs @@ -58,7 +58,7 @@ impl Rng { pub fn new(seed: u64) -> Rng { Rng(seed) } - pub fn next(&mut self) -> u64 { + pub fn next_u64(&mut self) -> u64 { self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15); let mut z = self.0; z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); @@ -66,11 +66,11 @@ impl Rng { z ^ (z >> 31) } pub fn i32(&mut self) -> i32 { - self.next() as i32 + self.next_u64() as i32 } /// A value of at most `bits` bits, signed. pub fn bits(&mut self, bits: u32) -> i32 { - let v = self.next() as i64; + let v = self.next_u64() as i64; (v >> (64 - bits.clamp(1, 32))) as i32 } } @@ -88,13 +88,13 @@ pub fn cases(s: u32, n: usize, rng: &mut Rng) -> Vec<(i32, i32)> { // One unit either side of a tie: t = odd·2^(s−1) ± 1, m = 1. let u = rng.bits(33 - s) as i64 >> 1; let t0 = (2 * u + 1) << (s - 1); - ((t0 + if rng.next() & 1 == 0 { 1 } else { -1 }).clamp(i32::MIN as i64, i32::MAX as i64) as i32, 1) + ((t0 + if rng.next_u64() & 1 == 0 { 1 } else { -1 }).clamp(i32::MIN as i64, i32::MAX as i64) as i32, 1) } - 3 => (EDGE[(rng.next() % 6) as usize], EDGE[(rng.next() % 6) as usize]), + 3 => (EDGE[(rng.next_u64() % 6) as usize], EDGE[(rng.next_u64() % 6) as usize]), 4 => (rng.bits(8), rng.bits(8)), 5 => (rng.bits(16), rng.bits(16)), 6 => { - let (bt, bm) = (1 + (rng.next() % 32) as u32, 1 + (rng.next() % 32) as u32); + let (bt, bm) = (1 + (rng.next_u64() % 32) as u32, 1 + (rng.next_u64() % 32) as u32); (rng.bits(bt), rng.bits(bm)) } _ => (rng.i32(), rng.i32()), @@ -111,12 +111,12 @@ fn tie(s: u32, rng: &mut Rng) -> (i32, i32) { if a <= 29 { let u = rng.bits(31 - a) as i64 >> 1; let t = ((2 * u + 1) << a) as i32; - let m = if rng.next() & 1 == 0 { 1i32 << b } else { -(1i32 << b) }; + let m = if rng.next_u64() & 1 == 0 { 1i32 << b } else { -(1i32 << b) }; (t, m) } else { // s = 61..63: 2^30·(±1)·(−2^31) = ∓2^61, a tie at s = 62 only; at s = 61 and 63 // these are the extreme products. - (if rng.next() & 1 == 0 { 1 << 30 } else { -(1 << 30) }, i32::MIN) + (if rng.next_u64() & 1 == 0 { 1 << 30 } else { -(1 << 30) }, i32::MIN) } } diff --git a/wai/wai-ops-npu/tests/support/rq.rs b/wai/wai-ops-npu/tests/support/rq.rs index c23208a85..6a0610d91 100644 --- a/wai/wai-ops-npu/tests/support/rq.rs +++ b/wai/wai-ops-npu/tests/support/rq.rs @@ -11,6 +11,10 @@ use wai_ops_npu::Npu; use wai_ops_npu::lines::{Epi, MAX_N, RQ_RESCALE, round_code, rq_line}; use wai_ops_npu::probe::{ROUNDS, Rng, round_name, spec}; +/// One line's cases: the inputs, the multiplier, the shift, the rounding and the results +/// §2 gives. +type Want = (Vec, i32, u32, &'static str, Vec); + /// What one probe run gave: the cases run, those that differ, and the first that did. pub fn run(npu: &Npu, reps: usize, seed: u64) -> (u64, String) { let (_, bad, first) = run_counted(npu, reps, seed); @@ -20,23 +24,23 @@ pub fn run(npu: &Npu, reps: usize, seed: u64) -> (u64, String) { pub fn run_counted(npu: &Npu, reps: usize, seed: u64) -> (u64, u64, String) { let mut rng = Rng::new(seed); let mut lines = Vec::new(); - let mut want: Vec<(Vec, i32, u32, &'static str, Vec)> = Vec::new(); + let mut want: Vec = Vec::new(); for s in 0..=63u32 { for r in ROUNDS { for _ in 0..reps { - let class = rng.next() % 6; + let class = rng.next_u64() % 6; let (m, pow_b) = match class { 0 | 1 => { - let b = if s == 0 { 0 } else { (rng.next() % s.min(31) as u64) as u32 }; + let b = if s == 0 { 0 } else { (rng.next_u64() % s.min(31) as u64) as u32 }; let v = 1i64 << b; ((if class == 0 { v } else { -v }).clamp(i32::MIN as i64, i32::MAX as i64) as i32, Some(b)) } 2 => (rng.i32(), None), 3 => (rng.bits(16), None), - 4 => ([i32::MIN, i32::MAX, -1, 1][(rng.next() % 4) as usize], None), + 4 => ([i32::MIN, i32::MAX, -1, 1][(rng.next_u64() % 4) as usize], None), _ => (rng.bits(8), None), }; - let post = if rng.next() % 3 == 0 { 0 } else { rng.bits(20) }; + let post = if rng.next_u64().is_multiple_of(3) { 0 } else { rng.bits(20) }; let mut ts = Vec::with_capacity(MAX_N); let mut ys = Vec::with_capacity(MAX_N); let mut tries = 0; @@ -48,15 +52,15 @@ pub fn run_counted(npu: &Npu, reps: usize, seed: u64) -> (u64, u64, String) { let a = s - 1 - b; let u = rng.bits(31 - a) as i64 >> 1; let t = ((2 * u + 1) << a) as i32; - if k % 4 == 1 { t.saturating_add(if rng.next() & 1 == 0 { 1 } else { -1 }) } else { t } + if k % 4 == 1 { t.saturating_add(if rng.next_u64() & 1 == 0 { 1 } else { -1 }) } else { t } } - _ => match rng.next() % 4 { + _ => match rng.next_u64() % 4 { 0 => rng.i32(), 1 => { - let bt = 1 + (rng.next() % 32) as u32; + let bt = 1 + (rng.next_u64() % 32) as u32; rng.bits(bt) } - 2 => [i32::MIN, i32::MAX, -1, 0, 1][(rng.next() % 5) as usize], + 2 => [i32::MIN, i32::MAX, -1, 0, 1][(rng.next_u64() % 5) as usize], _ => rng.bits(12), }, }; -- 2.54.0 (Apple Git-157) From 89e14b949e18dfbe1db9f84cf49934580b7aa1dc Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:21:28 -0400 Subject: [PATCH 007/242] =?UTF-8?q?wai:=20tensor=20files=20as=20prior=20fo?= =?UTF-8?q?rms=20=E2=80=94=20safetensors,=20GGUF=20and=20the=20NNC=20subse?= =?UTF-8?q?t,=20the=20integer-quantised=20profile,=20and=20their=20corpus?= =?UTF-8?q?=20(work=20in=20progress)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit prior-carriage §9 to §14 and Appendix A: wai.prior.safetensors and wai.prior.gguf registered as prior forms and listed as established exact; the integer-quantised profile wai-int/1 with the bindings wsr1/1 and whm1/1; the digest rule (a canonical file per form); the NNC reader and writer for the subset an open implementation of the 2024 edition reads (integer tensors under the context-adaptive arithmetic coding, raw floats), with where it stops. wai-rs: prior_wire (feature prior_wire), the corpus generator wai_prior_forms_vectors and tests/prior_wire.rs. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/prior-carriage.md | 608 +++++++++- wai/live-conformance/prior-forms/README.md | 1 + .../prior-forms/files/generic.gguf | Bin 0 -> 5792 bytes .../prior-forms/files/generic.nnc | Bin 0 -> 4701 bytes .../prior-forms/files/generic.safetensors | Bin 0 -> 5813 bytes .../prior-forms/files/hyper_g_s.gguf | Bin 0 -> 1394 bytes .../prior-forms/files/hyper_g_s.nnc | Bin 0 -> 669 bytes .../prior-forms/files/hyper_g_s.safetensors | Bin 0 -> 1410 bytes .../prior-forms/files/hyper_h_s.gguf | Bin 0 -> 1440 bytes .../prior-forms/files/hyper_h_s.nnc | Bin 0 -> 809 bytes .../prior-forms/files/hyper_h_s.safetensors | Bin 0 -> 1449 bytes .../prior-forms/files/natives/badtag.whm | Bin 0 -> 627 bytes .../prior-forms/files/natives/trailing.wsr | Bin 0 -> 771 bytes .../prior-forms/files/natives/wide.whm | Bin 0 -> 627 bytes .../files/neg/gg_alignment_explicit.gguf | Bin 0 -> 1872 bytes .../prior-forms/files/neg/gg_bad_key.gguf | Bin 0 -> 1840 bytes .../prior-forms/files/neg/gg_bad_magic.gguf | Bin 0 -> 1840 bytes .../files/neg/gg_duplicate_key.gguf | Bin 0 -> 1840 bytes .../prior-forms/files/neg/gg_five_dims.gguf | Bin 0 -> 1840 bytes .../prior-forms/files/neg/gg_misaligned.gguf | Bin 0 -> 1840 bytes .../files/neg/gg_no_architecture.gguf | Bin 0 -> 1808 bytes .../files/neg/gg_nonzero_padding.gguf | Bin 0 -> 1840 bytes .../prior-forms/files/neg/gg_q4_0.gguf | Bin 0 -> 1840 bytes .../files/neg/gg_trailing_byte.gguf | Bin 0 -> 1841 bytes .../files/neg/gg_truncated_header.gguf | Bin 0 -> 40 bytes .../prior-forms/files/neg/gg_version_2.gguf | Bin 0 -> 1840 bytes .../files/neg/nnc_alignment_bit.nnc | Bin 0 -> 883 bytes .../prior-forms/files/neg/nnc_dq_flag.nnc | Bin 0 -> 883 bytes .../prior-forms/files/neg/nnc_long_size.nnc | Bin 0 -> 885 bytes .../prior-forms/files/neg/nnc_lps_unit.nnc | Bin 0 -> 887 bytes .../prior-forms/files/neg/nnc_payload_bit.nnc | Bin 0 -> 883 bytes .../prior-forms/files/neg/nnc_pt_float.nnc | Bin 0 -> 883 bytes .../prior-forms/files/neg/nnc_raw_float.nnc | Bin 0 -> 880 bytes .../prior-forms/files/neg/nnc_starts_mps.nnc | Bin 0 -> 879 bytes .../prior-forms/files/neg/nnc_truncated.nnc | Bin 0 -> 882 bytes .../files/neg/st_binding_unknown.safetensors | Bin 0 -> 1767 bytes .../files/neg/st_dtype_unknown.safetensors | Bin 0 -> 1767 bytes .../files/neg/st_duplicate_member.safetensors | Bin 0 -> 1823 bytes .../files/neg/st_extra_member.safetensors | Bin 0 -> 1775 bytes .../files/neg/st_float_tensor.safetensors | Bin 0 -> 1767 bytes .../files/neg/st_header_past_end.safetensors | Bin 0 -> 1767 bytes .../files/neg/st_header_too_large.safetensors | Bin 0 -> 1767 bytes .../files/neg/st_leading_space.safetensors | Bin 0 -> 1767 bytes .../files/neg/st_member_order.safetensors | Bin 0 -> 1767 bytes .../files/neg/st_missing_tensor.safetensors | Bin 0 -> 1702 bytes .../files/neg/st_mult_zero.safetensors | Bin 0 -> 1767 bytes .../files/neg/st_not_json.safetensors | Bin 0 -> 823 bytes .../files/neg/st_offset_gap.safetensors | Bin 0 -> 1768 bytes .../files/neg/st_profile_absent.safetensors | Bin 0 -> 1743 bytes .../files/neg/st_shift_range.safetensors | Bin 0 -> 1767 bytes .../files/neg/st_truncated.safetensors | Bin 0 -> 6 bytes .../files/neg/st_wrong_shape.safetensors | Bin 0 -> 1767 bytes .../prior-forms/files/seeded.gguf | Bin 0 -> 1840 bytes .../prior-forms/files/seeded.nnc | Bin 0 -> 883 bytes .../prior-forms/files/seeded.safetensors | Bin 0 -> 1767 bytes .../prior-forms/files/title.gguf | Bin 0 -> 5792 bytes .../prior-forms/files/title.nnc | Bin 0 -> 4763 bytes .../prior-forms/files/title.safetensors | Bin 0 -> 5813 bytes .../prior-forms/files/wide.gguf | Bin 0 -> 1440 bytes .../prior-forms/files/wide.safetensors | Bin 0 -> 1449 bytes .../prior-forms/files/world_g_s.gguf | Bin 0 -> 936 bytes .../prior-forms/files/world_g_s.nnc | Bin 0 -> 474 bytes .../prior-forms/files/world_g_s.safetensors | Bin 0 -> 928 bytes .../prior-forms/files/world_t.gguf | Bin 0 -> 1392 bytes .../prior-forms/files/world_t.nnc | Bin 0 -> 720 bytes .../prior-forms/files/world_t.safetensors | Bin 0 -> 1368 bytes wai/live-conformance/prior-forms/vectors.json | 1070 +++++++++++++++++ wai/wai-rs/Cargo.toml | 34 + wai/wai-rs/src/bin/wai_prior_forms_vectors.rs | 78 ++ wai/wai-rs/src/codecs/glossary_check.rs | 2 + wai/wai-rs/src/codecs/mod.rs | 34 +- wai/wai-rs/src/codecs/prior_form.rs | 20 +- wai/wai-rs/src/codecs/registry.rs | 4 +- wai/wai-rs/src/lib.rs | 18 + wai/wai-rs/src/prior_wire.rs | 354 ++++++ wai/wai-rs/src/prior_wire/binding.rs | 564 +++++++++ wai/wai-rs/src/prior_wire/cabac.rs | 473 ++++++++ wai/wai-rs/src/prior_wire/corpus.rs | 399 ++++++ wai/wai-rs/src/prior_wire/gguf.rs | 314 +++++ wai/wai-rs/src/prior_wire/nnc.rs | 489 ++++++++ wai/wai-rs/src/prior_wire/safetensors.rs | 187 +++ wai/wai-rs/src/strict_json.rs | 338 ++++++ wai/wai-rs/tests/prior_wire.rs | 84 ++ 83 files changed, 5044 insertions(+), 27 deletions(-) create mode 100644 wai/live-conformance/prior-forms/README.md create mode 100644 wai/live-conformance/prior-forms/files/generic.gguf create mode 100644 wai/live-conformance/prior-forms/files/generic.nnc create mode 100644 wai/live-conformance/prior-forms/files/generic.safetensors create mode 100644 wai/live-conformance/prior-forms/files/hyper_g_s.gguf create mode 100644 wai/live-conformance/prior-forms/files/hyper_g_s.nnc create mode 100644 wai/live-conformance/prior-forms/files/hyper_g_s.safetensors create mode 100644 wai/live-conformance/prior-forms/files/hyper_h_s.gguf create mode 100644 wai/live-conformance/prior-forms/files/hyper_h_s.nnc create mode 100644 wai/live-conformance/prior-forms/files/hyper_h_s.safetensors create mode 100644 wai/live-conformance/prior-forms/files/natives/badtag.whm create mode 100644 wai/live-conformance/prior-forms/files/natives/trailing.wsr create mode 100644 wai/live-conformance/prior-forms/files/natives/wide.whm create mode 100644 wai/live-conformance/prior-forms/files/neg/gg_alignment_explicit.gguf create mode 100644 wai/live-conformance/prior-forms/files/neg/gg_bad_key.gguf create mode 100644 wai/live-conformance/prior-forms/files/neg/gg_bad_magic.gguf create mode 100644 wai/live-conformance/prior-forms/files/neg/gg_duplicate_key.gguf create mode 100644 wai/live-conformance/prior-forms/files/neg/gg_five_dims.gguf create mode 100644 wai/live-conformance/prior-forms/files/neg/gg_misaligned.gguf create mode 100644 wai/live-conformance/prior-forms/files/neg/gg_no_architecture.gguf create mode 100644 wai/live-conformance/prior-forms/files/neg/gg_nonzero_padding.gguf create mode 100644 wai/live-conformance/prior-forms/files/neg/gg_q4_0.gguf create mode 100644 wai/live-conformance/prior-forms/files/neg/gg_trailing_byte.gguf create mode 100644 wai/live-conformance/prior-forms/files/neg/gg_truncated_header.gguf create mode 100644 wai/live-conformance/prior-forms/files/neg/gg_version_2.gguf create mode 100644 wai/live-conformance/prior-forms/files/neg/nnc_alignment_bit.nnc create mode 100644 wai/live-conformance/prior-forms/files/neg/nnc_dq_flag.nnc create mode 100644 wai/live-conformance/prior-forms/files/neg/nnc_long_size.nnc create mode 100644 wai/live-conformance/prior-forms/files/neg/nnc_lps_unit.nnc create mode 100644 wai/live-conformance/prior-forms/files/neg/nnc_payload_bit.nnc create mode 100644 wai/live-conformance/prior-forms/files/neg/nnc_pt_float.nnc create mode 100644 wai/live-conformance/prior-forms/files/neg/nnc_raw_float.nnc create mode 100644 wai/live-conformance/prior-forms/files/neg/nnc_starts_mps.nnc create mode 100644 wai/live-conformance/prior-forms/files/neg/nnc_truncated.nnc create mode 100644 wai/live-conformance/prior-forms/files/neg/st_binding_unknown.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_dtype_unknown.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_duplicate_member.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_extra_member.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_float_tensor.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_header_past_end.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_header_too_large.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_leading_space.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_member_order.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_missing_tensor.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_mult_zero.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_not_json.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_offset_gap.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_profile_absent.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_shift_range.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_truncated.safetensors create mode 100644 wai/live-conformance/prior-forms/files/neg/st_wrong_shape.safetensors create mode 100644 wai/live-conformance/prior-forms/files/seeded.gguf create mode 100644 wai/live-conformance/prior-forms/files/seeded.nnc create mode 100644 wai/live-conformance/prior-forms/files/seeded.safetensors create mode 100644 wai/live-conformance/prior-forms/files/title.gguf create mode 100644 wai/live-conformance/prior-forms/files/title.nnc create mode 100644 wai/live-conformance/prior-forms/files/title.safetensors create mode 100644 wai/live-conformance/prior-forms/files/wide.gguf create mode 100644 wai/live-conformance/prior-forms/files/wide.safetensors create mode 100644 wai/live-conformance/prior-forms/files/world_g_s.gguf create mode 100644 wai/live-conformance/prior-forms/files/world_g_s.nnc create mode 100644 wai/live-conformance/prior-forms/files/world_g_s.safetensors create mode 100644 wai/live-conformance/prior-forms/files/world_t.gguf create mode 100644 wai/live-conformance/prior-forms/files/world_t.nnc create mode 100644 wai/live-conformance/prior-forms/files/world_t.safetensors create mode 100644 wai/live-conformance/prior-forms/vectors.json create mode 100644 wai/wai-rs/src/bin/wai_prior_forms_vectors.rs create mode 100644 wai/wai-rs/src/prior_wire.rs create mode 100644 wai/wai-rs/src/prior_wire/binding.rs create mode 100644 wai/wai-rs/src/prior_wire/cabac.rs create mode 100644 wai/wai-rs/src/prior_wire/corpus.rs create mode 100644 wai/wai-rs/src/prior_wire/gguf.rs create mode 100644 wai/wai-rs/src/prior_wire/nnc.rs create mode 100644 wai/wai-rs/src/prior_wire/safetensors.rs create mode 100644 wai/wai-rs/src/strict_json.rs create mode 100644 wai/wai-rs/tests/prior_wire.rs diff --git a/wai/extensions/prior-carriage.md b/wai/extensions/prior-carriage.md index 426795e20..c4e8bf423 100644 --- a/wai/extensions/prior-carriage.md +++ b/wai/extensions/prior-carriage.md @@ -2,13 +2,20 @@ > Status: Draft. Registers the wire **forms** a pin's digest may cover — > `wai.prior.set`, the listing of a parameter set ([SPEC.md §3.1](../SPEC.md)), -> and ISO/IEC 15938-17 (neural-network coding, "NNC") — registers a **candidate** +> the tensor files `wai.prior.safetensors` and `wai.prior.gguf` (§9), and +> ISO/IEC 15938-17 (neural-network coding, "NNC") — registers a **candidate** > general-tensor payload, and fixes how an in-band signature inside such an -> artifact relates to WAI's digest. The pin itself, its verification and its -> refusal reasons are SPEC.md §3.1. Reference impl: `wai-rs` — +> artifact relates to WAI's digest. §10 defines the integer-quantised profile +> `wai-int/1`, through which a prior carried in a tensor file or in NNC loads, bit +> for bit, into the native artifact an integer kernel reads; §11 the digest rule; §12 +> the NNC reader and writer and where they stop. The pin itself, its verification and +> its refusal reasons are SPEC.md §3.1. Reference impl: `wai-rs` — > `container::{Prior, PinRefusal}`, `pin::{verify_bytes, verify, parse_listing, > write_listing, listing_of_dir}`, `codecs::prior_form`, -> `codecs::{CAP_PRIOR_SET, CAP_PRIOR_NNC, CAP_TENSOR_NNC}`. +> `codecs::{CAP_PRIOR_SET, CAP_PRIOR_SAFETENSORS, CAP_PRIOR_GGUF, CAP_PRIOR_NNC, +> CAP_TENSOR_NNC}`, and `prior_wire` (feature `prior_wire`) for §9 to §14; corpus +> [`live-conformance/prior-forms/`](../live-conformance/prior-forms/), second +> implementation `live-conformance/prior_forms_verify.py`. > Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. ## 1. A prior has a digest, and had no form @@ -69,11 +76,15 @@ operand needs a form both ends can name. | capability | payload | status | |---|---|---| | `wai.prior.set` | the listing of a parameter set of one or more files (SPEC §3.1) | registered; WAI-native; established exact (§5) | +| `wai.prior.safetensors` | a parameter set as one safetensors file: an 8-byte header length, a JSON header naming each tensor's type, shape and bytes, the tensors' bytes (§9.1) | registered; established exact (§5) | +| `wai.prior.gguf` | a parameter set as one GGUF version 3 file: key-value metadata, tensor descriptions, aligned tensor data (§9.2) | registered; established exact (§5) | | `wai.prior.nnc` | the parameters of a trained network as a bitstream conforming to **ISO/IEC 15938-17:2024** (NNC, 2nd edition: parameters and their incremental updates, DeepCABAC entropy coding) | registered; a coded form | | `wai.tensor.nnc` | general tensorial data — split-inference feature maps, 3D/4D scene parameters — coded with NNC | **candidate**: payload format not fixed | -The NNC entries are sink-supplied: `wai-rs` contains no NNC reader or writer. The -standard's reference software is itself standardized, in **ISO/IEC 15938-18**, +`wai-rs` reads and writes the NNC subset of §12 (integer tensors under the +standard's arithmetic coding, raw 32-bit floats) and refuses the rest, quantised +payloads among them, with a named reason; otherwise the NNC entries are +sink-supplied. The standard's reference software is itself standardized, in **ISO/IEC 15938-18**, *Conformance and reference software for compression of neural networks* (1st edition 2023, 2nd edition 2025). That part also supplies the conformance bitstreams and the procedures for testing decoders and bitstreams. Its scope @@ -86,6 +97,11 @@ Registry classification (`codecs::capability_*`): - **`wai.prior.set`**: fidelity tier lossless and replicate class `StandardDefined` — a listing is its own output, and SPEC §3.1 fixes its bytes exactly; licence royalty-free (WAI-native, Apache-2.0 reference). +- **`wai.prior.safetensors`, `wai.prior.gguf`**: fidelity tier lossless and + replicate class `StandardDefined` — a reader that follows §9 hands over each + tensor's bytes as the file holds them, and §9 leaves no choice of which bytes + those are; licence royalty-free (the safetensors layout is published with its + reference implementation under Apache-2.0, GGUF under MIT). For the NNC entries: @@ -173,8 +189,9 @@ reasons: 1. WAI treats royalty-free implementation as a precondition for adoption (SPEC Appendix C). It does not recommend, for every prior, a form whose royalty status it cannot establish. -2. WAI's reference implementation (`wai-rs`) cannot read or write NNC, so it - cannot demonstrate the form end to end. +2. WAI's reference implementation (`wai-rs`) reads and writes only the subset of + NNC that §12 states, written from an open implementation rather than from the + standard's clauses, so it cannot demonstrate the form end to end. 3. Whether conforming NNC decoders reconstruct identical tensors is not established (§5), so the form cannot yet carry WAI's strongest claims. @@ -203,10 +220,15 @@ reasons: published editions and for any later one. - Therefore an envelope with `intent = replicate` MUST NOT rest on a prior whose declared form is not established exact. The registry lists the forms that are - established (`codecs::prior_form::form_reconstruction_is_exact`). It lists one, + established (`codecs::prior_form::form_reconstruction_is_exact`). It lists three. `wai.prior.set`: reading a set through its listing compares digests and hands the decoder each file's bytes unchanged, so every sink that holds the set reads - the same parameters. No coded form is listed. In the reference implementation the rule is part of + the same parameters. `wai.prior.safetensors` and `wai.prior.gguf`: a reader that + follows §9 finds each tensor's bytes where the file's header puts them and hands + them over unchanged, and §9 refuses every file whose header could be read two + ways. No coded form is listed: within the subset of §12 the integers an NNC reader + obtains are a function of the bits (§12.4), but the form also covers quantised + payloads, whose reconstruction is the open question above. In the reference implementation the rule is part of `Prior::is_cross_hardware_exact`, which requires `Prior::form_preserves_exactness` as well as an integer-exact contract. Every `replicate` check reads that predicate, so `container::replicate_is_sound` and `Wai::replicate_is_sound` @@ -303,8 +325,17 @@ WAI's binding sits beside the standard's, never in place of it. `pin-conformance/pin_verify.py`, which shares no code with `wai-rs`, checks it independently; the model-free neural-sink and neural C ABI tests, and the browser crate's tests (natively and under wasm32), are configured to run in CI. -- Not implemented: an NNC reader or writer, and any cross-decoder measurement of - NNC reconstruction. +- `prior_wire` (feature `prior_wire`): `safetensors`, `gguf` and `nnc` read a file + into a `TensorSet` and write one canonically; `cabac` is §12.4's coding; + `binding` holds `wsr1/1` and `whm1/1`; `load` runs §10.3 and `encode` the reverse. + Its corpus is `live-conformance/prior-forms/` (`wai_prior_forms_vectors gen|verify`, + `tests/prior_wire.rs`, which also loads every corpus prior in every form into the + integer kernels and compares the native artifact and, for `wsr1/1`, the + derivation's output). +- Not implemented: the pin verifier (`pin::READABLE_FORMS`) does not read the + tensor-file forms or NNC, so the reference sink refuses a pin in them with + `form_unreadable`, and its decoders read native files only; the NNC payload types + and options §12.1 lists as not read. The files the reference decoders read from a set (informative). A set built for another decoder of the capability may name other files; a decode that needs a @@ -356,3 +387,556 @@ file the set lacks is refused with `set_incomplete`. §6.4 (the amendment's contents and stage, the reference software, and the open implementation whose licence grants no patent rights), §8 item 9, §9 item 5 (the signing flag, resolved by the press release). +- The safetensors layout, as published with its reference implementation (format + description and source, read October 2026; Apache-2.0): the source for §9.1's + layout, its element types and its 100 000 000-byte header limit. +- GGUF, as published with its reference implementation (the format description, + read October 2026; MIT): the source for §9.2's layout, its metadata value types, + its key rule, its alignment default and rule, and the type numbers of §9.2's table. +- An open implementation of ISO/IEC 15938-17 that states compliance with the 2024 + edition (its high-level syntax and its arithmetic coder, read October 2026, under a + BSD licence that grants no patent rights): the source for §12's syntax and for + Appendix A's procedure and tables. Named in the research note above. + +## 9. Tensor files: `wai.prior.safetensors` and `wai.prior.gguf` + +A parameter set that is one artifact of named tensors MAY be pinned as one tensor file. +Its pin's `sha256` is the SHA-256 of the whole file (§11). Two layouts are registered, +each read as this section states; a reader refuses a file that breaks a rule with the +code of §13 named at the rule, and the first rule broken, in the order given, is the +one reported. + +### 9.1 safetensors + +1. The first 8 bytes are `N`, an unsigned 64-bit little-endian integer. A file shorter + than 8 bytes is `header_truncated`. +2. `N` past 100 000 000 is `header_too_large`; `8 + N` past the end of the file is + `header_truncated`. +3. The next `N` bytes are a JSON text (RFC 8259, UTF-8) whose value is an object, with + no member named twice in any object and at most 64 levels of nesting + (`header_malformed`). Whitespace around the value is admitted: the layout pads its + header with spaces. +4. The member `__metadata__`, where present, is an object whose every value is a string + (`header_malformed`). +5. Every other member is a tensor, named by the member's name. Checked in this order: + the name is not empty; the value is an object of exactly the members `dtype`, + `shape` and `data_offsets`; `dtype` is a string (each `tensor_malformed`); `dtype` is + in the table below (`dtype_unknown`); `shape` is a list of integers; `data_offsets` is + a list of two integers `[begin, end]` with `begin` at most `end` (each + `tensor_malformed`). An integer here is written in plain decimal (no sign, fraction, + exponent or leading zero) and is at most 2^64 − 1. +6. A tensor's element count is the product of its `shape` (1 for an empty `shape`); a + product past 2^64 − 1 is `tensor_malformed`. `end − begin` is that count times the + element's size (`offset_mismatch`). +7. Offsets count from the byte after the header. Ordered by `begin`, the tensors cover + those bytes exactly: the first begins at 0, each begins where the one before ends, + and the last ends at the end of the file (`offset_mismatch`). + +Steps 4 to 6 apply to the header's members in the order it writes them, and step 7 once +they all pass. + +A tensor's bytes are its elements in row-major order (the outermost dimension first), +each little-endian. + +| `dtype` | element | bytes | +|---|---|---| +| `BOOL` | boolean | 1 | +| `U8`, `I8` | unsigned, signed integer | 1 | +| `U16`, `I16` | unsigned, signed integer | 2 | +| `F16`, `BF16` | binary16, the 16-bit truncation of binary32 | 2 | +| `U32`, `I32` | unsigned, signed integer | 4 | +| `F32` | binary32 | 4 | +| `U64`, `I64` | unsigned, signed integer | 8 | +| `F64` | binary64 | 8 | +| `F8_E5M2`, `F8_E4M3` | 8-bit floating point | 1 | + +### 9.2 GGUF + +Version 3, little-endian. Every integer below is little-endian; a field due past the end +of the file is `header_truncated`. + +1. Bytes 0 to 3 are `GGUF` (`bad_magic`; a file shorter than 4 bytes is + `header_truncated`). +2. `version`, a u32, is 3 (`version_unsupported`). The big-endian variant of version 3 + reads here as another number and is refused the same way. +3. `tensor_count` and `metadata_kv_count`, each a u64. A count whose least size (12 + bytes a pair, 28 a tensor description) is past the file's length is + `header_truncated`, before anything is reserved for it. +4. A string is a u64 length and that many bytes, UTF-8 (`header_malformed`). +5. Each key-value pair: the key, a string of `.`-separated non-empty segments of `a` to + `z`, `0` to `9` and `_`, at most 65 535 bytes, no key twice (`header_malformed`); + the value type, a u32 from 0 to 12 (`header_malformed`); the value. A boolean (type + 7) is 0 or 1 (`header_malformed`). An array (type 9) is an element type, a u64 + count and the elements; this reader reads one level of array, so an element type of + 9 is `header_malformed` (checked before the count), and a count whose least size is + past the bytes left is `header_truncated`. + `general.alignment`, where present, is a u32 (type 4), non-zero and a multiple of 8 + (`header_malformed`); without it the alignment is 32. +6. Each tensor description: the name, 1 to 64 bytes, no name twice + (`tensor_malformed`); `n_dimensions`, a u32 from 1 to 4, and that many dimensions, + u64 each, innermost first, none 0 (`tensor_malformed`); the type, a u32 from the + table below (`dtype_unknown`); the offset, a u64, a multiple of the alignment + (`offset_mismatch`). A `Q8_0` tensor's innermost dimension is a multiple of 32, and + a count past 2^64 − 1 is malformed (`tensor_malformed`). +7. The tensor data begins at the first multiple of the alignment at or after the end of + the descriptions; the bytes between are zero (`header_malformed`), and the file + ending before the data begins is `header_truncated`. A tensor's bytes are at its + offset from there; no two tensors overlap and none runs past the end of the file + (`offset_mismatch`). + +A tensor's shape, as §10 and §11 use it, is its GGUF dimensions in reverse order +(outermost first); its bytes are its elements in that row-major order. + +| type | number | element | bytes | +|---|---|---|---| +| `F32` | 0 | binary32 | 4 | +| `F16` | 1 | binary16 | 2 | +| `Q8_0` | 8 | blocks of 32 elements: a binary16 scale and 32 signed bytes | 34 per block | +| `I8` | 24 | signed integer | 1 | +| `I16` | 25 | signed integer | 2 | +| `I32` | 26 | signed integer | 4 | +| `I64` | 27 | signed integer | 8 | +| `F64` | 28 | binary64 | 8 | +| `BF16` | 30 | the 16-bit truncation of binary32 | 2 | + +GGUF defines further types, which this reader refuses (`dtype_unknown`): their sizes +are not stated here. The file's string values are its metadata; a value of another type +is read and checked and not kept, so a file that holds one is not canonical (§11.2). + +### 9.3 What a reader obtains + +The metadata, a map from strings to strings, and the tensors, each a name, an element +type, a shape and bytes, in ascending byte order of their names. Two readers that follow +this section obtain the same tensors from the same file: no rule leaves a choice, since a +name twice and overlapping or uncovered bytes are refused. That is why the registry +lists both forms as established exact (§5): reading hands the decoder the bytes, and +what the decoder makes of a floating-point tensor is its own determinism contract. + +## 10. The integer-quantised profile `wai-int/1` + +A prior is in the profile when it is a tensor file, in a form of §9 or the NNC form of +§12, whose every tensor is a signed integer the kernel reads as it is. No scale is +applied on load; any scale the kernel uses (a multiplier, a shift) is itself an integer +tensor; nothing is floating point. The profile is how a network trained in floating +point reaches an integer kernel: whoever quantises it makes the integers once (the +integer decoders' priors round half to even at a fixed scale), and every sink then reads +the same integers, with no rounding on load. + +### 10.1 Metadata and element types + +| form | the metadata, exactly | +|---|---| +| safetensors, NNC | `wai.binding`: a binding of §10.2; `wai.profile`: `wai-int/1` | +| GGUF | the same, and `general.architecture`: `wai`, a key the GGUF layout requires | + +In NNC the metadata travels in the topology unit (§12.3). The element types are `I8`, +`I16`, `I32` and `I64` (GGUF types 24 to 27), little-endian two's complement. NNC +carries every integer tensor as `I32` (§12.1), and the binding's type then bounds its +values. + +### 10.2 Bindings + +A binding maps a native artifact, byte for byte, to named integer tensors and back. + +**`wsr1/1`**: the `WSR1` prior of [exact-upscale](exact-upscale.md) §3.1, with layers +`0` to `n − 1`, `n` from 1 to 8, `` written in plain decimal: + +| tensor | type | shape | values | +|---|---|---|---| +| `act_hi` | `I16` | [1] | 1 to 32767 | +| `layer..weight` | `I8` | [cout, cin, k, k] | any | +| `layer..bias` | `I32` | [cout] | any | +| `layer..mult` | `I32` | [cout] | 1 to 65535 | +| `layer..shift` | `I8` | [1] | 0 to 31 | + +`cin` is 3 in layer 0 and the previous layer's `cout` after it; `k` is 1, 3 or 5; `cout` +is 1 to 64, and 12 in the last layer. The native bytes are written as exact-upscale §3.1 +lays them out from these values. A native artifact that does not read under that section +is `native_malformed`. + +**`whm1/1`**: the `WHM1` model of [integer-payloads](integer-payloads.md) §9 (the magic, +`afrac`, `gf`, `shift`, the layer count, then each layer by its tag), with layers `000` +to `n − 1` written in three digits, `n` at most 1000: + +| tensor | type | shape | values | +|---|---|---|---| +| `header` | `I64` | [3] | `afrac`, `gf`, `shift`: each 0 to 2^32 − 1 | +| `layer.NNN.conv` (tag 1) | `I64` | [5] | in, out, k: each 1 to 2^32 − 1; stride, pad: each 0 to 2^32 − 1 | +| `layer.NNN.convt` (tag 0) | `I64` | [6] | in, out, k as for `conv`; stride, pad, opad: each 0 to 2^32 − 1 | +| `layer.NNN.weight` | `I16` | `conv`: [out, in, k, k]; `convt`: [in, out, k, k] | any | +| `layer.NNN.mult`, `layer.NNN.bias` | `I64` | [out] | any | +| `layer.NNN.igdn` (tag 2) | `I64` | [1] | c: 1 to 2^32 − 1 | +| `layer.NNN.gamma` | `I32` | [c, c] | any | +| `layer.NNN.beta` | `I64` | [c] | any | +| `layer.NNN.relu` (tag 3) | `I8` | [1] | 0 | +| `layer.NNN.leaky` (tag 4) | `I64` | [2] | `slope_q`: any; `slope_frac`: 0 to 2^32 − 1 | + +A layer is the kind its one kind tensor (`conv`, `convt`, `igdn`, `relu` or `leaky`) +names, with the tensors listed under that kind. A `WHM1` layer with a dimension of 0 has +no tensor form (`binding_mismatch`), and more than 1000 layers neither; a native artifact +that does not read as integer-payloads §9 lays it out is `native_malformed`. + +### 10.3 Loading and encoding + +A sink loads a tensor file of the profile in these steps, refusing at the first that +fails: + +1. Read the file in its form (§9, §12), with the form's codes. +2. Require the file canonical (§11): `header_not_canonical`. +3. Require §10.1's metadata exactly, `wai.profile` being `wai-int/1` + (`profile_absent`); the binding registered (`binding_unknown`); every tensor of a + profile integer type, `I32` in NNC (`dtype_not_integer`). +4. The binding's structure, then its names. For `wsr1/1`: `n` is the number of + `layer..weight` tensors (`` in plain decimal); the indices are `0` to `n − 1` + with `n` from 1 to 8, and each weight's first and last dimensions give a `cout` and a + `k` the table admits (`binding_mismatch`). For `whm1/1`: each layer index (three + digits) has exactly one kind tensor and the indices are `000` to `n − 1` + (`binding_mismatch`); then, layer by layer, its kind tensor (but `relu`'s) has the + type and shape the table gives (`binding_mismatch`), and its in, out and k (`conv`, + `convt`) or c (`igdn`) are from 1 to 2^32 − 1 (`value_out_of_range`). Then the + tensors are exactly the names the binding gives for those layers (`binding_mismatch`), + and then each, in the order of step 5, has the type and shape it gives + (`binding_mismatch`). +5. Every value within its type and the binding's range (`value_out_of_range`), tensor by + tensor in this order: for `wsr1/1`, `act_hi`, then layer by layer its `weight`, + `bias`, `mult` and `shift`; for `whm1/1`, `header`, then layer by layer its kind + tensor and, for `conv` and `convt`, `weight`, `mult`, `bias`, for `igdn`, `gamma`, + `beta`. +6. Write the native artifact from the values. + +The kernel reads the native artifact's bytes through the capability's own reader. A +prior loaded this way is the native artifact, byte for byte, so it decodes as the native +artifact decodes. Encoding is the reverse: read the native artifact into tensors, add +§10.1's metadata, and write the file canonically (§11). + +## 11. The digest rule + +A pin in a tensor-file form covers the whole file: the tensors' bytes and the canonical +header that names them. The forms are canonical as stated below, and a sink refuses a +file that is not (§10.3 step 2). So, in the profile, a file is a function of its tensors +(their names, types, shapes and bytes) and its metadata, and one prior has exactly one +file in each form: the digest identifies the tensors, and the tensors the digest. One +prior in three forms has three digests; a pin names one, and §2 forbids resolving it +with another. + +### 11.1 safetensors + +The header is `{`, then `"__metadata__":{` with the metadata's members in ascending byte +order of their names and `}` (omitted when there is no metadata), then the tensors in +ascending byte order of their names, each `"":{"dtype":"","shape":[,…], +"data_offsets":[,]}`, every two members separated by `,`, then `}`: no +whitespace, strings escaped as RFC 8785 §3.2.2.2 escapes them, integers in plain +decimal. Then `0x20` bytes until `8 + N` is a multiple of 8. The tensors' bytes follow in +the same order, contiguous from offset 0. + +### 11.2 GGUF + +Version 3. The metadata pairs are string values (type 8), in ascending byte order of +their keys, with no `general.alignment` (the alignment is 32). The tensor descriptions +are in ascending byte order of their names. The first tensor is at offset 0, and each +later one at the first multiple of 32 after the end of the one before; the bytes +between the header and the data, and between tensors, are zero; nothing follows the last +tensor. + +### 11.3 NNC + +The bitstream §12.3 writes. + +## 12. The NNC reader and writer + +### 12.1 What was read, the subset, and where it stops + +Readable: the public sample of the 2022 edition (its scope, the definition of an NNR +unit, its number conventions and its clause titles) and an open implementation that +states compliance with the 2024 edition (§8): its high-level syntax and its arithmetic +coder. The syntax of §12.2 is the syntax that implementation reads and writes, and +Appendix A is its arithmetic coding. The standard's syntax, decoding and entropy-coding +clauses were not read, so nothing here states a field's semantics beyond what that +implementation does with it, and neither the reader nor the writer was tested against +the conformance bitstreams of ISO/IEC 15938-18. + +The subset: one `NNR_STR`, first; one `NNR_MPS`, before any data unit, with no +performance map and topology elements referenced by name; at most one `NNR_TPL`, after +the MPS, of a storage format that carries its topology as a string (0, 3 or 4) with raw +compression; one `NNR_NDU` per tensor, each carrying one topology element by name, its +input parameters and dimensions in band, and a payload of one of two types: + +- `NNR_PT_INT` (0): integers, with `dq_flag` 0, `scan_order` 0 and the unary length in + band, coded as Appendix A codes them; read back as `I32`. +- `NNR_PT_RAW_FLOAT` (2): 32-bit floats, little-endian, as they are; read back as + `F32`. + +Everything else is refused with `nnc_not_read`, naming what was met: the units +`NNR_LPS`, `NNR_QNT` and `NNR_AGG`; a partial data counter; a performance map; topology +elements referenced by index; another topology storage format or a compressed topology; +the payload types `NNR_PT_FLOAT` (quantised parameters) and `NNR_PT_BLOCK`; several +topology elements in one unit; a decompressed data format other than the payload's +own; input parameters, dimensions or the unary length out of band; compressed parameter +types other than 0; `dq_flag` 1 (dependent quantisation); a `scan_order` other than 0 +(block scans with entry points); a tensor of more than 2^28 elements. + +**Where it stops, and why.** A quantised payload reconstructs each parameter from a +quantisation index and a step size, and that arithmetic, with whatever tolerance +conformance allows it, is in the text that was not read; dependent quantisation and +block scans add a state machine and entry points an integer prior does not need. Within +the subset, decoding is integer arithmetic on fixed widths from the first bit to the +last (Appendix A), so the integers a reader obtains are a function of the bits, and a +prior in the profile round-trips exactly (§14). + +### 12.2 Reading + +Bits are read most significant first: u(n) is an n-bit unsigned integer, i(n) an n-bit +two's complement integer, ue(k) the order-k exponential-Golomb code (zero bits, each +adding 2^k to the value and raising k by one, until a one bit, then k more bits added to +the value), and a string a zero-terminated UTF-8 byte sequence that begins on a byte +(`nnc_malformed` otherwise). A byte alignment is a 1 bit, then 0 bits to the byte +boundary (`nnc_malformed`). + +Units are read in order until the bitstream ends; an empty bitstream is +`nnc_truncated`. Each unit: `nnr_unit_size_flag` u(1), then `nnr_unit_size`, u(15) or, +with the flag, u(31), counting the whole unit, its size field included (a unit under 3 +bytes or past the end of the bitstream: `nnc_truncated`); `nnr_unit_type` u(6); +`independently_decodable_flag` u(1); `partial_data_counter_present_flag` u(1). The first +unit is `NNR_STR` (`nnc_malformed`), and a partial data counter is not read +(`nnc_not_read`), in that order. A field that runs past its unit's end, and a string +with no zero byte before it, are `nnc_truncated`; an exponential-Golomb code longer than +40 bits is `nnc_malformed`. Then by type, fields in this order, each check where its +field is read: + +- `NNR_STR` (0): only the first unit (`nnc_malformed`); `general_profile_idc` u(8). +- `NNR_MPS` (1), once and before any data unit (`nnc_malformed`): + `topology_carriage_flag`, four performance-map flags, `mps_quantization_method_flags` + u(3), `mps_topology_indexed_reference_flag`, seven reserved bits; the reserved bits + are zero (`nnc_malformed`), then the performance-map flags are zero and then the + indexed-reference flag is (each `nnc_not_read`); with quantisation flags, + `mps_qp_density` u(3) and `mps_quantization_parameter` i(13); a byte alignment. +- `NNR_TPL` (3), once and after the MPS (`nnc_malformed`): `topology_storage_format` + u(8), `topology_compression_format` u(8), the topology as a string. In storage format + 0, a topology that is a JSON object of string values is the file's metadata (§10.1). +- `NNR_NDU` (5), after the MPS (`nnc_malformed`): `nnr_compressed_data_unit_payload_type` + u(5), `nnr_multiple_topology_elements_present_flag` u(1), + `nnr_decompressed_data_format_present_flag` u(1), `input_parameters_present_flag` + u(1), after which the payload type is 0 or 2 and then the multiple-elements flag is 0 + (each `nnc_not_read`); the topology element's name, a string, not empty + (`nnc_malformed`); for `NNR_PT_INT`, `dq_flag` u(1), 0 (`nnc_not_read`); + `nnr_decompressed_data_format` u(7) where present, 0 for `NNR_PT_INT` and 1 for + `NNR_PT_RAW_FLOAT` (`nnc_not_read`); the input parameters are in band + (`nnc_not_read`); `tensor_dimensions_flag` u(1), `cabac_unary_length_flag` u(1), + `compressed_parameter_types` u(4), which is 0, and then the dimensions are in band + (each `nnc_not_read`); the dimension count ue(1), from 1 to 16, and each dimension + ue(7), outermost first, whose product is from 1 to 2^64 − 1 (each `nnc_malformed`) and + at most 2^28 (`nnc_not_read`); `cabac_unary_length_minus1` u(8) where flagged; with + more than one dimension, `scan_order` u(4), 0 (`nnc_not_read`); a byte alignment; then + the payload, to the unit's end. An integer payload with the unary length out of band is + `nnc_not_read`. + +A unit other than a data unit ends exactly where its fields end, and a data unit's +payload fills the rest of it: four bytes per element of raw floats, and an integer +payload whose decoding (Appendix A) ends exactly at the unit's end (`nnc_malformed`). +Two data units carrying one name are `nnc_malformed`, checked once every unit has been +read. + +### 12.3 Writing + +`NNR_STR` with profile 0. `NNR_MPS` with `topology_carriage_flag` 1 when there is +metadata and every other field 0. With metadata, `NNR_TPL` of storage format 0 and +compression 0, its topology the metadata as a JSON object, keys in ascending byte order, +no whitespace, strings escaped as RFC 8785 §3.2.2.2 escapes them. Then one data unit per +tensor, in ascending byte order of names: an integer tensor as `NNR_PT_INT`, a binary32 +tensor as `NNR_PT_RAW_FLOAT`; `independently_decodable_flag` 1, no partial data counter, +one topology element, no decompressed data format, input parameters in band with both +flags 1, `compressed_parameter_types` 0, `cabac_unary_length_minus1` 10, `scan_order` 0 +where present, `dq_flag` 0. Each unit takes the 15-bit size when the whole unit is under +32 768 bytes, else the 31-bit size. An integer past ±(2^31 − 1) has no `NNR_PT_INT` form +(`value_out_of_range`). + +## 13. Refusals + +| code | where | +|---|---| +| `header_truncated` | §9.1 steps 1 and 2; §9.2: a field, a count or the data's start past the end | +| `header_too_large` | §9.1 step 2 | +| `header_malformed` | §9.1 steps 3 and 4; §9.2 steps 4, 5 and 7 | +| `bad_magic` | §9.2 step 1 | +| `version_unsupported` | §9.2 step 2 | +| `tensor_malformed` | §9.1 steps 5 and 6; §9.2 step 6 | +| `dtype_unknown` | §9.1 step 5; §9.2 step 6; a tensor of a type a writer's form has no name for | +| `offset_mismatch` | §9.1 steps 6 and 7; §9.2 steps 6 and 7 | +| `header_not_canonical` | §10.3 step 2: the file reads and is not its canonical form (§11) | +| `nnc_truncated` | §12.2: a unit past the end; an integer payload that ends inside its unit (Appendix A) | +| `nnc_malformed` | §12.2, Appendix A | +| `nnc_not_read` | §12.1: outside the subset, with what was met | +| `profile_absent` | §10.3 step 3 | +| `binding_unknown` | §10.3 step 3 | +| `dtype_not_integer` | §10.3 step 3 | +| `binding_mismatch` | §10.3 step 4; §10.2: a native artifact with no tensor form | +| `value_out_of_range` | §10.3 steps 4 and 5; §12.3: an integer NNC cannot carry | +| `native_malformed` | §10.2: a native artifact that does not read under its own format | + +## 14. Conformance corpus + +[`live-conformance/prior-forms/`](../live-conformance/prior-forms/) holds, for seven +native artifacts other corpora commit, each pinned by SHA-256 (two trained `WSR1` +priors and a seeded one of the exact-upscale corpus, four generated `WHM1` models of the +integer-operators corpus), the canonical file in each of the three forms; native +artifacts that do not read or that NNC cannot carry; and negative controls, each refused +with its code. A vector is an *encode* (a native artifact to a file, or a refusal) or a +*load* (§10.3 over a file: the binding and the native artifact, or a refusal), and a +verifier conforms when it reaches every vector's outcome, its file bytes and its native +bytes. `wai_prior_forms_vectors gen|verify` writes and checks it, `tests/prior_wire.rs` +regenerates it and loads every prior in every form into the integer kernels, and +`live-conformance/prior_forms_verify.py`, written from this text and sharing no code +with `wai-rs`, is its second implementation. + +## Appendix A — The integer payload's arithmetic coding (normative) + +The integers of an `NNR_PT_INT` payload are binarised and coded with context-adaptive +binary arithmetic coding. Every quantity is an integer; `>>` is an arithmetic shift +(division by a power of two rounding toward negative infinity), and `lz(x)` is the number +of leading zero bits of `x` as a 32-bit word. + +**Contexts.** With `G = cabac_unary_length_minus1 + 1` greater-than flags there are +`L = 24 + 3 + 2G + 32 + 4` contexts, numbered from 0: 0 to 23 for significance (three for +each of eight quantiser states; with `dq_flag` 0 only 0 to 2 are used), 24 to 26 for the +sign, `27` to `26 + 2G` for the greater-than flags (two per flag, positive then negative), +`27 + 2G` to `58 + 2G` for the remainder's prefix, and four more; the one numbered +`L − 2` codes the initialisations. A context holds `s0` (a signed 8-bit integer), `s01` +(a signed 16-bit integer) and two shifts `h0`, `h1`. Initialisation `j` (0 to 8), the row +`[b0, b1, v01, v0]` of table A.3, sets `s0 = v0`, `s01 = v01`, `h0 = b0 + 4`, +`h1 = b1`. A context's most probable symbol is 1 when `s01 ≥ 0`, else 0. After a bin +`b`, with `σ = +1` when `b = 1` and `−1` otherwise: + +``` +s1 = s01 − 16·s0 +s0 = wrap8 ( s0 + σ·( C[16 + ((σ·s0) >> 3)] >> h0 ) ) +s1 = s1 + σ·( C[16 + ((σ·s1) >> 7)] >> h1 ) +s01 = wrap16( s1 + 16·s0 ) +``` + +where `wrap8` and `wrap16` keep the low 8 and 16 bits as two's complement and `C` is +table A.2 (an index outside it is `nnc_malformed`). A context's least-probable range for +a coder range `R` is `T[|s01 >> 7| + (R & 0xE0)]`, `T` being table A.1 (an index +outside it is `nnc_malformed`). + +**Decoding.** The coder starts with `range = 510`, `value = 256·B0 + B1` from the +payload's first two bytes (fewer: `nnc_truncated`), `need = −8`, and two bytes read. Reading +a byte past the payload is `nnc_truncated`. + +``` +bin(ctx): r = T(ctx, range); m = range − r + if value < m·128: b = mps(ctx); range = m + else: value −= m·128; range = r; b = 1 − mps(ctx) + n = lz(range) − 23; range <<= n; value <<= n; need += n + if need ≥ 0: value += readbyte() << need; need −= 8 + update(ctx, b); return b +bypass(): value = 2·value; need += 1 + if need ≥ 0: value += readbyte(); need = −8 + if value ≥ range·128: value −= range·128; return 1 + return 0 +bypass(k): k bypass bins, the first the most significant bit of a k-bit number +finish(): range −= 2 + value ≥ range·128, else nnc_malformed (the terminating bin) + ((last byte read) << (8 + need)) & 0xFF = 0x80, else nnc_malformed + the payload's length is the number of bytes read +``` + +**The payload.** First the initialisations: for each context `i` from 0 to `L − 5`, +skipping 3 to 23, decode a bin with context `L − 2`; when it is 1, `j = 1 + bypass(3)`, +else `j = 0`; initialise context `i` with `j` (context `L − 2` keeps adapting). Then each +of the tensor's elements in row-major order, `p` being the previous element's value (0 +before the first) and `c(p)` 0, 1 or 2 as `p` is zero, negative or positive: + +``` +if bin(c(p)) = 0: value 0 +else: + v = 1; s = bin(24 + c(p)) + g = bin(27 + s); k = 1 + while g = 1 and k < G: v += 1; g = bin(27 + s + 2k); k += 1 + if g = 1: + v += 1; q = 27 + 2G; rem = 0 + if bin(q) = 1: + rem = 1; q += 1; e = 1 + loop: q ≥ 59 + 2G → nnc_malformed + if bin(q) = 0: stop + rem += 2^e; q += 1; e += 1 + rem += bypass(e) + v += rem + v past 2^31 − 1 → nnc_malformed + value v, or −v when s = 1 +``` + +then `finish()`. + +**Encoding.** The writer's coder starts with `low = 0` (a 32-bit word: every addition and +left shift below keeps its low 32 bits), `range = 510`, `left = 23`, a buffered byte +`B = 0` and `n = 0` bytes buffered. + +``` +out(): lead = low >> (24 − left); left += 8; low &= 0xFFFFFFFF >> left + if lead = 0xFF: n += 1 + else if n > 0: c = lead >> 8; emit (B + c) mod 256; B = lead mod 256 + emit (0xFF + c) mod 256, n − 1 times; n = 1 + else: n = 1; B = lead +bin(b, ctx): r = T(ctx, range); range −= r + if b = mps(ctx): if range < 256: range = 2·range; low = 2·low; left −= 1 + else: k = lz(r) − 23; low += range; range = r << k; low <<= k; left −= k + if left < 12: out() + update(ctx, b) +bypass(b): low = 2·low; if b = 1: low += range + left −= 1; if left < 12: out() +finish(): range −= 2; low += range; low <<= 7; range = 256; left −= 7 + if left < 12: out() + if low >> (32 − left) ≠ 0: emit (B + 1) mod 256; emit 0x00, n − 1 times + low −= 2^(32 − left) + else: if n > 0: emit B + emit 0xFF, n − 1 times + low = 2·(low >> 8) + 1; left −= 1; low <<= 32 − (24 − left) + for i = 0, 8, 16, … while i < 24 − left: emit (low >> (24 − i)) & 0xFF +``` + +The writer codes every initialisation as 0: for each context `i` from 0 to `L − 5`, +skipping 3 to 23, a bin 0 with context `L − 2`. Then each value `v`, `p` as above: + +``` +bin(v ≠ 0, c(p)) +if v ≠ 0: + s = 1 if v < 0 else 0; bin(s, 24 + c(p)) + r = |v| − 1; g = (r > 0); bin(g, 27 + s); k = 1 + while g = 1 and k < G: r −= 1; g = (r > 0); bin(g, 27 + s + 2k); k += 1 + if g = 1: + r −= 1; q = 27 + 2G + if r = 0: bin(0, q) + else: bin(1, q); base = 1; e = 1; q += 1 + while r > base + 2^e − 1: bin(1, q); base += 2^e; q += 1; e += 1 + bin(0, q); r − base as e bypass bins, most significant first +``` + +then `finish()`. + +**Table A.1** — `T`, 256 entries, row `x` holding entries `32x` to `32x + 31`: + +``` +128 112 97 84 74 65 57 50 45 39 34 30 27 23 20 18 15 14 12 11 10 9 7 7 5 5 4 4 3 3 2 2 +142 125 108 93 82 72 63 56 50 43 38 33 30 26 22 20 17 16 13 12 11 10 8 8 6 6 5 5 3 3 2 2 +156 137 119 103 90 79 70 61 55 48 42 37 33 28 24 22 19 17 15 13 12 11 9 9 6 6 5 5 4 4 2 2 +171 150 130 112 99 87 76 67 60 52 46 40 36 31 27 24 21 19 16 15 13 12 10 10 7 7 6 6 4 4 3 3 +185 162 141 121 107 94 82 73 65 56 50 43 39 34 29 26 22 21 17 16 14 13 11 11 8 8 6 6 4 4 3 3 +199 175 152 131 115 101 89 78 70 61 54 47 42 36 31 28 24 22 19 17 15 14 12 12 8 8 7 7 5 5 3 3 +213 187 163 140 123 108 95 84 75 65 58 50 45 39 33 30 26 24 20 18 16 15 13 13 9 9 7 7 5 5 3 3 +228 200 174 150 132 116 102 90 80 70 62 54 48 42 36 32 28 26 22 20 18 16 14 14 10 10 8 8 6 6 4 4 +``` + +**Table A.2** — `C`, 32 entries: + +``` +2512 2288 2064 1840 1616 1392 1168 944 720 560 464 368 272 208 144 80 + 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64 0 +``` + +**Table A.3** — the nine initialisations `[b0, b1, v01, v0]`: + +``` +0: [1, 4, 0, 0] 1: [1, 4, -1310, -41] 2: [1, 4, 3039, 95] +3: [0, 5, 0, 0] 4: [2, 6, 962, 30] 5: [2, 6, 3039, 95] +6: [2, 6, -673, -21] 7: [3, 5, 0, 0] 8: [3, 5, 962, 30] +``` diff --git a/wai/live-conformance/prior-forms/README.md b/wai/live-conformance/prior-forms/README.md new file mode 100644 index 000000000..dc8613de9 --- /dev/null +++ b/wai/live-conformance/prior-forms/README.md @@ -0,0 +1 @@ +# live-conformance/prior-forms diff --git a/wai/live-conformance/prior-forms/files/generic.gguf b/wai/live-conformance/prior-forms/files/generic.gguf new file mode 100644 index 0000000000000000000000000000000000000000..3731c9ec4caeda8cd99df96b8ffc259b9efb1669 GIT binary patch literal 5792 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 UMGSsfsN1wxR>9R>WmC2P2Wf(56#xJL literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/hyper_g_s.gguf b/wai/live-conformance/prior-forms/files/hyper_g_s.gguf new file mode 100644 index 0000000000000000000000000000000000000000..f915171948d1da99f78788a107d3603501cce549 GIT binary patch literal 1394 zcmZ{ku}>616vn?3C3q;J!2)frv9NHt6+>yR0{sJ|AMVDz_4f8;UAUY!RI~(4)JS58 zCfXRa$HE$o#sX_&V>B_Sv7qsuGk5O^H_N?b^3BZq&3p6aZFXkn+VlW`<7Qa1b8BN= zL7-!usK`@Exf-UroGv7K$oPb7s&JT5Gxg@exDv*JB?e`?TK6aX{m{*%aXpMWVU7x< z!V6)XPWW=Fs#QfNIpda0XBo9J!t+SAbmA3@MUz8$5Tj`re#QRi-4nVazSb_X1U~D;wBN zcE;?L_*gbc7*t~GWB~Rdmii>Q=tWx9TGlHSSbT$VH~8d%O?lIowoS3lBW75$u|M~v z59&#QujTe+(%b6^2ZX5mT(&1ADG#t;vbB6yaEw*t8OPabQ^#7Y;+IHn8rhd?h{`$9 zSDd)W0oElD16MJPby~$^D&Y?1X_*Fd=V$=8v5W6?nnv-QX6XjLQjK0v9u>MmU+@EK zDBv;;^3=nwzW>?d z#uKx=Z^pK{cVNbvDg3D^cEsqc(M!E}X62Nd@vrbhx_uFFlOMLp`am445?_?D|DT70 gXO6y>L7=A?pz z6Ek%)^Gbj+wG0g6VvHH7i7BZ?42cI@9eNprLs%OP)-b4vG3F#zrWWZL7#ILeOe}^; zJ21{+U|=}dz@R32fI%KvRdRk_SqVepVOF3i5=-0d8yM7)rE^PjfU*!f92m5wGBEH= zVPKGr+rTgxSy_2%W_m^mL&AlCf`UXxpnDwnCor(Dd7$dSz#hxO(Eg2a3j^C01|bCt zmIDg@We%%5<7AB-y{C6CJcttPk#%OKr{pmt9soswxE+HcLdr-lCp9s<66gR>zzRDsEEHh;&!CD> zV2l<&8ypxIpD{4Z{LdhZtOyi642g$87Kk36^Z+?>jP+0>M_-VEfpMM(19KZUgEg`l p@W?4BFi-?X4s#^~^CSi*dj$r|1a>vXI1NUD3k(c>0TMeO006t3%7Xv^ literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/hyper_g_s.safetensors b/wai/live-conformance/prior-forms/files/hyper_g_s.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..495106e0d38905550933fe4475b2c38fc8842240 GIT binary patch literal 1410 zcmb7^!E4iS6vtm3$mmp5^x&-oJP1Ey@=F%G9fl`y|G+LX%5Nr3W0Q1iqotNzy^d|5 z;=qG~UPQgmzhH{uVdB|~D9BVi>|&oU%`c-_8>K1XoBrPKefoXx_tH-j(7(D$r4l%i zutg*)6=k-ow1uZGd7(#EhlQxlE%2XNU6T)I89a4H2O(FfiV!=WM(T!+Va1-h_; zckyW9JWjwJ*oSX;7Ei)+T)>;~1?%_%i(un5{0!e=14=N5lLa5vU1{Lv?jGrJ~AT=o_ z?ZTQYFzwH_EU+#0L4rKkpXSN%K|ka+ct4dqy*-}jp5%Xpb5O*e@EbSrSMMII<6q1H Bis=9V literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/hyper_h_s.gguf b/wai/live-conformance/prior-forms/files/hyper_h_s.gguf new file mode 100644 index 0000000000000000000000000000000000000000..f13be7a3c695baf3374b226cc14009705b127e70 GIT binary patch literal 1440 zcmah|T}V_x6#mBFvd{`2Qpff zQle}n=@SY8D`Z9dL61LDUS&tE@@On#&5(BnSM9fFX{a#yBpOT0ha}ZqCgtOje^O1G}EDT;{}q$h|_})>_ZxcG2k?_nD@DXrG0Y@htNj0Nh32dJ*93o~ny^pW(~Qb!!=K^R@Mp9;;jrd!o&M9awBzpN31l4a zvKC+P9r?1;^;pDK6y>L7=A?pz z6Ek%)^Gbj+wG0g6VvHH7i7BZ?42cI@9eNprLs%OP)-Y&^G3F#zrWWZL7#ILeOe|(d zJj4o;c3?clz`&kr$G|r00E6`U4-B%%DwFf`%77|C21(2~=f|LfESX!HQvx@JLE|d} z1FIPW1H;o_4lry*R$HE$nVwO?kZ@r_KtZA-$X+%V2F4_L2jL$LJR1|(J2+IC z7|!W1GTmUABEV$I!R(YFH-qUG(-y|ZJ{+QdxWyazy;-~#=qwOhdo9qU z@CpW|nFkm+J?j}Ik(DCCZ~{0C85sDs9pL!0Kr;6L>%kighZz_T>aesdFk0MTz{MaP zz*ntgb3kx1gW8*xsTUcQXRuc?bw9~a@wn6E#NZQ9c)P`sp_GAd0#i=!35DOzx(x#2 zrlQG*UU6hy_D|aSeW&KZ%Xc=J&v6jibv3aoBg<}U;C0?R+G!qf>u1fdy<7kNqW;zm zJ1#wrb1ty{y6j*2;iTJ^{~NSsZ(7F_^h&8~U(;4C*^l?%9(=5Djkl=1JJ})iqbBpW zjvZTA+~!0~yq@H+;>B6x=xKk;F74ocuWKWkkhJhh$qO|3{De-cLFqO3HAw1S(>zdt*ZKqKjABNS#=oj=hT9mm8>Bg81eg|x85 zEXax$VG?0wZHkP73PI8cDkGwW8_RT|-nTQ(Eu2w_x|{Ew?>paf-tRpeL}TG=GL)89 zI|6Ej8c<714Y^H*2Gx^U=J8f|ye>nop}}2k+GR2*3+Woa&*`ah1kd}W4hDZ^%0av~Vj^;LnD8O!L%u`4EXcDRrRhsWg($C&Jk z9I+~9#iA_ZizF%LEc0TTDbw$$st=Qty$EGmI+X-Sh-ERv{2%^iNrgsRI3?(C_*`K( zZC@%zA{2`)Ld-v#ED4GwJ3`FgUA8XovLq??ph3-B5<<@aiF#bE$6*or>6_!i=hWjf zJs+j-i_xS0oS>Kg!~z|h7aQ96LT~6y3ax!e7M1XDkg52JSBMic+7QM$f;E`NnAXJt zKH?spWjbg0S&Lx@9%6^c5K_6wMjNlv$2eZ*EzI+s_J(;p2n{(rz!c1BAJEAOwy~W~ z^mByi;x#%j1|tfX#!c+zm^Lf3_gRarqMH!ER(P-rvWN;dZVgQxgCML9xS`NzP=w*oI9kj@k`dN%g zKIdBTU3UW+5%6 z#4N~)7GV-$Wo?R#f(k)&BB+dr7PGNzT+H}RcbxISeTVlR+sY7kDBs+Ay5cB3g_5ruZIMbhzX)?|8{+iJqyO-~6S~G__Et zLBhP2XR}th?YhMrGaTWc4|9q}B}s6Vhq|Ei?4zGFC#lsa-FmN(*4dn(n^u?ZC_t|+ zQLhYDZj^RyaB2U|H*wrN9wVqn^w^{k#hBGwt#E(*D;*}zw4&N5T(u@tudJrj#tp8S znlBosh3h=FY|q-hqfsetu5r`4`Tv%Z%n3~Mjt?xMb1&xH0csJnEU!rhk8 zQKGiZAq`QX(`wT_+r>e)s)v`{=ZeM%xtMay`AjkSot29LvnC&&;ZS*jT`}o13x(vf K#s8m$Z~g=55c+ul literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/natives/trailing.wsr b/wai/live-conformance/prior-forms/files/natives/trailing.wsr new file mode 100644 index 0000000000000000000000000000000000000000..48bf961fb5964072e1288845785f142937baf556 GIT binary patch literal 771 zcmW+!IgU^<47DA{4kN@SA&}Tch-DgtxCIpjAW?A^D! zmrozxVM}Ni0*6S!IBE7}xrZDkYv{`RDEG5FG-<=4S?kNY$0paPa)!ex&Ux$vK_D+1 ziw4tD+u=-sgJz2}I;&(hZPW;-9MB;myz1dTq%&gxD8x)ZWZk&-h?+Hz_Q{d?z2b9b zeyw~703Q(G3f{m|xDOBD5#R-bHK@tLkdfoem7EPG=ijQmU46acSM}b3XYdl9K)ZDl z#+Yb6cJw@clsr2flNQ+QsLSzT8s5m9vS3IUjy=hphx>+EZPs||7TA(r%oI+;iQ3d` zA>f{iv3py_fyA~rP0~_UOre_RD*_05=vqx>JFW)YEDcuph>4z7oHqgpf!-p})`GOE zhAgZKFm=hGnwVbV&008SU{9oN9Xre>RfmdE^CUv&HT$?onj;O!X@$|ZUYDHZ#egyC z8Ve@00wsp0yOGR|Fg$tBJ@~ZgQpG2GaVJ!+P%_PuWA)-d&dG?UEhBy|ET{@ dDqq$4McqAy%hLK0+=I99svPsWZXd#3fPawURviEU literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/natives/wide.whm b/wai/live-conformance/prior-forms/files/natives/wide.whm new file mode 100644 index 0000000000000000000000000000000000000000..243b2bf49574ac26c4017e19b982f65c04dfa971 GIT binary patch literal 627 zcmXw$O-R&H6vcnfanvM?!bMr>rkM&_SVHB}B$%S1kIg`f=mQ*bkr+voVT4Q+F$-xi zC1ycZv8`Z9r;~G&dzj#lXoAaYgX^dseGUy@=aG|~p1RF9{g_=K2_+Uz>Fc3cev;>a2zxOmguQE`ORM%Oj9#; z>L#VcnN09gn zR{Tdls=KJRZ=10-BU(DHy4|O%>r{37{{8R1QULHOqYb?^@YZP0=gawOwwTOTXBWeI zeztzRntNVrd)jz18{XV{u%j-9^W~%Ya@}GbF>rJsL++!;i}fM*bmaCj^|+T8!@~`^C-E*5AB^PJM%&3x=EM2LrYgO9hqkBJqC6dU z@25B2N$H(xQclO?54l~+Rqlh4yDw!rxmL>4k-RTuI{8mZ7v61`(2s0V{umec(>vKo z>GMs>UpH{Ol&f5j+ZydlIk{fS=}6v}a&kq=VK=&je&nXNW0dW|0Mwlutgb}AGrlFF z@7W$P@0(sL_U|(8F+OIz&3K3L0i%yw-MPVLDIK6D8B9b<-exUPiQ;t1PMe$zD62eH zh04TYN=>r3%5)7`lP#Ju<|?5I9nnYWwTuo^Em~ILWl%MHqn#yEPDv$2uo;}U*6PRy z8*h!)q5?Rmfl8KWP;(xYV}v>s%MZcySH&4W9di@fqWzYu5j-KB$26 zwHm0B&moc~1wrrH_BF?B|1q~XkLTQ@-#Cvu{QW2Uf3tnY^X=Tb{QW7fvAZ`uO7YvM7L2M@V2nV>Y#^??0|+i!FF7I= zzMg>-uAvG;LMAlAKv0%>!imc9Y^^SA?w5$tsv@KZ3dkBuavMsHg)4wsy67VWP%O%# z$flsQAqnt>bPCDqtU|F&&SbcDR`R`f5*-1mXt)M8XR4KNx30cs@Y-h24@IPmj&BU- f>4I|7Nw7&*eq#h7MNiV%NHNK#4EU~Fn-K9|-cz-k literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/gg_bad_key.gguf b/wai/live-conformance/prior-forms/files/neg/gg_bad_key.gguf new file mode 100644 index 0000000000000000000000000000000000000000..a9becee2f17522db316b34084a2b4937f58e234b GIT binary patch literal 1840 zcmah}OK+St6!qJtyb*{}UQtD%n@*Vgf~+X3u0UNOI)rKRNoiV5riy?~7pz%#**{XL z|Dvn@4?nJLjwhc(W;79&&iH=z@%26TUeDdTKmQ;ZWA6fOG`o$D&nI>`KbbFP$CKIO z;AmLR50+0CbBDdoJDUw}kJS6KVe;E>^80Xd*cfJ9K094Zcc+~M{^ZHx@x$SG<>swU znhke`ljZKTpPC&ke?1yFwj0{`?0j1NzBAg#vp?pG$@jQ+cG`IT^bOcMBWIKQ=;`rt zle<51dltIi)1%?xirj;^4&sB6{N6Zs^4WYiJX$KO_T+5NJ-p`S*?4+Ayy=CM=BJG& zWq-VWliQ_S<~|s?>r$qZtx}$iMVscN#SiBb`)a23b};gkrLV#!Jr?u3^mIjyXzRaz;e;NB!=oiRoQT&$hZO7X&& zWyRN=1Zh%;qrjI>R{JXgAD7=C7w;o48hE~i`T^Fy25tdgZrT68{E%db*C5XV2L(VvLpCca<7evSEWthak_` z$XU6ZBl9>$Qh6H7AO9+f2Sxs{eEaK@t#it8)`sltV#pemV4)r8`WfWI6{!CTYC zh9IqpnFq;~q)IH5?26~P61qy_aTLR}EQM1DtPdIVg2;kt4W zNpsD-G~jW|3;dZDKuXx)0`p8llp&sUCSiQ!%;l=0 zk4`1&vjI6(jocZ5zPEyGWh8?}4oph56m+*Lt|aD^Ne&N%$3zw#gE+01R#?GIR0VIW VG9QDh!kSP+6ipdJuNxDI=wAn{u6h6f literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/gg_bad_magic.gguf b/wai/live-conformance/prior-forms/files/neg/gg_bad_magic.gguf new file mode 100644 index 0000000000000000000000000000000000000000..80c4afbe562b660f05dcbfaabda4d5dea41070ed GIT binary patch literal 1840 zcmah}%Wh;f5cS(;cq0&Rh!Bt2OhV@ivI{F#utZD~dXknx26cKg0yeW?&6*`Yg2Z32 z;y?T-yEyJ#hjc3umQK5_eO#_nRqo^CuaC75;zgj3Zujx})g(?A7mL;We1E<=Ia{t5 zC+jDxMZn(RJ)JL~pQteRzgu2>zq~l@3^$f9SF?lJP1296$B&lh8#m7l(tP<~d9gm2 zjZ^cJ^|xnBiS3R)-oBbPzb{Po`TVEFYX5UwJHPC_arzzDdlP4u`@@s-^)B~l;*Kl~ zzn5prM;r18;w=y#PUM%Sd61tjmZxWHgVm9o-Fbx9y*!&vZ-+O#meT$7(WM+s*Y9$N zl$+d#6L(w6Z2wj%&nEJ=l-d4&Qikv`4+-PSF6EDDb344lYbj&BOZn>#?vQemD-;f3 zY)d)3UCPl!-j;HBL&|P7hJUorDHnD;>YawH4(ec%D` z0q_R!Ch#sW#=T+OF&Cx0Aj2x!M4W>wMv<1)n_RrHC0n7L4%9Sho2soXtLhu*Mij%Y z8ZK0t5>1*!A*oz_DZY5ab9kcdYX(gj8!UyM^Nx0YsXN{3f2rpXXS`=9_ zh@-(*EZ)XD0-u)uK`vfKUM%qYE!0=A_95^I@cu3P|Cb-q0`WQ{xN#^kv0*Kuxh~rm z@Y(-l?jerfP@|s_$4B`76ZYSjf5m#g_ddQq#vaVA@gBbRfF573+iSo9@F~!1`x(&B z_vU#O*d3!Cch|>v2vJfExmhl?cvab|LQXfSsrc$j4dh$ZimGB1$~BT9M^&o{wPq1< zjI@b9JCPk%)E<>28Ku{rC`lo6Oh($O6PYHJp=6jG6T8%+Q}9<)Yj|t6`INMGxyq!4 z(ZnQDtD!|%nb1`lkE1zZb!(DR5=>wVM&7*;(I*!aJaR>QMg-hqla{5*WTh2RJMg5J z4gOqfK5qI;CgpUMDhQ4QjWzoXrKE}skWoH`B%;s|tVVzBP^YEUHiCPr>fld?|<`!7DBuX^wI4;-n^K^qvhk}dT};etWQo? zo8`&o>3SKkH+at$s~0CK%-N6YCl6O=TZS9U=j-|5{07nsgS1%PTRq+!&c~_6$>zJ$ zmBe;OAMajF+uxTa`)u*kay|Qe_Wf#c-g)End$12C&OY~tr)Qge?(xJOSr~rLPgf7O zAU>GLuT1kGKU=OIoo)jv(Sa+NC-4q)s`Il5lT z@kHL0a&$$?el>=KapktQlb4H&@ynOi^@eH8zW`q|^S77}K>KPW3-(>$9`F(H7VtLk zJ}}0uVcakmrMw`+D%wPxgDXanmereFys;%)p`8xYG-;cvtu3qS8|X$9!>$@GRGJb^ znnWR~U{vyyTQ#c5prURiSmPbD_F8ErqbtG(=bTBn*al~fkxd9MTI5<3Su%*D!B;Ha z#v1~kmj6L6UPWFk@cT8?*Rb|6a2xpWn*IOF4{3pToe|tPl$hAC7SUXm?MwLVe=@fa z$8V_7&xqp_eE$jiZ_K}9z2AEm-=AU+=Fa#4Uk5;suh;Dj;1IY2^xA#~^z*%WUI+Ha zXvh8a@f|{xR6}l-OD$eiwyKcRO=>E>x>5uAR<)w47=?0;WXMs~YC^49L>wb+qR&ob z#}&0lB}qo(Vh_jx7eg*sWMq1BgI*P4&p z{*p;KU8M?wBSB-$eoHB-A_HWUPa%mYG(tF0wrH@*3llpd##F6DSSexEy} zT<1QRxVuv3vm2#6naI0R=Cl8#4B^8L3FF8v<&SA`H@%}PDPz7%`Rf+$kaC?X6gFV& zN;$e&%JD?rm2z}V%KmK(3FFA^-A+-S9*keU@@{raWBvvBl9|7TJpk>CQ7rg(fqTGv zz-z$kz}vtWcZPF|ElPPohE=qQI0sjZA}y;oxp-qswn94{sAZDa5O(HM}+3d`jB8TxHV2 zXkrqn)zG4>Oz0|&$I+ayx;05D2_~=wqwZda=#vWy5xJs0BNA@0Ny}1YveJsE9eC2q zhIpQc^_)$S9ve5>aS~awLnbq+HBWgvutz98A%%I*Qw&SfI{7ygv4o&W#< literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/gg_misaligned.gguf b/wai/live-conformance/prior-forms/files/neg/gg_misaligned.gguf new file mode 100644 index 0000000000000000000000000000000000000000..2d568d60c8ddb89b4aec0a78e85c9b9e261d9ad5 GIT binary patch literal 1840 zcmah}%Wh;f5cS(;cq0&R7|;N-nS{<4WW}&z1xv&lc$aUYspf^V7Zg>gZ&- zUL38TtQG-#gZFg4e14+B-1~lc_QUe*xHH^XK3~oDXIDv&S3f>no^IScH%Rm4z2(_@ ze>P6dkJjIvEG4!(`gr?d+WfvS*{AcL7OTC_aqaxP^Tz3SV1G1mcDaw9oUV7dhZA>X zVfZ~iSw7s5M-Xp;_+TQxG|hwjbg?`>SsSd5aJqh% zJEUCYKA5=MQf7PCN_jSsx24SX{*y9s`uUH*klPt6ZUQ z0ApLq!SzxOC-SzGgDX;Yt1%>uD>t>Byj)z2U%s@iH%w#x1^Akozr}nTw68|8VBZDq z0UrQw0B-{C0%P17#tm~($_p~AqD{m(xMCD(S-r`{8(Xp!+UY<|leVea+On#?fo?=G z?5g2Hr76**NfeR_MkPlaPv#cl_zgAs8F74!?>}Mxjrmus_j~W+`wsSCZjJZwbsOmM^}4+V>;sNs>`|?TL~UGRI`3tvZouQW;8y$uY4@Ejk5%HMNGfW}8n*dzY(B zS{O}CBDES?l$8lxrSUkL6IQn-DJ8)KwqWGl3lV*CLBS(ev}Z)XEjDRcs!Uc|5w!zP zdfDL5wdUidzhqKQSE+*FNYGfb-%v`b$N(ATQ%E8T4N;C{v6YmIS&C5EX>6cX4`h+jy>QQPP>}$GP2gs;cMSy>ISnA;c>{H}%4amuCNDJ?(x!o%}eRJPcTJ zWBKfKwYR!V`uX(HgX#FNlWq*sdb%^6obRp1SL+|nzdM>FwmZ7L`@(I0U!KRu>t7B} zcfY{3>$A=qU%!X(&dgbIe|mg;zT_Uv+#zk8dv-KE*pNpM?|^uJCcirSL4I;LJv=%$ zSnXNrw)7*se#+BMfNh7jx{%V(>DHwj%-1iuL&|0D{h7NhWwm>yl&3R!TgqzpKPf}_ z@I%75vP=16-rNpv|3b=G?^6D{hC8HO=8g{Ama>1fl!Lh)x25b~lCrGEkT9;?)OIvA zLR}g!7Z!8Vn12JlX6A3I9K6LCC8jebQOpJ4tc#(%> zHs+sV55q0{0J9yS$JgujCa?$G0(xyf2m1HDkKO>5Hac^;KIS1rNj2nVxzyrSWvdD~ z-K3`Ct1C5-Z&fR*icu)nNQN9$ttQl(MZ{;MP4wA`?6{)#s3gfKz4k;&3YlXv(pH_w zG^q?F!{nIQr52sSc{R0$XJ?yFNqd*8Oj;OCOd_=!T9lOuU8V6(niE#HCMhMs1h!!0 z-3t+YazWulu4vDQfLmdwQ5Uwx*C=tZDgpLcuxa-fsN`C>Ic+n=wFPnYY( z@%qVX;c+&2Pv^_$Cn~1<-!9L;Tb`eEh8^1%tJ%TqI_byN5095;8#m7l(tP<~dA>fF zja&2M^*5(Wf#Z&DZ(k0Z-xsF&+5D%)YX4I_JHP0>ar+(2_a@FR_xmSj>s{{A#2r}} zelJd!k2mBI#9JUfn#eCr;~+m>EKg3?8oMJoyW-?vQewOA;4g z+foj1mU1+ax1}6jld@ZlAz?hZsqN%t$fb4}!mYo$w)P9~WozwgjC){yadn&MKGqL_ z4}dp-H-UG7VRy#lmN84|h;@^}L~hC3tR*W^oKD$klanE3mFKEhnOIDzNfuW@*OYa$ zMYqhkN~%If_ECB*qvKSImQ{EeRL$OKXIoQFNhL+FnVh%Q>WGJpw?=DGi5%6?N|tO` zBaRARAv+Up34GfA2f26|c`?B6H&9=}-iN?l;QbrU|1UqR2=Q7?tu~)SY`Q55`l@W7 z!)O1Kxq~=^-dZfF573+iSo9a1ZFU{RHUu zd-J>s?AmC@-TkrlJ_s&8Rnv0GPL^h5W~-`DoL#ZGcy_fY$wf94%2pOW1zC#rr6dZt z#!3g5EG63tYL7~?2*N4H90iFKq86s8$hr!tIqJr?akH@oE8(xiTJf!!>S9#RrXr#u zt++A8T6_(nG)7k`d>qx1QMC$=ks5B~JzvyMIn*aa+ literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/gg_q4_0.gguf b/wai/live-conformance/prior-forms/files/neg/gg_q4_0.gguf new file mode 100644 index 0000000000000000000000000000000000000000..1b6bf5028635d081c8651a790d72cff1dd34e13a GIT binary patch literal 1840 zcmah}%Wh;f5cS(;cq0&qXM}jnW;%4fAS;FyD_A0?2|Y*%;{+FL>A;j}Qw?6Oo>eE4-E-#kr#rbTpJ~>-$ zmM5D>>t(>+;5}Zfo}H+e&c0b)e7m|h?F=`TFW2+K`E}9{>+c_~&bMx!8KlMP-s)m= zI3K4LC!4R&RubDC-PF@z`}^E9KVST~T+cqiwTsKn8>ipI{9xkjbH96ZzS-v9lbLA(LtgNgjYG!F9PD}<=S5ms4Ze7apbp1Ye zNV(2^FmZRK%x5=Bc`}iArOaplN*Th(I3$cKyOiIj&E4>huB43hF6GZ#xI@Zy?kM@L zl%t!a98dkRE9K~#l>KT93FFFbZLfD3!WG8i*0b$7}9lreF*>OefQAv_fdhLml6f(zTq^&xUX;K+VhRHFpOD#GDe>Js+w`QA9Nqd*8Oj;OC zOd_=!T9lOuU8V6jniE#HCMhMs1h!!0-3t+YazVi(SF~qDz%4duS*lD{S`oDaPkPzl z&$Z^`w!dUjPFJad;7HI|v)@ums>lEt#eAGKFr371X78)Ff33aq?KoU-c5 L1bW@MOho?xPC~AO literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/gg_trailing_byte.gguf b/wai/live-conformance/prior-forms/files/neg/gg_trailing_byte.gguf new file mode 100644 index 0000000000000000000000000000000000000000..70ca0f6079f3add844e73d449c2d31d46648f890 GIT binary patch literal 1841 zcmah}%Wh;f5cS(;cq0&Rh!8W(W)eDIkQKv<6)X|cgr20O5KyN_BVaQN)~s3bBS`!O zEB?cevWw%+bx5}oVd=E%+Q;QORpoy8@T%#7s~;XO&o*wJ8>IR2!SZ~4 zFdL`l$LnuSmlE3@eY|}+ZGK;v?6dh#i`D+8xORTgdE@juu=gg;F8BK{KAOlcP4gf>T`W&d*9NO2IlJ=+uX}kmo!$;_b|t0z>7z?Iny%mF z4k_2Uk0$Q6l-d4`Ql3rZZ7H+;|D+7zV;&O5m0ijo)8=+~hgVX@dYAIoE!-jHI#(zh zz}S{@c(atFiM%c4@S2p}Y77bE%1v!2FP9hNm#?gw4bzx^0lsABuQBg|_QgmR?EAn2 z-~-?d;7#CNV2nG%xMeO%c|nF%w23$eSBxSpt2eoLV@tL|I~}NL(l%9FTUOOK(2XdD zT{T>&G$ooei9%ArsN^ZPYE+X!McqoU#ye*1wbDvPSA-AFIg@a)4bB=Pn-E^K$h9c4 zWDrM#uUNc|w*)>d|ASn-jJ#Oj_Zz6MVC_TTF7W;h`~R08(gN{1Be-!WF|lDSqPZ&D z=kVG8WbPo2-%z8U5ywaP{uB1!n1983zxO`AKgJ%+t??ed_JAH=uiI z=X>+K3ha*2j=SsQJA^2yhTJTdTD+=kRUxOF)Kq+Rr3Uh?YDHBs3gsHfkfW;Agj%zR zI7Zq;pPk5#D{7BQl8n-8Pn4vPIVK}*)rm}#%1|;)j)`4r(JA<=sWrSc+k8seyIf_` z!f0X=snyV;tW4-CjmOcPu(~x#DG4U91tafXi0G3G3Ld$lJtG2cu}RBPWwO$Ws2zCH z%Lae0H6J(qC6jWxN)-f0g2tNthEh^R2FNI%LK0DEh;k&0t)yJcQiRGT#~e)2u{w|o zTjb6u^u1T)S&&W^IdG+LYv^v%LMtK|R{|c2%!NEU266hRz4A)9Y#QDeiG ORaYj^>&|5&68{2{^{$!# literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/gg_truncated_header.gguf b/wai/live-conformance/prior-forms/files/neg/gg_truncated_header.gguf new file mode 100644 index 0000000000000000000000000000000000000000..49f2f4241e091038890506dcae6d7f3caff5f721 GIT binary patch literal 40 icmZ>F4|QW^U|`?{Vh{kaK|lmTF{G#Fr4}XT=m7ve+5}wy literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/gg_version_2.gguf b/wai/live-conformance/prior-forms/files/neg/gg_version_2.gguf new file mode 100644 index 0000000000000000000000000000000000000000..841ae7fc7e78a357d8be6643bd40be494974058a GIT binary patch literal 1840 zcmah}%Wh*u5bbw2!yAEkLxh-NHsj!YK~@YaRMqW8ZU@^+L`%n2x9{odI#qT3@ZndVDIvs*z_|345wB17ba}B{EzW0))ydhg zUY@LqhU?pxtNG!4oAl%AhsVSDCd_l4v=|-?7wf}$ zKeaene{(iSydE*Gx3A3R_l3znU;MOO%|6Aoi_4MMPrn2IVB+j^zkhPR-sc`q++Kwq z_wsCbydn1_-U9K_M1Ec6$-`ZBK8E@@zW2o8J6d${1%{qm<+6`h9Mf zvdw)oad)N6XE#cDHj#Iw%xC{e>B77168e>+ls~4IyXhTWOX>Tgl)rA_b}8FjF%Bly zu9Tyjr5sP>T`5OfQue#iCG;ydy`7?5U5sD8_HK4eWBvvBl9|7TJpk>CUM%?cfd{|` zz#G7uz`HM5qpH<}TC<2a zM%qN5oyd+WdXG+$jM8gQl%$Y3CL?XtiAZDa5O(HGFHf`INMGxyq!4 z(ZnQDtD!|%nJ`rvA4hY->eeKsB$&V!jJkUvqE9X;MC6L`j7Ye}CM`>q$x17tcHl`b z8{)aveB8{JOv>phRS+Br8f*3&N=X$JAftQ=NkpL`%8@L#l5#Oi5h|M;b1+56>Od}R zQ9GwF_g;}_K{{E~z?H(SVY*EVt%zV;3HVTCF61#W$kRvdl~=-L)9{V4h&csT-X%_1 NjmiXO-MLIe{{r10u9*M; literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/nnc_alignment_bit.nnc b/wai/live-conformance/prior-forms/files/neg/nnc_alignment_bit.nnc new file mode 100644 index 0000000000000000000000000000000000000000..0c7637a1041de71761585664e22d053f61f5045a GIT binary patch literal 883 zcmZQzVParlV{2ewV6fn0V5n9qPt4Ry%FIj2%u83YQYtSlGSoLz(g6z>6y>L7=A?pz z6Ek%)^Gbj+wG0emVvLE&CGi=V42cI?9eNpr9hfiwVbB$0%t@?FEz&ap8kbnika(CC zD96CSAp*ox7?|cTFmP?zAnnD#yuN}#1EDy#G^YfrSlB_h-l80ryH<*AwJ86^w}7cLYOBs%soFp6^dN;7M%{UBzLas5xVYGjGG zflSVeN3XfBon-3U)ilo|U~Y=y%`C$QI}O)eMj zUOur?;_11FhXxGRFsqE9E;oiY40^0F2iGvjAhe*k+Ts5JMgfEz z%-I(wOaMoXa0p|7_7nlZEk{NaSs5M#d>_H%*#%2Uc-r8)AwORieQxRzusSRTn= zvEiYI?;67^ecBn0GZs!Zy!s^h(q|!w8#k)=p1ps-^2>=^51t>fk$iu%{b~VENKD>) z$@OdYg4qq}%g@a{S^IbC35ueZ*My zD6y>L7=A?pz z6Ek%)^Gbj+wG0emVvLE&CGi=V3=a>qI`lFKJ1}4V!=Nk1n3GtUTBK(HG%m52A@MLP zP>z9tLj;JYFfh$wVBp%aLE4Lfd3^zY z0;|wz24jR)h%2F51sxc~8km+gFfhagjz@LgkgrB6GQhF6~?U-~R0apOky-m~`)SbjNi>%sFQHj?jewqGsa35m&j zFWGsk;_dgXbIsrAWUo^zY>aRx@`V(#(2r;ixR zeg%XxH`^$zzx`&zDKYLg#$ZOWRudLgpWt*B$t9 z{(|y|#nYEgy1aPX>{|;q+&TDAbdJ2e@X-$&nJ20{ytXK;kJzZR^%Hx-r%FBRGwH%R M_n%sNvE=&$05-Evz5oCK literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/nnc_long_size.nnc b/wai/live-conformance/prior-forms/files/neg/nnc_long_size.nnc new file mode 100644 index 0000000000000000000000000000000000000000..82482dee90b77f8b580dec1fae36f416e8f165f9 GIT binary patch literal 885 zcmZo*U|?ecVzvf`1_ld028L>-^2AKNq|CgO%)E3ZE2Z+{B13&cB^|JEK~a8MW=<+d zI5ATw&gxKi*hoK%JRi2ueo>9V(aN$BhL84g`=7}R0b8$!K%2;^O12gXYb3@qFX z44&o;ob441$_Ql;k3yA&Fo@k`P`JP_Hv!>CLp^jq!o$>15AMea7Yr0Ze*7Wt+~jic z?&T9ZC7zy(cxb?24YSG!>Y`&17x4#hFiB?R9x#++P&?+!n$@5*fgw+&A^i11_5;jI z6jgEev|BF$dQ$$RMI`O;@0i5oYn_ny6f!1Bw9TMwQev5|a#v;ArTPe@GO zd&$mQ6>q<9oooI^CwrY*VPk}=$^2^->$df!&ghjD*?OCGQ>5GTgL3@h6?2dGJ$=Mj z_A4Nqx!Fcx{p~j!CNHvI?(lR?!HhWvX4K8z_Ta1dCdtRI6*}jOUfR~u7c&3Iyzan< z^B0syES|n}(&fe5X5U({;m*N_qI2Z!g^zyN$UITq;k89!eZ)qkt)JKvK2_>jpGg6y>L7 z=A?pz6Ek%)^Gbj+wG0emVvLE&CGi=V42cI?9eNpr9hfiwVbB$0%t@?FEz&ap8kbni zka(CCD96CSAp*ox7?|cTFmP?zAnnD#yuN}#1EDy#G^YfrSlB_h-l80ryH<*AwJ86^w}7cLYOBs%soFp6^dN;7M%{UBzLas5xV zYGjGGflSVeN3XfBon-3U)ilo|U~Y=y%`C$QI} zO)eMjUOur?;_11FhXxGRFsqE9E;e!JK_@!US;C2!}8RXipIk+;U{qs&0eE3NiMJVLunxuRNvfRhlE;yX2}>jB82O zg5{C?6&oIU_^vU$(x;u_IAh^t!>dn{FMSr0xN)O;@7enYEWez%_2Bst8_D-K+piYz zgv8{%m+ZV%@%H=Hx#n+lve&5MY=sdD90~eG52`i z(?^VDzXHOUn{5=<-+r@U@*?}?4o}w<%$RdvM&0af559_Tl6?GHp>w|IrEM*JA@h&S z>kfQ4e?fV~;^|8#U0%Fx_N@gQ?i_q5I!E4K_~?g?%oEieURxB_M{HEu`iVW^Q>C8u QnRMZu`%f*sSn~Y=0F2#F%m4rY literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/nnc_payload_bit.nnc b/wai/live-conformance/prior-forms/files/neg/nnc_payload_bit.nnc new file mode 100644 index 0000000000000000000000000000000000000000..44d76969bb6f880253be2bbfec8634c21c71a536 GIT binary patch literal 883 zcmZQzVParlV{2e&V6fn0V5n9qPt4Ry%FIj2%u83YQYtSlGSoLz(g6z>6y>L7=A?pz z6Ek%)^Gbj+wG0emVvLE&CGi=V42cI?9eNpr9hfiwVbB$0%t@?FEz&ap8kbnika(CC zD96CSAp*ox7?|cTFmP?zAnnD#yuN}#1EDy#G^YfrSlB_h-l80ryH<*AwJ86^w}7cLYOBs%soFp6^dN;7M%{UBzLas5xVYGjGG zflSVeN3XfBon-3U)ilo|U~Y=y%`C$QI}O)eMj zUOur?;_11FhXxGRFsqE9E;e z!JK_@!US;C2!}8RXipIk+;U{qs&0eE3NiMJVLunxuRNvfRhlE;yX2}>jB82Og5{C? z6&oIU_^vU$(x;u_IAh^t!>dn{FMSr0xN)O;@7enYEWez%_2Bst8_D-K+piYzgv8{% zm+ZV%@%H=Hx#n+lve&5MY=sdD90~eG52`i(?^VD zzXHOUn{5=<-+r@U@*?}?4o}w<%$RdvM&0af559_Tl6?GHp>w|IrEM*JA@h&S>kfQ4 ze?fV~;^|8#U0%Fx_N@gQ?i_q5I!E4K_~?g?%oEieURxB_M{HEu`iVW^Q>C8unRMZu M`%f*sSn~Y=0Fn()HUIzs literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/nnc_pt_float.nnc b/wai/live-conformance/prior-forms/files/neg/nnc_pt_float.nnc new file mode 100644 index 0000000000000000000000000000000000000000..8bf424a62740bd377cdb0d6685b7a7423ae7fa00 GIT binary patch literal 883 zcmZQzVParlV{2e&V6fn0V5n9qPt4Ry%FIj2%u83YQYtSlGSoLz(g6z>6y>L7=A?pz z6Ek%)^Gbj+wG0emVw{P|CGi=V42cI?9eNpr9hfiwVbB$0%t@?FEz&ap8kbnika(CC zD96CSAp*ox7?|cTFmP?zAnnD#yuN}#1EDy#G^YfrSlB_h-l80ryH<*AwJ86^w}7cLYOBs%soFp6^dN;7M%{UBzLas5xVYGjGG zflSVeN3XfBon-3U)ilo|U~Y=y%`C$QI}O)eMj zUOur?;_11FhXxGRFsqE9E;e z!JK_@!US;C2!}8RXipIk+;U{qs&0eE3NiMJVLunxuRNvfRhlE;yX2}>jB82Og5{C? z6&oIU_^vU$(x;u_IAh^t!>dn{FMSr0xN)O;@7enYEWez%_2Bst8_D-K+piYzgv8{% zm+ZV%@%H=Hx#n+lve&5MY=sdD90~eG52`i(?^VD zzXHOUn{5=<-+r@U@*?}?4o}w<%$RdvM&0af559_Tl6?GHp>w|IrEM*JA@h&S>kfQ4 ze?fV~;^|8#U0%Fx_N@gQ?i_q5I!E4K_~?g?%oEieURxB_M{HEu`iVW^Q>C8unRMZu M`%f*sSn~Y=0KElIMgRZ+ literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/nnc_raw_float.nnc b/wai/live-conformance/prior-forms/files/neg/nnc_raw_float.nnc new file mode 100644 index 0000000000000000000000000000000000000000..462d81ccea0c10c514eac0e2eb59fada1108ec87 GIT binary patch literal 880 zcmZQzVParlV{2e&V6fn0V5n9qPt4Ry%FIj2%u83YQYtSlGSoLz(g6z>6y>L7=A?pz z6Ek%)^Gbj+wG0fxVuFduCGi=V42K)J8W|3>2Rg4CSEw+mJB4W1Gk7{xf7NdbRNpO^_~c~p z48=^xbt`}RSMa9nsb}_CJ^AjIyaOq#kBH5g(JNQ_fk7Q+y&=@Qhd|yHc3`~3z`(-I zz~E`lz}a5Gpo~xk@hDVT2!q&7289a@a}yALG}J@)BRotE_27P-aKS(kX zRDo6KG=nihE5wyht%42=Vhv17fe{>&;2Fg@#bH*$76v`mn1gE=WDr_VTgpUU%Tb z`3uS;7EfO~>GI-jvu`cfaOdDd(K+(=!bd-BWS*$*@Y6y>L7=A?pz6Ek%) z^Gbj+wG0emVvLE&CGi=V42cI?9eNpr9hfiwVbB$0%t@?FEz&ap8kbnika(CCD96CS zAp*ox7?|cTFmP?zAnnD#yuN}#1EDy#G^YfrSlB_h-l80ryH<*AwJ86^w}7cLYOBs%soFp6^dN;7M%{UBzLas5xVYGjGGflSVe zN3XfBon-3U)ilo|U~Y=y%`C$QI}O)eMjUOur? z;_11FhXxGRFsqE9E;e!JK_@ z!US;C2!}8RXipIk+;U{qs&0eE3NiMJVLunxuRNvfRhlE;yX2}>jB82Og5{C?6&oIU z_^vU$(x;u_IAh^t!>dn{FMSr0xN)O;@7enYEWez%_2Bst8_D-K+piYzgv8{%m+ZV% z@%H=Hx#n+lve&5MY=sdD90~eG52`i(?^VDzXHOU zn{5=<-+r@U@*?}?4o}w<%$RdvM&0af559_Tl6?GHp>w|IrEM*JA@h&S>kfQ4e?fV~ z;^|8#U0%Fx_N@gQ?i_q5I!E4K_~?g?%oEieURxB_M{HEu`iVW^Q>C8unRMZu`%f*s ISn~Y=0FLKRH~;_u literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/nnc_truncated.nnc b/wai/live-conformance/prior-forms/files/neg/nnc_truncated.nnc new file mode 100644 index 0000000000000000000000000000000000000000..d393e9c03d45c71832f24b124bfd1117138b6770 GIT binary patch literal 882 zcmZQzVParlV{2e&V6fn0V5n9qPt4Ry%FIj2%u83YQYtSlGSoLz(g6z>6y>L7=A?pz z6Ek%)^Gbj+wG0emVvLE&CGi=V42cI?9eNpr9hfiwVbB$0%t@?FEz&ap8kbnika(CC zD96CSAp*ox7?|cTFmP?zAnnD#yuN}#1EDy#G^YfrSlB_h-l80ryH<*AwJ86^w}7cLYOBs%soFp6^dN;7M%{UBzLas5xVYGjGG zflSVeN3XfBon-3U)ilo|U~Y=y%`C$QI}O)eMj zUOur?;_11FhXxGRFsqE9E;e z!JK_@!US;C2!}8RXipIk+;U{qs&0eE3NiMJVLunxuRNvfRhlE;yX2}>jB82Og5{C? z6&oIU_^vU$(x;u_IAh^t!>dn{FMSr0xN)O;@7enYEWez%_2Bst8_D-K+piYzgv8{% zm+ZV%@%H=Hx#n+lve&5MY=sdD90~eG52`i(?^VD zzXHOUn{5=<-+r@U@*?}?4o}w<%$RdvM&0af559_Tl6?GHp>w|IrEM*JA@h&S>kfQ4 ze?fV~;^|8#U0%Fx_N@gQ?i_q5I!E4K_~?g?%oEieURxB_M{HEu`iVW^Q>C8unRMZu L`%f*sSn?eJR98-3 literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/st_binding_unknown.safetensors b/wai/live-conformance/prior-forms/files/neg/st_binding_unknown.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..8250d7aa6da30ad90bca79d92880b5243725b196 GIT binary patch literal 1767 zcmb7@$!;4*5Qcm2jt^N31PPE$AhP%{ra42AvitnV?iW-%uILHH&uW2{3Y4;cRgAxo}Fx#$IH!fu^2tM9$hV0 z<6l-6$E%B{qbH-Q_2uECcGPT_UtB&vU7erw%gfcn)x`!oubR>F*UjQ=)#n~>UcTtJ ze`x1OS)VQYr=Q!SX0+u$KRsQaY}QzsG~E$0&zCPxF2@tRZ;91G{bLCew~@ab>@mH*K@SUSOK;U@)TY7F*hG zhc5?{X4*b}+fz4ww=cUO05sG2ps8+rXJe2L#=)?VTa0PDW`Fm20jD`Q#IRrO?d?^j zO0Cz>Pqo$~T=(!@|Hv78fa^o(OXy?h6X-MOUJ}AGQdM{zs77a!5+r%HDkiIRike9y z9TiDZhpcmnBHMy+#geKftC}bk!3tM_To6u)U@4ZYMBz?&S(4Mrnp&lmLJICpV%8aB zRKSb1Gg>KL7_+SSnv)<+3Uw5)e6rgAgYrK>D;oL(C4UHg1$_g30o^0XfHaELQeE<) zsw$aR^?Pi-*9QRb27dewzzENO;{I=3Ut_%oJiznU*!vv!dr%MVy}1uPg1&`%|GtCz z_)npap!c2goU)5W*OZeLxoVNf$igKhn@!4&Y{^nk78NF#Lfl0!a#k+q$UM?WDsQ7< z(bNoI;SzDrt+YgeGah|VTxS_b6)qG7RmoJUCKPW3csZ291k=TaAgzg+2g#JAN-UJ@ zis!i!CME@A#V{>P;S>U^glJY_m}Sh{V4MK4W{lpW;NlC{m5WH4Yv!dv9JjoHXG*bt zJ3AhPk(mp}swecAquqw2kl_F+VS@|IGl`}Q@uV{e<0EG-R~3D9DoLLWf>YJ-&Ip_- zE67$xGH7^UQmUokoF{+{5)wOPjRri9M{=CV9%Kb8JJzgu z86@6<74K1(Q&s#u8#nS0%QO1b-Bss&ea?TX|B`I`yB;kT&sUq}@p7|VEJjbSM_0@B z{!i=k znJTqjLqF77k8s_?cl{k_>>;j?pwFNWpbw#sp$AC_%Sct>b)XuZNlK99*{YbV(kW^t zjdWBbNgcAzC5mhd!WB!ZnyhM~R0Jzr1#&?+C4!|`vJ!eYR!eos zhpMV%Ue#}~`BonQ!0+(mR{%zM{uB3q zl3npUSHi@kV5}IXWhtCOV3iQfDh#uXc^ixqAl8i0dlX!J;kt4WNpsD-G>GGt7w}9e z)^BIWgD^64;aK&A9&@zYkQ6c;ASG;Yfq5p;lp&sUCSiQ!%;l=0k4`1&vq5mG8r~U! zGi3$Y%18zc4@^q66r3DYTuIC+lN=5qkBKZsjXJHDR#?GIR0Zd^G9QDh!kSP+6n$q5 K=g62yME?RJ&3{$^ literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/st_duplicate_member.safetensors b/wai/live-conformance/prior-forms/files/neg/st_duplicate_member.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..24738d3f99369c33137ec8be92fb0c7cfc0071a7 GIT binary patch literal 1823 zcmbW1$!;4*5Qcm2jt^N31PPE$AhP%{ra42Avit6+RQ=VYmu&mF9xWEnPBzQqXGg|(-S)8r<+~dv57yb4R z?HnoVvt|GEb9>Z`w*2R(r|Xl=8cUO=J3{99^5x0pc!KvWu{y|~c7y!+&HS$E=0m`< z-_AF81I(M*WJp+_txo@qcswX!M+>t_Gi!&0tCQ8!v;RiSn`!^O*~_eLx_0`O)ZSvp z{!Ik(o31+?GT!aTE=G6Qbo;X*^JkKZFblTOw%0c;Q(Wfug1W;!1<)s63L4D!J^7#4DiF>TlE?>;Z!GzW*-mAUJ zRH^kE`l;4>gzFyu4}Ro~J;3!L^d*ApcM`Mfs#LjzJk7izJTtLWI!6lYN;;y zP*s)8tNJ}Q-|GVacmqFv2VjKfKXLyTuCKA)10LY{YwUfF`#q=!_ukxx9zowiy?@_9 zef+1;N6`Duc~05IqHD^@id?lwWMtuzlFcS%N48`sD2oacOd;-~7db1Jb7UTAB$c<( zuxM(AuW*UD=T=&xz!{G|D6X>%qzV^`f~sUHRTGLg0=yhbVS?#mLy*?Q%!6b~QY98j zcE$5t2@{iov0|8(rEm&?RYEkYFw8RMZ7@!NSTjcNQE>5v>&itW%{BAVAdXvJz%!*- zznvWq!pO{pW7QLS%+YQ`Qpj+Cl(4}C=9xrOhIrDMgz=Fxm#d0CI+di)2EnOncxMF8 zloezvBN;S2Fe%kiaB@^}B{8Q=ayW!MCbAec>a<>3VFfc$6`bG7dR)WewPlM{=CVGspxhyR2FB zGVFN+R=h`DPF3;uY|`K%mV5N8yQ|Lm`kcSc{3+Syx*i=JJv&)1ju-32(b4GOdUUl| zj(=QU94{}Pjt)jwtIPIrJ8Cw|FD{>-F3(T;<;C*R@?wphSIubg)B5Oa+2J8{7CUxtB9Py7-TsjAc1N}`y8Wixoeddx_5}H03{IvU-{Q({GjG~vi@m@!-QM7c zx?5~%`?>nhumR9a+b3^&>c(&PWg7&5W;!1<)s63L4D!L)8y0elF>TlEZl4!$n!SA- z_N$%#s!Wwyuc7a2t%tbp;Q##{XY3*FkD$+>51|Lp$I!hbgk_|v@H$Y9&LkyB@@!R1 zR_PQqlSVo!lB5n<=MqJ>1>uS%RZUhkQ7VEJt^&CroD#uOELn-do$#_Gr_eGOy}4*nFoC0N^+H@hbo$y#Imczi@wz^&apL?_Xf=6Fl!gJ-GMgKJ*y+ z3hMp)8tUUefywm|zNV7rn??xtt^O zNF%AdjfO>2Gkk?h#67pt5(Un9^g(f*Wgu0!P!v=pQ>mIzyb<8#Pzn=F7aM}KCT1Qa zQ<5sNP_iqY=Sny+DHtn;X;})V5LhKdvkJp3W8Mbi1c)``=sgN9zHnW+h@`n@UK+%4 z%L{m>6zex<$Ad63bKzL^gdTIWn~)ST93UlZaDjOy(Uc*cbS7bZ9av_ zsv6!Iftj*`Y-J>ah6g64S_&pd6;~2-$|Q#&cRfBjdVaE59xpe`qoeWD>+#ic zHTh|EalE>CHhwz3T3@!0+i|m9etG%gbaj5xFE3Y*Ru>!Wyl%$JpEpNmt3LO5^Xg^4 z{Y|?-%KB{CKmFJqHsdY-#p&t#WV6Q7wCN6!dA@vgaygmeeM_v~?|BqNSv;Kd(mwDTC?d*os z-eJeyZ3Ob0t~(eq-t5ROMt9J3d-EaV-ku;IjKO5u$sMljHH)TgcGwF{)9nvN)ZJlA z+wJgWf7;C2CpSHHlQ;Xa3j#nhTMU}&CigZ5`C#l13%SFXwrlowpBHeN{R0g9^=LGz zOqE)%p&x3khq#XLU4PFRdx+~J=ria8=tJmZ=s^;~GE!A|9jHcUk`g3&wkjs8bc&iu zBOMhiGl|l;cO=8v= zV^qM4wKG~NUKq2i_?nX-O$v1suza%G|AX@1K`R>i10}x;eGYvIeF{Aw$$&J9)lyyZ zp{gpGSM^(LzSRc+@H_nY6@W3G|HS>@xc-Lq9`F#)UtsSO+>f9h+izo~ z>f=9x-h;y~tU)oFnr{BdNTN zhDB2|e1%KIJ-5;l1s+4`Ny#B?|QUYJUiJekC&U}VljGhJ-S-1 z#=opCj#n2?M^8pq>&y00J8HJeFD{>-uFg;T<>l((>SBYPSIubo>t=Db>T{1bFJJWA zKeThCtk0JH)6eZuGurZ>pPsHyHftj{*|7I_lk!1W>YCG;`$3G^9sF9~58sVckjMCI13!KTV1(yCasL;tud&_(9^mCLEl2Xf8RlU z{HM@I(EHALPT9qxYs$%rT(wAKWZ{yM%_e0>wqz+NiwYA=A?~6VIV+cQWFBcGmABEb zXljP9aEZ9*R$8LK8IL|FuColJ3Kxoks$?ov6N)ziyc|klg6U#Ikk-V^gJeolB^F9{ z#q(SV6O)3mVwjeta0-D{LNu!|%rfR}FiwD2Ge+-GaPfuf%0(p2HS^LSj$2;9Go@I+ zogELt$jpUf)f0Nm(QZRh$Z&v^u)zi9nM6~Dc+#1K@sTr^tBO84m88!G!KrF^X9Uia z6=W+T88kdFDb-SNa#V38F{ey&ID|YVvKTe$v|d_a1v604JOBUy literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/st_header_too_large.safetensors b/wai/live-conformance/prior-forms/files/neg/st_header_too_large.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..d8e70d87d11e221b0c259062fe4355defcffec15 GIT binary patch literal 1767 zcmb7@$!;4*5Qcm2jt|*@zyY!mL>3>$G-oJMmY*QGNxo6kAt zW%3+3<&cL6&`Wi9{?Ww~h_N6NKW3)8>YJ*+didY}Xola_c)5Ibyj>lwwyWiG{N!qU zxmr(tTAv@S&!3K;j4wAA?W1xb*}Ep}ct|W5J=(r} z(Qkj(E|9W0UG+~twujC5n*aRdWOKaTU}@TPhsZozy*$2{O!2-IRtNdBZjisYncp?t zVhDKl%h~pBfJHN(4hfsn^~rw`j|U~}Xkk8W=IxMhdAxpl`u~VUGwc61dzrUQ*UsLM z+FR_{yNN)4({%?!#@ij)#pn*2Zf`zh+}RW4gE5#)JGsS`y=Kw0%?^8kX}bNvh`L*B zX}cZ1>`$9n`}j>y-Q?}Q?1BK$%oc;Dy2+i5K|UD!!$NK`rtO-&-RA|IX8!=gel;46 zDpRG_E9i$>>mjZqeAVA`#vb7M5c(YY2)Yk_0^LhOSVpP}uLITSOj3d*&sN1`l}=GJ zX{4hfN$QYwE>UD#5UyBK)nru@r6O42Dv%4pDG@Bil9edj2`@`>T3J)8v{FdHy-CbE zV~h%Tv35o)#S3GW6<>1_q)DNU0+vr!`@c~BduT;NzoX>$p)a7XpwFOtBpHxKv0AE2 zK2%jD^QwM}&3F0$0DglXzXCAE^B=hX6W7;R?*R|+{3Z52#r+8C!M!&hLXV)Yq29l5 zpg#U%=mY3o=RBwEV$n6_WJRu8Br>vaNy%oDvLjov6qH4U38oNt(Tkjw%Q-TSG?L2O zXjn8g!&kUO+;b}}QQ(Y69~9SF22zC!ML|_Em8uEF8v$Mpr7*#Cu^~unV&*|IC8-h% zCA;Ezu7rt6!B{a&%ThRnz$zh{RTyR&^EMbKK&%;~_b9md!gb{$lIEIuX%NRPFW{L{ ztiPTe55maIg=5tddd$&Yhoq3<04ZUE3(PZ#rVR0nV?iW-%uILHH&uW2{3Y4;w|6~SES{ZgmdDG@as*Z|(+|H?zr*us&Oz{ul9hP{NKDW|L;t4hdH$tEXrGkC->p{(rNVS=)5&^ew5q z#g6@(2;?_icQ|Cc+mT(2?y%|hXG6xFJwZMggUPhxTU^<1=1tq|uosx7I~a_pyTz8a z+u_T>q?xvl-}cmv-|fpT2msA=K4_{N-`N=CgK;n{jwoR!NtGLJNp%G+pI zG&RFlxJ2A@D=ksrj7J|7*I5Qqg$qSNRWg;T3B?-$UJj)&!E~`9NNZx|K{6$&5(_1} z;(4xwiAlj&F-*%+IEBC}A(~YfW*PG~7$-oi8Kd_oxcI_#int5pu$1N}5nNqCZ z&W;CRWah%L>IpsOXtyCLWH>-d*x&;5Orj}6Jn2lr_{f>dRYf12O44V8;8ZodGXiJI z3bK`v3>qGolxitBIjXplm{TS>96}xwS&SNWS}(1zf|;la&TnNt23LhOp@t~>&KS;- IF_DP=1ytXE5&!@I literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/st_member_order.safetensors b/wai/live-conformance/prior-forms/files/neg/st_member_order.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..620200aa899e6af9ba04c7f0fd897f917f93d47b GIT binary patch literal 1767 zcmb7@$!;7s5Qe#v@gW-!I6$_+Ad3&QJl(TM@)IPNoN^0 zXRFK8)#dZy)8X~{s(I23{dW1))yuQh#j;yotRAl}H?(u>hZl?2%d3O-;Ci`we!l4# zr<>QWy0D*)=*#u_qMQ8Mw0`2pethDG+tXg2ovoLfHSLUNhyL*R_@u{LpRdmLpms54 zo;ZERi{CHqBF62Vbap_RBZAEOkY}p}rP#aDy^!{BF6?t@@EmY9m{W5KQ+l=34Gdg&0 zSEeIBZTf`$p3HW5GV{}3Wk&SV?CX?W1mAAUviNN?KVRKxvS!xr)OQab`S#@Yb}$$q zQx)-s^a~=M&^Vy~`e)AABN`u*z9xM}dPw?$^dJji87LG%N5JGvRstk1R;6r}&Pg+A zq=PC!>R5ECNfcWtT(PXs6onab6|8aUN>uJdkTpB4tU;7k3MqInnOSFy zQIQVT&S<50Wz4b~uw(%m6y;Hg6|&XgKT7{2x{4BLT4XS?atYZMlZyjeixf}0OTYuS(gH-zcnVQ*U1S7Rxlj-lf^H z7He@e@Pa@UlY+5on3lD2h>;;sG($4XG8Sw!P7o0bl|E3yHB_#Viv*f$7Nns#Zh0l1 zsnv#U?RXSM7A_pa0CdNa>^3CFLJoiuHoC|HlXNKqJnI6&gv6OEs8UEyWf_X0;EbBQ zGlF``3b4pPMok`=oUvBw993P-%qf!{H6c$KER~w_v_V>71v5!i>R)6bMTf$gh%t%o MW{mpCm;?a-10U>v5&!@I literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/st_missing_tensor.safetensors b/wai/live-conformance/prior-forms/files/neg/st_missing_tensor.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..d1afac66a6375bf6e5fc5e0942ea24753a806aa4 GIT binary patch literal 1702 zcmb7@%WfP+6o%_w1q*Hx0*RY7;*v#Y;%VCxCr-lHz3>hSk$(#S)s^ypW2SJnCYod2BpO^eNSGdVtfdb(bmEY^$T>t*DEaTcKs0|FBUIOuco{B-U6$m_`QA2r5`pp3I?qGLJxISGzIsY%?aGXQ8H~emAa)451qhe-v6tjODf%vZP4_{?7o4&rD zZH)e~>-YD^jMe$_?Eeee(iFslaWH1w;mUqD?`GXr)B!s5=oPBJ!xmV0Ye)d-_GS;q zkk|XN4FW*7Hy;(%Pv6)W#Dg&!xPE$vG3e<2_IUxPd-w>=ez`MTEKFm*fqr1-BiwiJ zzx-YbaS!+V(C5&H&_~cGP*KVYGOVIa#5uTP6lqz#$;BI6vK89tKuwdjsoL7Is=nd4 z5yh~ph6|OZM3W{_NGceWJmpr6YBH#(TM5>9$E>|pTFK~&@WDA}54I7MpJk2LSj3 zcKiy!1n+<0`ET5R$NB)chxaeB_bHxtpaDEs^8xe#`WhPS`vw}$e+<13y%R!|R6}l- zOD$eiwyKcRO=>E>x>5uAR<)w47$&$zGUTXgHKEolBEm?U=(7{qafPpNiDZ;sd!i(T z%rO~ht4?H^RECmaa!l+}i%x-8Q)@yA+k8seyIf_`!f0X=snyV;tW4;bRzfr|NFoZ2q8!O$D=8PV6rr-oF$Yt0tPY0@TX^RbX38t_EJ!B{4_qnS8YaiI t(25Agm4G3XxsXSzk*AN^E3bshreS_r#GC>v?-Hl1hROuy$hk~J{{oerYDNG6 literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/st_mult_zero.safetensors b/wai/live-conformance/prior-forms/files/neg/st_mult_zero.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..711eddd6cf8f2ad8fb8e42c0f35a9c57d4406c9c GIT binary patch literal 1767 zcmb7@$!;4*5Qcm2jt|*@zyY!mL>3>$GG{1KmY*QGNxo6kAt zWdh_aa>{%3Qr(?@n1d$}V?iW-%uILHH&uW2{3+S?cRgM#o}Fx#$IH!fu^2zO9$zh2 zdq1r%j#n2?$4|yr>&y00J8rhiFD{>-uFg;T<>l((>SBYPSIv0&^Ja0j>T{1bFJJWA z-?ek3tk0JH(~s>@Gv4x_pPsHyHftCC! zJN9oPkl%FO;gIomM|Lr~!=~Gx4HbLCe!ZS;>vzAZ`x*uy}&fx!C*w)Ew;4X z4qpx?&9r^|rl)T2?Y``S0MJb5gQmK@I~#+1Fb;-=++s}IHT%2I3pmZeA%^{GG#XW= zO0Cz>54F}KTu1n>zvusc-4Aj39Qp{l4}AjNOF~#istT_I)#yx8f+WvY#blLEQ8Q_z zqasP_kaaFmWLprfSW?wwRTHHmSm7#=3&JT8EX9(QDBKAzOLAISQ>(O6NWr~H%sOL? z3V5-0Mk~b&W0n{+*I4fX5AggY_CCe^2nsDQ!iA!sDw#^vgyM|=FNac?V7k~4q%|?~AeoX>iG`9~ z@jO?;#H3)X7^Y<@oI+rg5X~wKvy6Egj1wT%jL~}(TzuiWauG>$&Ac>-Hq)$ literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/st_not_json.safetensors b/wai/live-conformance/prior-forms/files/neg/st_not_json.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..da6defdd6aded06e5bcf31441cc7b8b05574a6b0 GIT binary patch literal 823 zcmXAny^dE$48=Vjk3HD}Hb{s_BxJQBfdHp{M_OB0rzyJ8@_K%zUCrKjn4!q6GcUW%__@)rO#QF+61CPNI z@DyBaW+Rv6(Wd9>x`n-#W3skM+pAI4+C8e*HcvaT_*fuZXiMsm+`fY;icIg5yYMt$ zafG9E9yNAvVwzd4a^*5~lXX>vhOidveJ*W$2w5K};=d&T6_& znU$n^I?XseMmg!|SY@J6YBi$xoRp4eN*HED!=`qIVji@9AmJEMS{7d3N~2K~am7kN zPa|yH_pVIVdJ1>S;i&N}yARp(zyTMw*(VLEC`!HTcARyrLj9zyvAXO!jv{zxgLlf9 zDPy-Jud~4e-8&;NITAfKbJKZsK&;mO9JzoG}i3NrZeZ_OJ#&PQeE!; E9Tv`600000 literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/st_offset_gap.safetensors b/wai/live-conformance/prior-forms/files/neg/st_offset_gap.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..fd5403a77d1f6e7f1be6e32a11c21be0000aeb27 GIT binary patch literal 1768 zcmb7@$!;4*5Qcm2jt^N31PPE$AhP%{ra42AvitnV?iW-%uILHH&uW2{3Y4;cRgAxo}Fx#$IH!fu^2tM9$hV0 z<6l-6$E%B{qbH-Q^=1309W~qK7njdZSLY}F@^bZXb+N(Dt7f$Pb+b5I^|{BJmoNJ5 zAKE!m)@RH9>F4&S8EyH`Pfyn;n>CguO?QOM^X1Ev%kc#7TVi#PKkWwj^PBly)6Iu~ zXTP0q?gp4Qv&oRKK3kps7x8#d!j2YZlV;Wq30Ehpr)U3ua9#g?|) z;mg6KnYNGL_SB8v?aM9*0L^qhXsR3E*%;)5aWE|87Gv73+24I$z-bN+G3-}+dwZ-h zRcgJ4eyX(|;kt(p`$x{$16&_MUqT;4pFp2M_mU8nk*dP$Ks7p(lpx8oRWVtmQ`Afv z>8MDOI%J(o6xkMpE0$C>S=B_T2v)cXGk6jE?+60^=2 zqXJ&6ozY71!kA^n*PH}tQmCVV<&)L^AC&(ATG7xSDEUL^E9e{O3+Nt62BcA}mgbdH}K7Ay3YUm`Zlxs(obl*`;yTMfs&Jtws7j_%HKBMTz{{Z&CYUZZ1ZhpoJV>S_RbruJ zS3J*^Ffl0@D~4%V3a1cQB}B6d!z^Ro2IB;XHDmN11s7kqu3SXYTr)2X;<)7nJX4DG z+u89TjLcj(Rz0D|9PKtFg$xHs2^(Bso=G%ih$o#%7#}%vxvJ=+Q%U-45S*%pcShh$ zSwXfkl0m}*lTs}OCr1@m5_8HVheOC?B8yR@PV1!=RxlG)!TGJs$Ka~4Ce#o`-x IGA0tyzc8bJ5&!@I literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/st_profile_absent.safetensors b/wai/live-conformance/prior-forms/files/neg/st_profile_absent.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..c51c573cac296b6307f0df01b71bb94abfef3eda GIT binary patch literal 1743 zcmb7@%WfP+6o%_wMHXBlA&|IPEiPGfCeuBhahxZB4J&rY8Vz_HkCX(I8Ds@3JJzgu zguFzagB9;lms3^z?M)hah~*yr>aOZ@zB%VV)vqMGx-OgL^67fFI$iBn%VqQEvbk7o zCO>VSoo=2zZXPuk+vnYbu6fxutDkqv^UV-(x_j~K8t=aA7ERl1&sW3h$L^$Ut|-5p zoo&~>s~79%lPRbx?2hth{V0ENJ-=`J#TfAPwj~}$_CA@mXS33Mk3VHv3^ybe^OGf4@OJX;l$RXRn@q>+w_B&kEzxkQm|LAYW`Rg+as zl!{=5t3WOYr$n$6OID(AC%i1lX=P2V(n=u(_a-sxj4>+UkF_&eDP9<}toWLfAWaH& z6tH}<+W&*{-$N@J`U@q$4}AfB1$_qHA<2L=iq%qG@}a6KnOF5&ynJT}0N_vf@jCzw z*01sWH|~F6e*oOW`b)g~6we3H03N)#3q63oh6ew>frj{xp%0*Uo%5Wsi$&LzlNGsY zk;urxB_*3p%8qQwQcxBZCYVCpMK5wzF6YQR(nuiK^iIR_0@HRag^hh+^oB;T#zgiRfSR4R+H2 literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/st_shift_range.safetensors b/wai/live-conformance/prior-forms/files/neg/st_shift_range.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..d9589a12006c9e763315b34c4d9e52898f5b98ef GIT binary patch literal 1767 zcmb7@$!;4*5Qcm2P7c|CzyY!eL>3*!G-oJMmY*QGNxo6kAt zWdh_aa>{%3Qr(?@c<}^cEQrL9ndz?jrs}Vrza-oKZbm03&z76T>0+}uIT<~<8C@?{ zo1SqbH;5^;P?*9W~qK7gx{ERu{{Dd9ix9y4+yrRWn-rvN<_l^|_~;moNJ5 z@7p<2*5`}<>8JL%8EyH`&(7A%%^FLSraMOF#p31iYCOUF7FZqRPrE_>{C0lVbn_wL z*{>Iy`vK<7Y%(OQ&sS&vMLZsqu%m_9q?xrt!u4|X^!)!3^Jd!rZ}&25o35R{A+>kd zac~=f{HE)UhK#p6vWw9jHQm8%$hfyB$OmIEnRa}KD+kTIX`3DP0@HMdgAsLi*wS`8 zd^wyn)AsS3p1Sedec1&8pqb7GO?BgY8-si>4u^%@VNBaK2fNP;IL+Y^hW%=9Z?7^{ zYQ2GethFBFx`*%j2hP|&t`DIvppT&k(5KLT62dZ4Rd^k!MrV=|Bzd+fCaZLcnn@!a z6-iQutaFJX+k$Y#lBy=FnkW^)3Ri(#5Kf6;DVD56;ZAs2lGDnXTBVgj3hqr}))`|| zz>BprS}9%_v#j`1k-gC}#$}SdNQ%+Xoszo9r3zw8^HYq!@B}+kBRG452aTmSFS-G4e^GGA9yp4uM zQ!{*pOT<05(h>#Ec=SPWon;_ZxKI>SB~z)IP`nY~=WU#i&uI_0kF}n2D<3{8r{;a8+0nYKWrmjNu#^ H6N%_wUL}7p literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/st_truncated.safetensors b/wai/live-conformance/prior-forms/files/neg/st_truncated.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..5bcf4d5562b6ae69d517d4d24bcc3644b011ea7c GIT binary patch literal 6 LcmX@W%m4%c1eE~9 literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/neg/st_wrong_shape.safetensors b/wai/live-conformance/prior-forms/files/neg/st_wrong_shape.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..4afb128d2875dc0c3133d1adee47be0633a99b93 GIT binary patch literal 1767 zcmb7@$!;4*5Qcm2jt^N31PPE$AhP%{ra42AvitnV?iW-%uILHH(h_#{3Y4;cRgAxo}Fx#$IH!fu^2tM9$hV0 z<6l-6$E%B{qbH-Q^=1309W~qK7njdZSLY}F@^bZXb+N(Dt7f$Pb+b5I^|{BJmoNJ5 zAKE!m)@RH9>F2g-kDAf;4bM+c*C(4b7AH-2gxvGx%ahCT1aDkobwHSQ1H$}fe%EyK zA>i3>=bO6$=FMy}B&^R?r~gGf9vopu3A0HvYlnoZlhxC+|3}Q5Y5%`j%dBm>cKVjo z-eSl8O$73rt~(qu-tEXPMt9hB`?DeA&YmD2j6rAG@hz_GH}huagaFfY2ZI)Mx7gBl zJA65qG}HF++nT!ZyM5UO0ic=A2Ss(`I~#+1Fb;+Xxy6{aYxZ}~3pmZeA)5VauQF9? zy@r0OwI1QRhwu7F&e#K7A3|S3A48u&pF#JM5SEdu!s|daI+K(j$+J~4S*26dOd9E^ zNRm2aol6wi7KAI7R5e-EM5zc?xC-Qga7qMAv1BC*cf!k(oL1JZ zQ|Ke;edj!<>|)V1Ea7oE#ld>aQvJ{j>g$bq*chQTSmCHFYk2I3X+h|xc zHN#i9MBH;LEm7c%M;{c|Sq4&t3q?UyGL@~wYhva>G9{@J3njba zd9H+xNx@h#Ov_R@g}^EynpGHP8S^$6CqS$jt@k)^@rCQkMI_BN^U@%WTVB93rC7i1 z9S_3D%!Oms6Kc%SZbMSYaDbGs!3E}-L{Wx#(wT(uku#U8iat7(q|XMyscLv<1ZK(# zvXzkx8XlOGYAKi;Ra{BTDU%$AkjF$8t;RX6msVK8OjHH)TbYl+Rbfr2A&R~-hB-1O G64Aeg<9{yz literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/seeded.gguf b/wai/live-conformance/prior-forms/files/seeded.gguf new file mode 100644 index 0000000000000000000000000000000000000000..f6970164060eee92896e13bb222e1e84a4de5eb5 GIT binary patch literal 1840 zcmah}%Wh;f5cS(;cq0&Rh!8W(W)eDIkQKv<6)X|cgr20O5KyN_BVaQN)~s3bBS`!O zEB?cevWw%+bx5}oVd=E%+Q;QORpoy8@T%#7s~;XO&o*wJ8>IR2!SZ~4 zFdL`l$LnuSmlE3@eY|}+ZGK;v?6dh#i`D+8xORTgdE@juu=gg;F8BK{KAOlcP4gf>T`W&d*9NO2IlJ=+uX}kmo!$;_b|t0z>7z?Iny%mF z4k_2Uk0$Q6l-d4`Ql3rZZ7H+;|D+7zV;&O5m0ijo)8=+~hgVX@dYAIoE!-jHI#(zh zz}S{@c(atFiM%c4@S2p}Y77bE%1v!2FP9hNm#?gw4bzx^0lsABuQBg|_QgmR?EAn2 z-~-?d;7#CNV2nG%xMeO%c|nF%w23$eSBxSpt2eoLV@tL|I~}NL(l%9FTUOOK(2XdD zT{T>&G$ooei9%ArsN^ZPYE+X!McqoU#ye*1wbDvPSA-AFIg@a)4bB=Pn-E^K$h9c4 zWDrM#uUNc|w*)>d|ASn-jJ#Oj_Zz6MVC_TTF7W;h`~R08(gN{1Be-!WF|lDSqPZ&D z=kVG8WbPo2-%z8U5ywaP{uB1!n1983zxO`AKgJ%+t??ed_JAH=uiI z=X>+K3ha*2j=SsQJA^2yhTJTdTD+=kRUxOF)Kq+Rr3Uh?YDHBs3gsHfkfW;Agj%zR zI7Zq;pPk5#D{7BQl8n-8Pn4vPIVK}*)rm}#%1|;)j)`4r(JA<=sWrSc+k8seyIf_` z!f0X=snyV;tW4-CjmOcPu(~x#DG4U91tafXi0G3G3Ld$lJtG2cu}RBPWwO$Ws2zCH z%Lae0H6J(qC6jWxN)-f0g2tNthEh^R2FNI%LK0DEh;k&0t)yJcQiRGT#~e)2u{w|o zTjb6u^u1T)S&&W^IdG+LYv^v%LMtK|R{|c2%!NEU266hRz4A)9Y#QDeiG NRaYj^>&|5&`WNI(u9^S< literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/seeded.nnc b/wai/live-conformance/prior-forms/files/seeded.nnc new file mode 100644 index 0000000000000000000000000000000000000000..5faf3a46a02dcadd85f8ae57c84baf0be5d82e5a GIT binary patch literal 883 zcmZQzVParlV{2e&V6fn0V5n9qPt4Ry%FIj2%u83YQYtSlGSoLz(g6z>6y>L7=A?pz z6Ek%)^Gbj+wG0emVvLE&CGi=V42cI?9eNpr9hfiwVbB$0%t@?FEz&ap8kbnika(CC zD96CSAp*ox7?|cTFmP?zAnnD#yuN}#1EDy#G^YfrSlB_h-l80ryH<*AwJ86^w}7cLYOBs%soFp6^dN;7M%{UBzLas5xVYGjGG zflSVeN3XfBon-3U)ilo|U~Y=y%`C$QI}O)eMj zUOur?;_11FhXxGRFsqE9E;e z!JK_@!US;C2!}8RXipIk+;U{qs&0eE3NiMJVLunxuRNvfRhlE;yX2}>jB82Og5{C? z6&oIU_^vU$(x;u_IAh^t!>dn{FMSr0xN)O;@7enYEWez%_2Bst8_D-K+piYzgv8{% zm+ZV%@%H=Hx#n+lve&5MY=sdD90~eG52`i(?^VD zzXHOUn{5=<-+r@U@*?}?4o}w<%$RdvM&0af559_Tl6?GHp>w|IrEM*JA@h&S>kfQ4 ze?fV~;^|8#U0%Fx_N@gQ?i_q5I!E4K_~?g?%oEieURxB_M{HEu`iVW^Q>C8unRMZu M`%f*sSn~Y=0Hc*pJ^%m! literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/seeded.safetensors b/wai/live-conformance/prior-forms/files/seeded.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..bc1a65a0e81d9c47d07438a4188f9069b72f368b GIT binary patch literal 1767 zcmb7@$!;4*5Qcm2jt^N31PPE$AhP%{ra42AvitnV?iW-%uILHH&uW2{3Y4;cRgAxo}Fx#$IH!fu^2tM9$hV0 z<6l-6$E%B{qbH-Q^=1309W~qK7njdZSLY}F@^bZXb+N(Dt7f$Pb+b5I^|{BJmoNJ5 zAKE!m)@RH9>F4&S8EyH`Pfyn;n>CguO?QOM^X1Ev%kc#7TVi#PKkWwj^PBly)6Iu~ zXTP0q?gp4Qv&oRKK3kps7x8#d!j2YZlV;Wq30Ehpr)U3ua9#g?|) z;mg6KnYNGL_SB8v?aM9*0L^qhXsR3E*%;)5aWE|87Gv73+24I$z-bN+G3-}+dwZ3s zQtLJJQ?2y~*FAjKKXS$%;QA2y68aeW1o{lRmxQp4R25zas?nLG1WBH)ipeUSqGr-a zM@5pjwoR!NtGLJNp%G+pI zG&RFlxJ2A@D=ksrj7J|7*I5Qqg$qSNRWg;T3B?-$UJj)&!E~`9NNZx|K{6$&5(_1} z;(4xwiAlj&F-*%+IEBC}A(~YfW*PG~7$-oi8Kd_oxcI_#int5pu$1N}5nNqCZ z&W;CRWah%L>IpsOXtyCLWH>-d*x&;5Orj}6Jn2lr_{f>dRYf12O44V8;8ZodGXiJI z3bK`v3>qGolxitBIjXplm{TS>96}xwS&SNWS}(1zf|;la&TnNt23LhOp@t~>&KS;- IF_DP=1qNS#5&!@I literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/title.gguf b/wai/live-conformance/prior-forms/files/title.gguf new file mode 100644 index 0000000000000000000000000000000000000000..cae2cd2d0adef2b59a5bbc2e042f5b0aee18e417 GIT binary patch literal 5792 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 QlDhW!7U2v1s#2i-6S2T$HUIzs literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/title.nnc b/wai/live-conformance/prior-forms/files/title.nnc new file mode 100644 index 0000000000000000000000000000000000000000..ffe95611593fbd7136b4fc2c5faee0ca2fed9999 GIT binary patch literal 4763 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 Mw+3GvR+R$v-ztVV9RL6T literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/wide.gguf b/wai/live-conformance/prior-forms/files/wide.gguf new file mode 100644 index 0000000000000000000000000000000000000000..c17af51c7e28fdd708e04ec1649cc212211faf23 GIT binary patch literal 1440 zcma)5NoZ3+82-lE#RWHds9Wp7R0WMm1eco&+A4|*h~Po`n!YBlq=|hd78j}_6-9a| z9;&5Q!3__hBH~g7QLHGa2t_M`8zLThsn&XEeV;SQ4AoS@fqCCQGvEBn_s=UU+q^0V zz-T$uP_`O{89>C2+pZP!TW&b&B<*lA<=O@E%<$?hXN;yw(_iVttDJa5G4jlIv^F?D z_&aI4o2YhTX*Z(`$#Uj7@#OrVxfQjoD%%zE*hHGDsS3tvJ7(>&U4I}Dkb$ioiak=F zDE16(4`+uH@tumDs~L*DRNKSZwW(M#+xLFWo}h6klX}~UM3X8HlexhWnrm{LEZ3*? zUUt-=-?d{Y6;(|&isW3X7uoF4U+b^X?D6UQfXOlBSM{e$GIA>c_W#rQ-vqd)+1Ubw z{Qnlf6mC!n@Lv1TIc9@;Nott)IX1KW^DA9k9%On{Yka8PW@~PGVWJxR)8WvnTnApJ zpUPvMdy8&C0)?Um4m((k&v=SF@x$xH2XdkhR=NTuY=;jI=wU94#3G(TA3mT0-#s5(EMN{4 zx1kZQxQ??4F^(@-$^pJ)1SfDzX7h%fSb^iXCOfuE#*H8adI2oNB&mOn^tGFlMIn2= zgSd{%T#I~hfbXTxCays{&!J9cxLOKrVV!&n!#(HT=f3AXT*M%>nhj-T zHI9HPg0io}@g z3mma3X2qf`;fo|G=4|sqnkmcg@H9k7%2|LiE$&JJB*d~1V*U?*v!z0#Es_#+II^#Z zN!u5TkqE_NixTtCCQE{1$%zv4cbBb8yDUkHJ!nwN=EU$fK$0F;=`j=p&_Bn}a_aG^ z%mvqqa6qgc_3s3|{3jOb;QYAo!RPvguH^9EhZIo_H;0&puXu@gF{2G*oFiC`X^d&z zEaXG(<{4&ihM%=qcH#lHi%cPviyXA`3Vn>{CEmn5-)XOz&jZkq%l%Bnob~}-oM1aU z=)?d=m?2)F6Js!d`oTj9;r$BUXEVOx2P{%*0n*uv3_PI*h3iB%FJlVhIEtTI2K=zF3mrIzTRh1V z*oSURz>8E4qM0w4#!Ye{ablDWyvi7~@ia0yjU+LMYHk%1+D9!H6|(hm#PK#-B2>vmulW~7zMb1JJRwd`K}8Mpk{;}Yo&oP zl69QxMG7aiHrz!&_aZ@@;wQ=M;2!ky8tSCNB5ACOVmahK_92l0x_DiV=pf8&#UtFn NSq>phlyDAH+Arwbzlxk!%fsp8p# z7X`6`(%uR!h5iAh(0@Qt4_4qao%r zGA4+!o}8*^Rn}pOwx!Y-TP+MQud1dF9oKcKuv$yH$JsU5;V!KN>yfofIh%Z*ZJo($ z_3E|z?p0ds9%8(`N<$mJ5Xh>nvjYa$q;v0QMnTvLG)1qK~+ZM={*Ye=v?5E1#r1uccLy1x1%qf;WkoZ1Bg~ z;fw6ckKz4vY8Jl27r20r_!&;yXYFS&4GlQQCg$KZy+#qg!9eN~ukaZ2@V7mO85qVF zkjEbQ4Nu?*f8iONz(072-*E~b!zOOyTR6bMl#4}Jg?IP?CvgjtceCr~uVS^_ig9l# hMwtslW~jLz)c8*t6V&*7!d=o1X{7IKoD;}<{RctyhSC54 literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/world_g_s.nnc b/wai/live-conformance/prior-forms/files/world_g_s.nnc new file mode 100644 index 0000000000000000000000000000000000000000..5b85c718520f676565e5f3877c83c0178546b470 GIT binary patch literal 474 zcmZQzVParlV{2e&V6fn0V5n9qPt4Ry%FIj2%u83YQYz2LHPkm$(g6z>6y>L7=A?pz z6Ek%)^Gbj+wG0g6VvHH7i7BZ?42cI@9eNprLs%OP)-WiGG3F#zrWWZL7#ILeOe}^< zGcY@eExN*_DHOpVkE|p)Kd-EWA@MLP&}7&Kon zFlYn|Fib~QSDu=go>9V(a3P?eAkh)%8V5!}2L??BhQ4nMjISQB$y{KKKET4ffMpgx zLzjS{_kr359p8oR3C90Y5*co37DzcTXDLSR?<{HN`o6rtVYz%G^L1AZt4XT_lrq&F zwpOf4ogT+n*O)oWL?ce?bI6Acu~#407=JRX;cNJ~>aV_VW~YYYyHCYH=W8LHWvG{w zT9SxxKFh-r1_s8dr+66{%8to1SR<=UPt46tWJs6*bv?fT1D7HLgY4H52Fc(93_1=B gwPzSY);1VgI=DAJV30=Enwg%G$B=jc6ba&X0ORPBmjD0& literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/world_g_s.safetensors b/wai/live-conformance/prior-forms/files/world_g_s.safetensors new file mode 100644 index 0000000000000000000000000000000000000000..de8f3db050589d8c134443eeb93dfbc6559da455 GIT binary patch literal 928 zcmXS5VgQ3`rTF;V)RM%M#FE7LcqOZ9rSil~y`;>%l+3(zB`c-!j9f!~LnR%sa6wUi zT4qivNH{T5H#4sUC{wGWl#!a4l3E1Pn^IC)0Fw4JGXZKT&PW9DqK#v9l)&car==CA zmJ|bp40Mc4Vu4z75-U@S^b8CPfL0~~B}g#B$VA7)0@aM<{JgRf+=iH8y2Hdm$I=Yd zklfN7JZ50=i=~;4p^*WqA?2x==^3cT7@8pkiII*m2x2-2Xp)YR5sJSI^^$-QgxgzK zOaYppV`OTC>?T9K^u*j;RAY>dker0oIY2{nj4aTclbN29hub-Zn4SWfpko9I=UN30 zNU~$#g3>%t`pbU^FoyECFhdy7WXb^L!fBW|BUC+%uH%LXEPz_Hk`uz0VKiab&#;f- zIm71vyBQw;d-_kFL6f1J;o1Mn|HceKKy$tSA7tQTeEt8=|Be668NU9r|F6p+{9lE^ z{67c72L>^Q4gatHf6DNP;Twa`|0Dl3{_`?S_&?`=2gB|qn23i0B literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/files/world_t.gguf b/wai/live-conformance/prior-forms/files/world_t.gguf new file mode 100644 index 0000000000000000000000000000000000000000..c4eb7ad7c5d472b219dff9de593c37937f045601 GIT binary patch literal 1392 zcmZ{iTS!z<6o&tknq9O)l#;QME@EYK@x?A6K4lM9L|9akje49pHjXiksSj2!h6NcK z^&}z$k`!SS6cHg<5k5$Cks>OBpn@8(6hh+s?LD)%%9-47&bsVxueJ8ttGv8&R|MxVzw z7djbfs<%S%FpD+hx3*g0sK=*I1x$ zI*!jBGW{poWZDI4D}E(qIEyTvX~0~%Y+m|*4LCk8ts5{mcE2*54JdZcHb7P{mzMhC zI*x3>ur}gk(oas3TzWn;66VZ*h>3MXc}A2x_&4Eq_FTV(E=kLofDR=?0b@!QsylS6 zLV7ufH}(MEA>VMZ$k@PM#Oy7`Cl+uIr{Kd&?iUU4-~lG>R($7SjIsx#Sd9zt;x-3Z zig9}y71)a!^xzEF8*Yr_24CZ+usSh>DZ2t0{AC}-CN!awrA*^0t~A=2$7^uIg>UG? zC9LILu0b~kxs4O{7yB3w@d^4^$Q*uTws8?r#@NNp2p|_lMizE(k~P?9+{Zn>=MPNd zyojC3bhe^|V6y>L7=A?pz z6Ek%)^Gbj+wG0g6VvHH7i7BZ?42cI@9eNprLs%OP)-Y&@G3F#zrWWZL7#ILeOe|(d zJj4o=W?&E~`@ppN1NWzCj8&_DFvuaROU}GP>|>dvQ;GVgMjM+MhPJXuHp&oP7DlJ z_dXC`cYy762aD4KMk5B9GZPf080LC#m>%F4VN_HXV`QD-xLGZI;)awSdF{zPTfEqJ zxGiT;*YoI}+!43H+;YC!asPSAE1yp<61bCgW&`6V?Ub+0vIJ z8&yNp-fh-eUOAa_ac7p@o{QJoxaZH;y~-HtaPWm&si103rz?lJeaM%GFTSmM&90en ztIhcQ(Q}<0kNJD?%se?^`np|yJDF2rS&c$}&aTTgsj1h#x5&$Vg-OMv+Y3(ZVYGhU5zxZz zsG`(*|BZe2p>*}a!zra30w-)MVXZl}+HL-q7L_M~e^ibwK5>9S5fPe(dO4|y*_FWP z1;wAR1H(cA#{b9)jG*xgmSoHfh0v31w}*zmV^%y{gEOnf}nz0uoOb#evR%GTz5BVUe25|bAEHqcNnB#es!r{ zZ-Xyn)|esF>s3p;)OIuAs17vN1RCqqQnlURpcm?@m=8C%G}Q)zzPYd&*b!(9$&;{9 z{XVnC*D{w|6Y6Z9JKp1V%9K{WIpI=2XkR}Xgl}rtqoxT=_rfKp? zQywHkIO7q9Q@Gr*j1x_br;;;V@eG$+c$P34+Jd3vj0AOf+(I`>Vhd^a1?v2<7~Q={ z5m~w@5(#vXB;hQHq3MpG&pg?gye1>QFNqK?EkZQyOj@%j$39e}AE&uVabObH`3gUz*MkwvSmnszPpbl3 z(S#nBF^y}vMhP>QSKvSnzG4s;u#tDT0eu{138$>j)^Q%;6AZF|+5EuO%6WtsVK29# z4(lM4ER=GZ)!3rk!(G1Pcg*6fWT!Hn9Vq66d|yL0hFQxg2Jwqt^zsujm3kKQGW&T| z_ArKSoZ(K4GAy$mBZ4e>JHksigfP1qu~fw?t81aoLOeqoU*aamu%Ea2p4V6@HQdb^ zYs&Jo5?}bndc+4fh#Smf4sw-;NKr1Xhz@39zM1xX>zB;Yz7{t}`x-o%(cP}}sLkhZ OJY}x(9J4Hz-Twvn!NI=( literal 0 HcmV?d00001 diff --git a/wai/live-conformance/prior-forms/vectors.json b/wai/live-conformance/prior-forms/vectors.json new file mode 100644 index 000000000..7f187c7da --- /dev/null +++ b/wai/live-conformance/prior-forms/vectors.json @@ -0,0 +1,1070 @@ +{ + "about": "the conformance corpus of extensions/prior-carriage.md §14, written by wai_prior_forms_vectors gen", + "natives": [ + { + "name": "generic", + "binding": "wsr1/1", + "path": "../upscale/priors/generic.wsr", + "sha256": "0106ab198efcbc009a32c4fbf85da4a557daf827d1f92d8de9c7939953329915" + }, + { + "name": "title", + "binding": "wsr1/1", + "path": "../upscale/priors/title.wsr", + "sha256": "9ea90833e37dc07982682799abf95840b32fa4e152eebc1830776a5b0c843a07" + }, + { + "name": "seeded", + "binding": "wsr1/1", + "path": "../upscale/priors/seeded.wsr", + "sha256": "18b634abf31169c3c5126e258da73ca6cb0c4ba219220fca6f7736f869b94807" + }, + { + "name": "hyper_g_s", + "binding": "whm1/1", + "path": "../../ops-conformance/sets/hyper/g_s.whm", + "sha256": "fbd6b941d412e7bd0bc73fef90e4131780942aeb06b441abe887b4eb75768d9e" + }, + { + "name": "hyper_h_s", + "binding": "whm1/1", + "path": "../../ops-conformance/sets/hyper/h_s.whm", + "sha256": "0aad151f7f4a27f9c28fa3452692642802a9146ac102fcf664fdf182c80670a7" + }, + { + "name": "world_t", + "binding": "whm1/1", + "path": "../../ops-conformance/sets/world/T.whm", + "sha256": "a47ad9f8d12f8c0e8edde8e61cabe31794a8d7cdb6a4768c95db6a5816713c02" + }, + { + "name": "world_g_s", + "binding": "whm1/1", + "path": "../../ops-conformance/sets/world/g_s.whm", + "sha256": "1c13bee0387fd312580e44ad5f726ada6bd8f6cd06546dd92d5d66a72d9ab3e1" + } + ], + "files": [ + { + "file": "files/generic.gguf", + "sha256": "ad6d2846fd69eacc63ea2f84f85c2048c46f33bb5b8f45341feb2733289b0df0" + }, + { + "file": "files/generic.nnc", + "sha256": "efe969a8d3f7d1ad332d949c620ea2168641d1a2a589cd454563f80abac9c481" + }, + { + "file": "files/generic.safetensors", + "sha256": "b081e1b38970f3f94614ace86af7c1d2bc4f4f015b688e65bf62dcf45a5b6a1a" + }, + { + "file": "files/hyper_g_s.gguf", + "sha256": "8b0dc39e865fb9ea8849591d8d36643c4a6ee5b38c1a1070088e29ae9391b705" + }, + { + "file": "files/hyper_g_s.nnc", + "sha256": "897d1865d4260bd80132b45e4eb39ecfbd8b73532144852c03158bd7ec5aebde" + }, + { + "file": "files/hyper_g_s.safetensors", + "sha256": "fdf5cbe824e2cec68d73dbcd94fe26ab525c35f9663dd8df439f25b3efadebdc" + }, + { + "file": "files/hyper_h_s.gguf", + "sha256": "463b2462a32903b717a9e02431d10d6fa6594f545fc6bb252d05ed674f78f7ee" + }, + { + "file": "files/hyper_h_s.nnc", + "sha256": "31ac4b8f75806fd6905e1706f952c5282a411139f4547ab4b5ae2e6105965d5e" + }, + { + "file": "files/hyper_h_s.safetensors", + "sha256": "a28ae1255cee4bbb48f2a152b6f4e66d926a74c3a74403dfb214b3388838d0f0" + }, + { + "file": "files/natives/badtag.whm", + "sha256": "b93d07c351402117efda3b251b07f39ec5af13d7d834e6f9d054aaf370e6721d" + }, + { + "file": "files/natives/trailing.wsr", + "sha256": "bcc1f34b74a914eada9bcf518037acbe701fea107b8cc70f0efa83fbffaf0b50" + }, + { + "file": "files/natives/wide.whm", + "sha256": "bb76bb82914fd88bcd1f63c91df1c283d93d6cdc67cc9e5fa8ca09d9d6d17391" + }, + { + "file": "files/neg/gg_alignment_explicit.gguf", + "sha256": "a569ea610a379f0b423e2d52b95277d1797c239a2ba08b911c2f9140ce74e178" + }, + { + "file": "files/neg/gg_bad_key.gguf", + "sha256": "66cf367f0352d4a60138b79113dac0ab0a74ec990673de364c0cc90e01f0ee57" + }, + { + "file": "files/neg/gg_bad_magic.gguf", + "sha256": "05261802a2349843d70a6d9795f7143a6f0d832d8f0ff9820d74904041cd82e0" + }, + { + "file": "files/neg/gg_duplicate_key.gguf", + "sha256": "d4f557105493dec1fc8d2f034943d8d1f4fe0ee8e6ea5e2b678df27092da1e21" + }, + { + "file": "files/neg/gg_five_dims.gguf", + "sha256": "d63cddb159cc279ff4c36553cac4b72ec78548dcac61fee7e73aae2084825edf" + }, + { + "file": "files/neg/gg_misaligned.gguf", + "sha256": "0800c3b5616e06576bf59ffd5494b25ae0dc82b212bbf32f8dac427bc0d1dc1d" + }, + { + "file": "files/neg/gg_no_architecture.gguf", + "sha256": "9b885d97a676b43a5d38e66989fc85871c2eee47c1896eb79fa407ef9d7c53a9" + }, + { + "file": "files/neg/gg_nonzero_padding.gguf", + "sha256": "57f9f489d5195bcd7ba0d42870749afae11b244433ae6bf07fe76f39430c747d" + }, + { + "file": "files/neg/gg_q4_0.gguf", + "sha256": "1af0d7f72f4a9a1cc6051a1ef8ef2915dfd6d6a180bd679b8a68f03216a1f7f1" + }, + { + "file": "files/neg/gg_trailing_byte.gguf", + "sha256": "d54882e2b38c54f52053419de77d36f92abfd95e245ec8b9079a09e32141bf08" + }, + { + "file": "files/neg/gg_truncated_header.gguf", + "sha256": "6627ed7bc1a36f0d76d4e88678d87756c3beacae51ae6d8faefddc34693d078d" + }, + { + "file": "files/neg/gg_version_2.gguf", + "sha256": "e8104801a3c7a18d69be91fe3cc4488ec8e4c1fb037ab11064a3bfc9b3671def" + }, + { + "file": "files/neg/nnc_alignment_bit.nnc", + "sha256": "35621b50372c091a2561b78d16469e2af08b0ad39f5354d1c6d848fdc149613f" + }, + { + "file": "files/neg/nnc_dq_flag.nnc", + "sha256": "820d2ae77035944441bcfaf7cb3a5b7c795ba7419e1a2abb9ad0b0bb403ca12f" + }, + { + "file": "files/neg/nnc_long_size.nnc", + "sha256": "6de09d11983794af3995860475d25d0dfbd73d88fa01b258ebd268fdf07f1c3d" + }, + { + "file": "files/neg/nnc_lps_unit.nnc", + "sha256": "505a8c7e52799922dd3961fd5a8134e79aecfe037ac7417959946b76d5d93752" + }, + { + "file": "files/neg/nnc_payload_bit.nnc", + "sha256": "56b82bcf7195c8607fd9c35ca1d331c1d94f83c8488073a993df2766e2e74f38" + }, + { + "file": "files/neg/nnc_pt_float.nnc", + "sha256": "d66cbc3d2c1c7efed38d7e08eb8f565dd6c77dcf0ba40bb2f0953eeead09d72e" + }, + { + "file": "files/neg/nnc_raw_float.nnc", + "sha256": "213b060e32f1c0f64cc9b91f2d449cde7833814f12b5276f1d0a60408edd2392" + }, + { + "file": "files/neg/nnc_starts_mps.nnc", + "sha256": "be0ab64ae8460cea79bbc8e4559266bd3efd5d3375d2932e1e43d9371955e8d7" + }, + { + "file": "files/neg/nnc_truncated.nnc", + "sha256": "5fdc7a5d5aab4aec9cfbd69e2bed1e7c4641c5c3e8239a90fbb525760353552c" + }, + { + "file": "files/neg/st_binding_unknown.safetensors", + "sha256": "6a7cc2ba82ca1a8d711e64d3b2e012354bf4c0302f06ad0b504af17b7773830b" + }, + { + "file": "files/neg/st_dtype_unknown.safetensors", + "sha256": "2aa91a67af319b6d30c78342af507e564ded30215064371597c807f93cab115e" + }, + { + "file": "files/neg/st_duplicate_member.safetensors", + "sha256": "a7226370b44e37789537eaa6852956900c6c3f8b8aa830968320385605fa40c5" + }, + { + "file": "files/neg/st_extra_member.safetensors", + "sha256": "91ef66dc5667c6207b5c33fd0e844d32ccfc9a261add3da072d47a8378648063" + }, + { + "file": "files/neg/st_float_tensor.safetensors", + "sha256": "243826cc3c57ab23937391295b033b382dd48bb77ba508b54b69611094258dba" + }, + { + "file": "files/neg/st_header_past_end.safetensors", + "sha256": "08fb6306fb5b2149b42192d5d428e7c9c884f5828733346bfb7742cc0dcd10c1" + }, + { + "file": "files/neg/st_header_too_large.safetensors", + "sha256": "51a28da99f1229dd77c2708f33db647a78bc8d83e5a7529352198866c4235e25" + }, + { + "file": "files/neg/st_leading_space.safetensors", + "sha256": "8b01c293604b363ba3488a8c898e2abef3f351cceb1511922cc82fba850bb14b" + }, + { + "file": "files/neg/st_member_order.safetensors", + "sha256": "24b250ab102dfb308ec8d0627360a85d1f54c86cba826b79e370312255f64773" + }, + { + "file": "files/neg/st_missing_tensor.safetensors", + "sha256": "aa96dea78109992a03f18d25151a5bbb585c3a4c495809edd6d498f8afc5529c" + }, + { + "file": "files/neg/st_mult_zero.safetensors", + "sha256": "7d5f2d1eb0c5f466f293f98f04cd8732582abef2967fb0e8249272f8f9323e06" + }, + { + "file": "files/neg/st_not_json.safetensors", + "sha256": "812eda3e1d8d9d3179689ed8e641db5976d8c33b5be90c88afcd56b42cc79ee6" + }, + { + "file": "files/neg/st_offset_gap.safetensors", + "sha256": "2c4b461f8a7cceaefeeb56530e6beaafea87c11192d743f589d5866cd3e8c151" + }, + { + "file": "files/neg/st_profile_absent.safetensors", + "sha256": "c05fec31a5d8c08a4fdc616bc943c7a73c449e5949cfdc3d1971742b2f6dd725" + }, + { + "file": "files/neg/st_shift_range.safetensors", + "sha256": "87b3a803acd14e32a1d0141949ad31ddeb5aedb5977e6875334f72b0abf70932" + }, + { + "file": "files/neg/st_truncated.safetensors", + "sha256": "265fb7b38f2cce18cbffe3676e4a562c96a1c504847bfe4ef8957ae915872402" + }, + { + "file": "files/neg/st_wrong_shape.safetensors", + "sha256": "db58eec6889c155c710d3a740d240bc85d16c4f769aaba6e9a1b88f00922f209" + }, + { + "file": "files/seeded.gguf", + "sha256": "f4745bce30ec828de55c7ac248268b1edf84bb127d5819114ef45b4650e61f5a" + }, + { + "file": "files/seeded.nnc", + "sha256": "ee84265568718c58c0347e3371fe5e9d2bf24e6da16a2c6e13195a6185e532f0" + }, + { + "file": "files/seeded.safetensors", + "sha256": "438d10637b2c98e641858a140ebfdab1511b11efb0cd5a644df3c405e001e33f" + }, + { + "file": "files/title.gguf", + "sha256": "c7493e72b701b173997e1ae99d6e40c8ebf84fec6c3ed9f95fcb6162f5ec00f0" + }, + { + "file": "files/title.nnc", + "sha256": "e2650a0d57876899313bddf1f4bb6895af94cc4eec4e1162a20dc7891c34120c" + }, + { + "file": "files/title.safetensors", + "sha256": "266e8783329877c532de74650843b3c2ef635d64e8ed5188ab7ad5e8cd8a331c" + }, + { + "file": "files/wide.gguf", + "sha256": "7a48ebf67719582921621d496b7395c4e8d0a7ad2b61141ece7328d1c453ea83" + }, + { + "file": "files/wide.safetensors", + "sha256": "9b2a240a96a020ec3e5ce1e449cfd2cc382d580d869730cdfeeda3f92a5265b6" + }, + { + "file": "files/world_g_s.gguf", + "sha256": "528b0903ba96334091246a23c56b5fc5601ebc3dc37e861a3c2b3a017b2a75ba" + }, + { + "file": "files/world_g_s.nnc", + "sha256": "d9911c4b82e5761c8db4358d03b65d62dab26603f9a3b092cde0607389785233" + }, + { + "file": "files/world_g_s.safetensors", + "sha256": "1cef3155af9afad71e2f5a5d79d52b745bab145f7c3ce712a148dda0e0a9320f" + }, + { + "file": "files/world_t.gguf", + "sha256": "f60a4c2a2fbac1ccc5df5f14493a4500b38cc5a55d55f0c84ff85c5df1b3ad54" + }, + { + "file": "files/world_t.nnc", + "sha256": "0cb7da71380911f0a0f3c30e41b4eb499a9c5a675dce78ae07b1122f2b35f283" + }, + { + "file": "files/world_t.safetensors", + "sha256": "4043ae26fc6119be870ad07103997a4180b2019994791faf1b2c5b28584f2035" + } + ], + "vectors": [ + { + "name": "generic_safetensors_encode", + "op": "encode", + "form": "wai.prior.safetensors", + "native": "generic", + "binding": "wsr1/1", + "outcome": "accept", + "file": "files/generic.safetensors" + }, + { + "name": "generic_safetensors_load", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/generic.safetensors", + "outcome": "accept", + "binding": "wsr1/1", + "native": "generic" + }, + { + "name": "generic_gguf_encode", + "op": "encode", + "form": "wai.prior.gguf", + "native": "generic", + "binding": "wsr1/1", + "outcome": "accept", + "file": "files/generic.gguf" + }, + { + "name": "generic_gguf_load", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/generic.gguf", + "outcome": "accept", + "binding": "wsr1/1", + "native": "generic" + }, + { + "name": "generic_nnc_encode", + "op": "encode", + "form": "wai.prior.nnc", + "native": "generic", + "binding": "wsr1/1", + "outcome": "accept", + "file": "files/generic.nnc" + }, + { + "name": "generic_nnc_load", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/generic.nnc", + "outcome": "accept", + "binding": "wsr1/1", + "native": "generic" + }, + { + "name": "title_safetensors_encode", + "op": "encode", + "form": "wai.prior.safetensors", + "native": "title", + "binding": "wsr1/1", + "outcome": "accept", + "file": "files/title.safetensors" + }, + { + "name": "title_safetensors_load", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/title.safetensors", + "outcome": "accept", + "binding": "wsr1/1", + "native": "title" + }, + { + "name": "title_gguf_encode", + "op": "encode", + "form": "wai.prior.gguf", + "native": "title", + "binding": "wsr1/1", + "outcome": "accept", + "file": "files/title.gguf" + }, + { + "name": "title_gguf_load", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/title.gguf", + "outcome": "accept", + "binding": "wsr1/1", + "native": "title" + }, + { + "name": "title_nnc_encode", + "op": "encode", + "form": "wai.prior.nnc", + "native": "title", + "binding": "wsr1/1", + "outcome": "accept", + "file": "files/title.nnc" + }, + { + "name": "title_nnc_load", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/title.nnc", + "outcome": "accept", + "binding": "wsr1/1", + "native": "title" + }, + { + "name": "seeded_safetensors_encode", + "op": "encode", + "form": "wai.prior.safetensors", + "native": "seeded", + "binding": "wsr1/1", + "outcome": "accept", + "file": "files/seeded.safetensors" + }, + { + "name": "seeded_safetensors_load", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/seeded.safetensors", + "outcome": "accept", + "binding": "wsr1/1", + "native": "seeded" + }, + { + "name": "seeded_gguf_encode", + "op": "encode", + "form": "wai.prior.gguf", + "native": "seeded", + "binding": "wsr1/1", + "outcome": "accept", + "file": "files/seeded.gguf" + }, + { + "name": "seeded_gguf_load", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/seeded.gguf", + "outcome": "accept", + "binding": "wsr1/1", + "native": "seeded" + }, + { + "name": "seeded_nnc_encode", + "op": "encode", + "form": "wai.prior.nnc", + "native": "seeded", + "binding": "wsr1/1", + "outcome": "accept", + "file": "files/seeded.nnc" + }, + { + "name": "seeded_nnc_load", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/seeded.nnc", + "outcome": "accept", + "binding": "wsr1/1", + "native": "seeded" + }, + { + "name": "hyper_g_s_safetensors_encode", + "op": "encode", + "form": "wai.prior.safetensors", + "native": "hyper_g_s", + "binding": "whm1/1", + "outcome": "accept", + "file": "files/hyper_g_s.safetensors" + }, + { + "name": "hyper_g_s_safetensors_load", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/hyper_g_s.safetensors", + "outcome": "accept", + "binding": "whm1/1", + "native": "hyper_g_s" + }, + { + "name": "hyper_g_s_gguf_encode", + "op": "encode", + "form": "wai.prior.gguf", + "native": "hyper_g_s", + "binding": "whm1/1", + "outcome": "accept", + "file": "files/hyper_g_s.gguf" + }, + { + "name": "hyper_g_s_gguf_load", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/hyper_g_s.gguf", + "outcome": "accept", + "binding": "whm1/1", + "native": "hyper_g_s" + }, + { + "name": "hyper_g_s_nnc_encode", + "op": "encode", + "form": "wai.prior.nnc", + "native": "hyper_g_s", + "binding": "whm1/1", + "outcome": "accept", + "file": "files/hyper_g_s.nnc" + }, + { + "name": "hyper_g_s_nnc_load", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/hyper_g_s.nnc", + "outcome": "accept", + "binding": "whm1/1", + "native": "hyper_g_s" + }, + { + "name": "hyper_h_s_safetensors_encode", + "op": "encode", + "form": "wai.prior.safetensors", + "native": "hyper_h_s", + "binding": "whm1/1", + "outcome": "accept", + "file": "files/hyper_h_s.safetensors" + }, + { + "name": "hyper_h_s_safetensors_load", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/hyper_h_s.safetensors", + "outcome": "accept", + "binding": "whm1/1", + "native": "hyper_h_s" + }, + { + "name": "hyper_h_s_gguf_encode", + "op": "encode", + "form": "wai.prior.gguf", + "native": "hyper_h_s", + "binding": "whm1/1", + "outcome": "accept", + "file": "files/hyper_h_s.gguf" + }, + { + "name": "hyper_h_s_gguf_load", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/hyper_h_s.gguf", + "outcome": "accept", + "binding": "whm1/1", + "native": "hyper_h_s" + }, + { + "name": "hyper_h_s_nnc_encode", + "op": "encode", + "form": "wai.prior.nnc", + "native": "hyper_h_s", + "binding": "whm1/1", + "outcome": "accept", + "file": "files/hyper_h_s.nnc" + }, + { + "name": "hyper_h_s_nnc_load", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/hyper_h_s.nnc", + "outcome": "accept", + "binding": "whm1/1", + "native": "hyper_h_s" + }, + { + "name": "world_t_safetensors_encode", + "op": "encode", + "form": "wai.prior.safetensors", + "native": "world_t", + "binding": "whm1/1", + "outcome": "accept", + "file": "files/world_t.safetensors" + }, + { + "name": "world_t_safetensors_load", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/world_t.safetensors", + "outcome": "accept", + "binding": "whm1/1", + "native": "world_t" + }, + { + "name": "world_t_gguf_encode", + "op": "encode", + "form": "wai.prior.gguf", + "native": "world_t", + "binding": "whm1/1", + "outcome": "accept", + "file": "files/world_t.gguf" + }, + { + "name": "world_t_gguf_load", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/world_t.gguf", + "outcome": "accept", + "binding": "whm1/1", + "native": "world_t" + }, + { + "name": "world_t_nnc_encode", + "op": "encode", + "form": "wai.prior.nnc", + "native": "world_t", + "binding": "whm1/1", + "outcome": "accept", + "file": "files/world_t.nnc" + }, + { + "name": "world_t_nnc_load", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/world_t.nnc", + "outcome": "accept", + "binding": "whm1/1", + "native": "world_t" + }, + { + "name": "world_g_s_safetensors_encode", + "op": "encode", + "form": "wai.prior.safetensors", + "native": "world_g_s", + "binding": "whm1/1", + "outcome": "accept", + "file": "files/world_g_s.safetensors" + }, + { + "name": "world_g_s_safetensors_load", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/world_g_s.safetensors", + "outcome": "accept", + "binding": "whm1/1", + "native": "world_g_s" + }, + { + "name": "world_g_s_gguf_encode", + "op": "encode", + "form": "wai.prior.gguf", + "native": "world_g_s", + "binding": "whm1/1", + "outcome": "accept", + "file": "files/world_g_s.gguf" + }, + { + "name": "world_g_s_gguf_load", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/world_g_s.gguf", + "outcome": "accept", + "binding": "whm1/1", + "native": "world_g_s" + }, + { + "name": "world_g_s_nnc_encode", + "op": "encode", + "form": "wai.prior.nnc", + "native": "world_g_s", + "binding": "whm1/1", + "outcome": "accept", + "file": "files/world_g_s.nnc" + }, + { + "name": "world_g_s_nnc_load", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/world_g_s.nnc", + "outcome": "accept", + "binding": "whm1/1", + "native": "world_g_s" + }, + { + "name": "trailing_safetensors_encode", + "op": "encode", + "form": "wai.prior.safetensors", + "native_file": "files/natives/trailing.wsr", + "binding": "wsr1/1", + "outcome": "refuse", + "code": "native_malformed" + }, + { + "name": "trailing_gguf_encode", + "op": "encode", + "form": "wai.prior.gguf", + "native_file": "files/natives/trailing.wsr", + "binding": "wsr1/1", + "outcome": "refuse", + "code": "native_malformed" + }, + { + "name": "trailing_nnc_encode", + "op": "encode", + "form": "wai.prior.nnc", + "native_file": "files/natives/trailing.wsr", + "binding": "wsr1/1", + "outcome": "refuse", + "code": "native_malformed" + }, + { + "name": "badtag_safetensors_encode", + "op": "encode", + "form": "wai.prior.safetensors", + "native_file": "files/natives/badtag.whm", + "binding": "whm1/1", + "outcome": "refuse", + "code": "native_malformed" + }, + { + "name": "badtag_gguf_encode", + "op": "encode", + "form": "wai.prior.gguf", + "native_file": "files/natives/badtag.whm", + "binding": "whm1/1", + "outcome": "refuse", + "code": "native_malformed" + }, + { + "name": "badtag_nnc_encode", + "op": "encode", + "form": "wai.prior.nnc", + "native_file": "files/natives/badtag.whm", + "binding": "whm1/1", + "outcome": "refuse", + "code": "native_malformed" + }, + { + "name": "wide_safetensors_encode", + "op": "encode", + "form": "wai.prior.safetensors", + "native_file": "files/natives/wide.whm", + "binding": "whm1/1", + "outcome": "accept", + "file": "files/wide.safetensors" + }, + { + "name": "wide_gguf_encode", + "op": "encode", + "form": "wai.prior.gguf", + "native_file": "files/natives/wide.whm", + "binding": "whm1/1", + "outcome": "accept", + "file": "files/wide.gguf" + }, + { + "name": "wide_nnc_encode", + "op": "encode", + "form": "wai.prior.nnc", + "native_file": "files/natives/wide.whm", + "binding": "whm1/1", + "outcome": "refuse", + "code": "value_out_of_range" + }, + { + "name": "st_truncated", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_truncated.safetensors", + "outcome": "refuse", + "code": "header_truncated" + }, + { + "name": "st_header_too_large", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_header_too_large.safetensors", + "outcome": "refuse", + "code": "header_too_large" + }, + { + "name": "st_header_past_end", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_header_past_end.safetensors", + "outcome": "refuse", + "code": "header_truncated" + }, + { + "name": "st_duplicate_member", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_duplicate_member.safetensors", + "outcome": "refuse", + "code": "header_malformed" + }, + { + "name": "st_not_json", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_not_json.safetensors", + "outcome": "refuse", + "code": "header_malformed" + }, + { + "name": "st_extra_member", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_extra_member.safetensors", + "outcome": "refuse", + "code": "tensor_malformed" + }, + { + "name": "st_dtype_unknown", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_dtype_unknown.safetensors", + "outcome": "refuse", + "code": "dtype_unknown" + }, + { + "name": "st_offset_gap", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_offset_gap.safetensors", + "outcome": "refuse", + "code": "offset_mismatch" + }, + { + "name": "st_leading_space", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_leading_space.safetensors", + "outcome": "refuse", + "code": "header_not_canonical" + }, + { + "name": "st_member_order", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_member_order.safetensors", + "outcome": "refuse", + "code": "header_not_canonical" + }, + { + "name": "st_profile_absent", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_profile_absent.safetensors", + "outcome": "refuse", + "code": "profile_absent" + }, + { + "name": "st_binding_unknown", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_binding_unknown.safetensors", + "outcome": "refuse", + "code": "binding_unknown" + }, + { + "name": "st_float_tensor", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_float_tensor.safetensors", + "outcome": "refuse", + "code": "dtype_not_integer" + }, + { + "name": "st_missing_tensor", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_missing_tensor.safetensors", + "outcome": "refuse", + "code": "binding_mismatch" + }, + { + "name": "st_wrong_shape", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_wrong_shape.safetensors", + "outcome": "refuse", + "code": "binding_mismatch" + }, + { + "name": "st_shift_range", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_shift_range.safetensors", + "outcome": "refuse", + "code": "value_out_of_range" + }, + { + "name": "st_mult_zero", + "op": "load", + "form": "wai.prior.safetensors", + "file": "files/neg/st_mult_zero.safetensors", + "outcome": "refuse", + "code": "value_out_of_range" + }, + { + "name": "gg_bad_magic", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/neg/gg_bad_magic.gguf", + "outcome": "refuse", + "code": "bad_magic" + }, + { + "name": "gg_version_2", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/neg/gg_version_2.gguf", + "outcome": "refuse", + "code": "version_unsupported" + }, + { + "name": "gg_truncated_header", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/neg/gg_truncated_header.gguf", + "outcome": "refuse", + "code": "header_truncated" + }, + { + "name": "gg_duplicate_key", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/neg/gg_duplicate_key.gguf", + "outcome": "refuse", + "code": "header_malformed" + }, + { + "name": "gg_bad_key", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/neg/gg_bad_key.gguf", + "outcome": "refuse", + "code": "header_malformed" + }, + { + "name": "gg_five_dims", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/neg/gg_five_dims.gguf", + "outcome": "refuse", + "code": "tensor_malformed" + }, + { + "name": "gg_q4_0", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/neg/gg_q4_0.gguf", + "outcome": "refuse", + "code": "dtype_unknown" + }, + { + "name": "gg_misaligned", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/neg/gg_misaligned.gguf", + "outcome": "refuse", + "code": "offset_mismatch" + }, + { + "name": "gg_alignment_explicit", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/neg/gg_alignment_explicit.gguf", + "outcome": "refuse", + "code": "header_not_canonical" + }, + { + "name": "gg_trailing_byte", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/neg/gg_trailing_byte.gguf", + "outcome": "refuse", + "code": "header_not_canonical" + }, + { + "name": "gg_no_architecture", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/neg/gg_no_architecture.gguf", + "outcome": "refuse", + "code": "profile_absent" + }, + { + "name": "gg_nonzero_padding", + "op": "load", + "form": "wai.prior.gguf", + "file": "files/neg/gg_nonzero_padding.gguf", + "outcome": "refuse", + "code": "header_malformed" + }, + { + "name": "nnc_truncated", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/neg/nnc_truncated.nnc", + "outcome": "refuse", + "code": "nnc_truncated" + }, + { + "name": "nnc_starts_mps", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/neg/nnc_starts_mps.nnc", + "outcome": "refuse", + "code": "nnc_malformed" + }, + { + "name": "nnc_alignment_bit", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/neg/nnc_alignment_bit.nnc", + "outcome": "refuse", + "code": "nnc_malformed" + }, + { + "name": "nnc_pt_float", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/neg/nnc_pt_float.nnc", + "outcome": "refuse", + "code": "nnc_not_read" + }, + { + "name": "nnc_dq_flag", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/neg/nnc_dq_flag.nnc", + "outcome": "refuse", + "code": "nnc_not_read" + }, + { + "name": "nnc_lps_unit", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/neg/nnc_lps_unit.nnc", + "outcome": "refuse", + "code": "nnc_not_read" + }, + { + "name": "nnc_long_size", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/neg/nnc_long_size.nnc", + "outcome": "refuse", + "code": "header_not_canonical" + }, + { + "name": "nnc_raw_float", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/neg/nnc_raw_float.nnc", + "outcome": "refuse", + "code": "dtype_not_integer" + }, + { + "name": "nnc_payload_bit", + "op": "load", + "form": "wai.prior.nnc", + "file": "files/neg/nnc_payload_bit.nnc", + "outcome": "refuse", + "code": "nnc_malformed" + } + ] +} diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index e5e2eafc1..7c1824679 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -418,6 +418,20 @@ bearer_receipts = ["key_discovery"] # wai.ai.usage statement), rankings under them, and the disclosure of what steered a pick. # SHA-256, BLAKE3, Ed25519 and ai_usage's statement reader; no new dependency. exploration = ["generate", "ai_usage"] +# Tensor files as prior forms (extensions/prior-carriage.md §9 to §14): `wai.prior.safetensors`, +# `wai.prior.gguf` and the NNC bitstream of `wai.prior.nnc` (the subset §12 reads: integer tensors +# under context-adaptive arithmetic coding, raw 32-bit floats), each read strictly and written +# canonically; the integer-quantised profile `wai-int/1` and its bindings `wsr1/1` (the WSR1 +# prior) and `whm1/1` (the WHM1 model), through which a prior in any of the three loads, bit for +# bit, into the native artifact an integer kernel reads. SHA-256 only; no new dependency. +prior_wire = [] +# Scene carriage (extensions/scene-carriage.md): WAI worlds, audio scenes, mesh assets and timed +# media mapped onto ISO/IEC 23090-14 scene description (glTF 2.0 with the MPEG extensions) and +# onto a USD layer (USD Core Specification v1.0.1, text format), both ways: the export writes a +# document byte for byte, the import reads one back to the same WAI bytes, or, from a document +# WAI did not write, to new ones under stated rules. Reuses world and audio_scene; no new +# dependency. +scene_map = ["world", "audio_scene"] # Timed text and live captions (extensions/timed-text): `wai.text.timed` — the cue model, # live caption revisions (`caption` events of wai.live.events with the WTX1 annotation), # and their exact mappings onto WebVTT documents and segments, the IMSC1 Text Profile, @@ -978,6 +992,16 @@ name = "wai_influence_vectors" path = "src/bin/wai_influence_vectors.rs" required-features = ["influence"] +[[bin]] +name = "wai_prior_forms_vectors" +path = "src/bin/wai_prior_forms_vectors.rs" +required-features = ["prior_wire"] + +[[bin]] +name = "wai_scene_vectors" +path = "src/bin/wai_scene_vectors.rs" +required-features = ["scene_map"] + [[bin]] name = "wai_bearer_vectors" path = "src/bin/wai_bearer_vectors.rs" @@ -1773,6 +1797,16 @@ name = "influence" path = "tests/influence.rs" required-features = ["influence"] +[[test]] +name = "prior_wire" +path = "tests/prior_wire.rs" +required-features = ["prior_wire", "neural_int"] + +[[test]] +name = "scene_map" +path = "tests/scene_map.rs" +required-features = ["scene_map"] + [[test]] name = "bearer_receipts" path = "tests/bearer_receipts.rs" diff --git a/wai/wai-rs/src/bin/wai_prior_forms_vectors.rs b/wai/wai-rs/src/bin/wai_prior_forms_vectors.rs new file mode 100644 index 000000000..a23651ef9 --- /dev/null +++ b/wai/wai-rs/src/bin/wai_prior_forms_vectors.rs @@ -0,0 +1,78 @@ +//! Conformance corpus for the tensor-file prior forms (`live-conformance/prior-forms/`, +//! extensions/prior-carriage.md §14). +//! +//! wai_prior_forms_vectors gen write vectors.json and files/ from the native +//! artifacts other corpora commit, which must +//! match their pins +//! wai_prior_forms_vectors verify the natives match their pins, every file +//! regenerates byte for byte, no other file but +//! README.md is there, and every vector gets +//! the outcome it states +//! +//! The generator and this verifier are one implementation. The corpus's second +//! implementation is `live-conformance/prior_forms_verify.py`, written from the extension's +//! text and sharing no code with this binary. + +use std::collections::BTreeMap; +use std::path::Path; +use wai::prior_wire::corpus; + +fn walk(root: &Path, rel: &str, out: &mut BTreeMap>) -> Result<(), String> { + let d = root.join(rel); + let entries = std::fs::read_dir(&d).map_err(|e| format!("read {}: {e}", d.display()))?; + for e in entries.flatten() { + let name = e.file_name().to_string_lossy().into_owned(); + let key = if rel.is_empty() { name.clone() } else { format!("{rel}/{name}") }; + if e.path().is_dir() { + walk(root, &key, out)?; + } else if key != "README.md" { + out.insert(key, std::fs::read(e.path()).map_err(|err| format!("read {}: {err}", e.path().display()))?); + } + } + Ok(()) +} + +fn natives(dir: &Path) -> BTreeMap> { + corpus::INPUT_NATIVES.iter().filter_map(|(n, _, p, _)| std::fs::read(dir.join(p)).ok().map(|b| (n.to_string(), b))).collect() +} + +fn die(e: impl std::fmt::Display) -> ! { + eprintln!("{e}"); + std::process::exit(1) +} + +fn main() { + let a: Vec = std::env::args().collect(); + match (a.get(1).map(String::as_str), a.get(2)) { + (Some("gen"), Some(d)) => { + let dir = Path::new(d); + let nat = natives(dir); + let bad = corpus::check_inputs(&nat); + if !bad.is_empty() { + die(format!("the natives {bad:?} are missing or do not match their pins in corpus::INPUT_NATIVES")); + } + for (name, body) in corpus::generate(&nat) { + let p = dir.join(&name); + if let Some(parent) = p.parent() { + std::fs::create_dir_all(parent).unwrap_or_else(|e| die(format!("mkdir {}: {e}", parent.display()))); + } + std::fs::write(&p, body).unwrap_or_else(|e| die(format!("write {}: {e}", p.display()))); + } + println!("wrote the prior-forms corpus to {d}"); + } + (Some("verify"), Some(d)) => { + let dir = Path::new(d); + let mut files = BTreeMap::new(); + walk(dir, "", &mut files).unwrap_or_else(|e| die(e)); + let bad = corpus::verify(&files, &natives(dir)); + if !bad.is_empty() { + for b in &bad { + eprintln!("FAIL {b}"); + } + std::process::exit(1); + } + println!("prior-forms corpus: {} files regenerate byte for byte and every vector reaches its outcome", files.len()); + } + _ => die("usage: wai_prior_forms_vectors gen|verify "), + } +} diff --git a/wai/wai-rs/src/codecs/glossary_check.rs b/wai/wai-rs/src/codecs/glossary_check.rs index ed6b91a66..58bdbe42e 100644 --- a/wai/wai-rs/src/codecs/glossary_check.rs +++ b/wai/wai-rs/src/codecs/glossary_check.rs @@ -122,6 +122,8 @@ const GLOSSARY_NO_ENTRY: &[(&str, &str)] = &[ (CAP_SEMANTIC_PERCO2, "PerCoV2 is a different model from the PerCo entry"), (CAP_PRIOR_NNC, "neural-network coding codes model parameters, not media"), (CAP_PRIOR_SET, WAI_NATIVE), + (CAP_PRIOR_SAFETENSORS, "a tensor-file layout for parameters, not a media codec"), + (CAP_PRIOR_GGUF, "a tensor-file layout for parameters, not a media codec"), (CAP_TENSOR_NNC, "a candidate with no payload format pinned; neural-network coding codes tensors, not media"), (CAP_SPLAT_SOG, "a candidate with no payload format pinned"), ]; diff --git a/wai/wai-rs/src/codecs/mod.rs b/wai/wai-rs/src/codecs/mod.rs index 52cd6ba1c..98045b130 100644 --- a/wai/wai-rs/src/codecs/mod.rs +++ b/wai/wai-rs/src/codecs/mod.rs @@ -319,9 +319,11 @@ pub const CAP_TERNARY_TT: &str = "wai.ternary.tt"; // wai.prior.nnc: a trained network's parameters as an NNC bitstream conforming to // the published second edition (ISO/IEC 15938-17:2024) — the registered wire form // a `Prior` MAY declare for the artifact its digest covers, and the capability the -// artifact travels under when it is carried at all. Decoder sink-supplied; this -// crate reads no NNC. The standard's reference software and decoder-conformance -// tests are specified by ISO/IEC 15938-18. +// artifact travels under when it is carried at all. `crate::prior_wire::nnc` (feature +// `prior_wire`) reads and writes the subset prior-carriage §12 states: integer tensors +// coded by context-adaptive arithmetic coding and raw 32-bit floats; quantised payloads +// are refused. The standard's reference software and decoder-conformance tests are +// specified by ISO/IEC 15938-18. pub const CAP_PRIOR_NNC: &str = "wai.prior.nnc"; // wai.prior.set: the listing of a parameter set of one or more files (SPEC §3.1) — // each file's SHA-256 and path below the set's root, one line per file, sorted by @@ -331,6 +333,15 @@ pub const CAP_PRIOR_NNC: &str = "wai.prior.nnc"; // unchanged, so it is the one form the registry lists as established exact. Read // and written by `crate::pin`. pub const CAP_PRIOR_SET: &str = "wai.prior.set"; +// wai.prior.safetensors and wai.prior.gguf: a parameter set as one tensor file — the +// safetensors layout (an 8-byte header length, a JSON header, the tensors' bytes) or GGUF +// version 3 (key-value metadata, tensor descriptions, aligned tensor data). Registered prior +// forms (extensions/prior-carriage.md §9): reading one hands the decoder the tensors' bytes +// as the file holds them, so the registry lists both as established exact. The integer- +// quantised profile (§10) is how a prior in either loads, bit for bit, into the native +// artifact an integer kernel reads. Read and written by `crate::prior_wire`. +pub const CAP_PRIOR_SAFETENSORS: &str = "wai.prior.safetensors"; +pub const CAP_PRIOR_GGUF: &str = "wai.prior.gguf"; // wai.tensor.nnc: general tensorial data (split-inference feature maps, 3D/4D // scene parameters) coded with NNC. A CANDIDATE (is_candidate; SPEC §5, Candidate // capabilities): general-tensor coding is in an NNC amendment at committee-draft @@ -620,6 +631,8 @@ pub fn known_capabilities() -> &'static [&'static str] { CAP_PRIOR_NNC, CAP_TENSOR_NNC, // the listing of a parameter set: the form a pin over several files takes CAP_PRIOR_SET, + // a parameter set as one tensor file + CAP_PRIOR_SAFETENSORS, CAP_PRIOR_GGUF, // immersive audio (IAMF royalty-free; AC-4 / IVAS license-flagged) CAP_AUDIO_IAMF, CAP_AUDIO_AC4, CAP_AUDIO_IVAS, // cinema mastering (JPEG 2000 / DCP) @@ -814,8 +827,9 @@ pub fn capability_tier(cap: &str) -> Option { // HDR dynamic metadata reproduces exactly (it IS the descriptor bytes). | CAP_IMAGE_JP2 | CAP_VIDEO_J2K | CAP_MESH_DRACO | CAP_AUDIO_IAMF | CAP_META_HDR10PLUS | CAP_META_DOVI - // A parameter-set listing is its own output, byte for byte. - | CAP_PRIOR_SET => Lossless, + // A parameter-set listing is its own output, byte for byte; a tensor file hands + // its reader the tensors' bytes as it holds them. + | CAP_PRIOR_SET | CAP_PRIOR_SAFETENSORS | CAP_PRIOR_GGUF => Lossless, // Interactive worlds: replay-equivalence is EXACT state // reproduction (identical BLAKE3 state hash at every checkpoint, // no tolerance parameter) — the simulation analog of lossless. @@ -973,6 +987,9 @@ pub fn capability_license(cap: &str) -> Option { | CAP_AVATAR_POSE | CAP_SPLAT_4D | CAP_FILM_LINEAR | CAP_FILM_REEL | CAP_QUANTUM_CIRCUIT | CAP_QUANTUM_CIRCUIT2 | CAP_TERNARY_TT | CAP_PRIOR_SET | CAP_IMAGE_INT_REFINE | CAP_VIDEO_INT_REFINE | CAP_AUDIO_INT_REFINE | CAP_SPLAT_INT_REFINE + // Tensor-file formats published with their reference implementations under + // permissive licences (the safetensors layout under Apache-2.0, GGUF under MIT). + | CAP_PRIOR_SAFETENSORS | CAP_PRIOR_GGUF // Open asset formats: glTF (Khronos), SPZ (open source). | CAP_MESH_GLB | CAP_SPLAT_SPZ // JPEG 2000 Part 1 is fee-free under ITU-T policy 2.1 (extensions / @@ -1317,8 +1334,9 @@ pub fn capability_replicate_class(cap: &str) -> Option { | CAP_AVATAR_POSE | CAP_SPLAT_4D | CAP_FILM_LINEAR | CAP_FILM_REEL | CAP_QUANTUM_CIRCUIT | CAP_QUANTUM_CIRCUIT2 | CAP_TERNARY_TT // A parameter-set listing: SPEC §3.1 fixes its bytes exactly, and they are - // its own output. - | CAP_PRIOR_SET + // its own output. A tensor file: its format fixes where each tensor's bytes are, + // and reading hands them over unchanged (prior-carriage §9). + | CAP_PRIOR_SET | CAP_PRIOR_SAFETENSORS | CAP_PRIOR_GGUF // A residual refinement: integer prediction, residual and clamp // (staged-delivery §6.1), exact given the stage below; it composes its base's // decoded output (`composes_decoded_sources`), so it is as exact as its base. @@ -2460,6 +2478,8 @@ mod tests { (CAP_QUANTUM_CIRCUIT2, "extensions/quantum-sim.md: statevector-equivalence"), (CAP_TERNARY_TT, "extensions/ternary-tt.md: reconstruction-equivalence"), (CAP_PRIOR_SET, "SPEC.md §3.1: a listing's bytes are fixed exactly and are their own output"), + (CAP_PRIOR_SAFETENSORS, "extensions/prior-carriage.md §9.1: the tensors' bytes are the file's, fixed by its header"), + (CAP_PRIOR_GGUF, "extensions/prior-carriage.md §9.2: the tensors' bytes are the file's, fixed by its tensor descriptions"), (CAP_IMAGE_INT_REFINE, "extensions/staged-delivery.md §6.1: integer predictor, residual and clamp; staged-conformance"), (CAP_VIDEO_INT_REFINE, "extensions/staged-delivery.md §6.1: integer predictor, residual and clamp; staged-conformance"), (CAP_AUDIO_INT_REFINE, "extensions/staged-delivery.md §6.1: integer predictor, residual and clamp; staged-conformance"), diff --git a/wai/wai-rs/src/codecs/prior_form.rs b/wai/wai-rs/src/codecs/prior_form.rs index e6b3a40b6..0e757e5c4 100644 --- a/wai/wai-rs/src/codecs/prior_form.rs +++ b/wai/wai-rs/src/codecs/prior_form.rs @@ -8,11 +8,11 @@ //! name, and a sink decides whether it can read a form by the same dispatch it //! uses for payloads. -use super::{CAP_PRIOR_NNC, CAP_PRIOR_SET}; +use super::{CAP_PRIOR_GGUF, CAP_PRIOR_NNC, CAP_PRIOR_SAFETENSORS, CAP_PRIOR_SET}; /// Every capability string a `Prior` may declare as its artifact's form. pub fn prior_forms() -> &'static [&'static str] { - &[CAP_PRIOR_NNC, CAP_PRIOR_SET] + &[CAP_PRIOR_GGUF, CAP_PRIOR_NNC, CAP_PRIOR_SAFETENSORS, CAP_PRIOR_SET] } /// Whether `cap` is a registered prior form. @@ -23,9 +23,13 @@ pub fn is_prior_form(cap: &str) -> bool { /// Prior forms whose decode to the tensors a network consumes is **established** /// to give identical tensors at every conforming reader. /// -/// One: `wai.prior.set`. Reading a set through its listing compares digests and +/// Three. `wai.prior.set`: reading a set through its listing compares digests and /// hands the decoder each file's bytes unchanged, so every sink that holds the set -/// reads the same parameters. +/// reads the same parameters. `wai.prior.safetensors` and `wai.prior.gguf`: a reader that +/// follows prior-carriage §9 finds each tensor's bytes where the file's header puts them +/// and hands them over unchanged, refusing a file whose header could be read two ways (a +/// name twice, overlapping or uncovered bytes), so every such reader reads the same +/// tensors from the same file. /// /// No coded form is listed. ISO/IEC 15938-17 already has payload types that carry /// values without quantisation: its 2022 edition lists decoding methods for an @@ -39,7 +43,7 @@ pub fn is_prior_form(cap: &str) -> bool { /// lossless mode added by an amendment would not settle it by itself. Until a coded /// form is listed, a `replicate` claim cannot rest on a pin in it (see /// [`Prior::form_preserves_exactness`](crate::container::Prior::form_preserves_exactness)). -const EXACT_RECONSTRUCTION_FORMS: &[&str] = &[CAP_PRIOR_SET]; +const EXACT_RECONSTRUCTION_FORMS: &[&str] = &[CAP_PRIOR_GGUF, CAP_PRIOR_SAFETENSORS, CAP_PRIOR_SET]; /// Whether reading an artifact out of `form` is established to reproduce the /// same tensors at every conforming reader. `false` for an unregistered form. @@ -62,7 +66,7 @@ mod tests { #[test] fn prior_forms_are_registered_capabilities() { - assert_eq!(prior_forms(), [CAP_PRIOR_NNC, CAP_PRIOR_SET]); + assert_eq!(prior_forms(), [CAP_PRIOR_GGUF, CAP_PRIOR_NNC, CAP_PRIOR_SAFETENSORS, CAP_PRIOR_SET]); for &f in prior_forms() { assert!(is_known_capability(f), "{f} must be a registered capability"); } @@ -74,8 +78,10 @@ mod tests { } #[test] - fn only_the_listing_form_is_established_exact() { + fn the_listing_and_the_tensor_files_are_established_exact_and_no_coded_form() { assert!(form_reconstruction_is_exact(CAP_PRIOR_SET)); + assert!(form_reconstruction_is_exact(CAP_PRIOR_SAFETENSORS)); + assert!(form_reconstruction_is_exact(CAP_PRIOR_GGUF)); assert!(!form_reconstruction_is_exact(CAP_PRIOR_NNC)); assert!(!form_reconstruction_is_exact("wai.prior.nope")); } diff --git a/wai/wai-rs/src/codecs/registry.rs b/wai/wai-rs/src/codecs/registry.rs index 9ed0a7939..9a50a1f0e 100644 --- a/wai/wai-rs/src/codecs/registry.rs +++ b/wai/wai-rs/src/codecs/registry.rs @@ -291,6 +291,8 @@ const ROWS: &[(&str, &str, Option<&str>, &str)] = &[ (CAP_VIDEO_CHANNEL, S_NEURAL, None, "2026-09-03"), (CAP_PRIOR_NNC, S_PRIOR, Some("ISO/IEC 15938-17:2024"), "2026-09-28"), (CAP_PRIOR_SET, S_PRIOR, None, "2026-09-30"), + (CAP_PRIOR_SAFETENSORS, S_PRIOR, None, "2026-10-22"), + (CAP_PRIOR_GGUF, S_PRIOR, Some("GGUF version 3"), "2026-10-22"), (CAP_TENSOR_NNC, S_PRIOR, None, "2026-09-28"), (CAP_EDGE_DERIVATION, S_PRIOR, None, "2026-10-21"), (CAP_WORLD_SCENE, S_INTERACTIVE, None, "2026-06-13"), @@ -930,7 +932,7 @@ pub fn capability_media(cap: &str) -> Option { | CAP_FILM_LINEAR | CAP_FILM_REEL => Video, CAP_WORLD_SCENE | CAP_WORLD_REPLAY | CAP_WORLD_LIVE | CAP_WORLD_MODEL | CAP_FEED_STATE | CAP_AVATAR_POSE => Interactive, - CAP_PRIOR_NNC | CAP_PRIOR_SET | CAP_TENSOR_NNC | CAP_QUANTUM_CIRCUIT | CAP_QUANTUM_CIRCUIT2 + CAP_PRIOR_NNC | CAP_PRIOR_SET | CAP_PRIOR_SAFETENSORS | CAP_PRIOR_GGUF | CAP_TENSOR_NNC | CAP_QUANTUM_CIRCUIT | CAP_QUANTUM_CIRCUIT2 | CAP_TERNARY_TT => Binary, c if c.starts_with("wai.image.") => Image, c if c.starts_with("wai.audio.") => Audio, diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 073498658..6c3affe95 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -606,6 +606,24 @@ pub mod age_assurance; /// cbindgen:ignore #[cfg(feature = "influence")] pub mod influence; + +// Tensor files as prior forms (extensions/prior-carriage.md §9 to §14): safetensors, GGUF and +// the NNC subset §12 reads, each read strictly and written canonically, and the integer-quantised +// profile through which a prior in any of them loads into the native artifact an integer kernel +// reads. Rust-only. +/// cbindgen:ignore +#[cfg(feature = "prior_wire")] +pub mod prior_wire; + +// Scene carriage (extensions/scene-carriage.md): WAI worlds, audio scenes, mesh assets and timed +// media onto ISO/IEC 23090-14 scene description and a USD layer, both ways. Rust-only. +/// cbindgen:ignore +#[cfg(feature = "scene_map")] +pub mod scene_map; + +// The strict JSON reader the two mappings above share. +#[cfg(any(feature = "prior_wire", feature = "scene_map"))] +pub(crate) mod strict_json; // Bearer receipts (extensions/bearer-receipts.md): `wai.bearer.receipts` — one receipt on every // bearer; the verdict over a group from its transmission and reconciliation claims, the upper // bound on a receiver's share of a shared transmission's energy, and confirmation with no return diff --git a/wai/wai-rs/src/prior_wire.rs b/wai/wai-rs/src/prior_wire.rs new file mode 100644 index 000000000..a32b38abf --- /dev/null +++ b/wai/wai-rs/src/prior_wire.rs @@ -0,0 +1,354 @@ +//! Tensor files as the wire forms of a prior (`extensions/prior-carriage.md` §9 to §13): +//! `wai.prior.safetensors`, `wai.prior.gguf` and the ISO/IEC 15938-17 (NNC) bitstream of +//! `wai.prior.nnc`, each with a strict reader, a canonical writer, and the integer-quantised +//! profile `wai-int/1` through which a prior carried in any of them loads, bit for bit, into +//! the native artifact an integer kernel reads. +//! +//! - [`safetensors`], [`gguf`] and [`nnc`] read a file into a [`TensorSet`] and write one +//! canonically. A reader refuses with one of the codes of §13 ([`CODES`]). +//! - [`binding`] holds the registered bindings (§10.2): `wsr1/1` (the `WSR1` prior of +//! exact-upscale §3.1) and `whm1/1` (the `WHM1` model of integer-payloads §9). A binding +//! maps a native artifact to named integer tensors and back, exactly. +//! - [`load`] runs §10.3: the file in its form, canonical, in the profile, its tensors +//! exactly the binding's, every value in range, and returns the native bytes; +//! [`encode`] runs the other way. The digest rule (§11) is what makes the round trip one +//! to one: a canonical file is a function of its tensors and its two metadata values. +//! +//! The corpus is `live-conformance/prior-forms/` ([`corpus`]); its second implementation is +//! `live-conformance/prior_forms_verify.py`. + +use std::collections::BTreeMap; + +pub mod binding; +pub mod cabac; +pub mod corpus; +pub mod gguf; +pub mod nnc; +pub mod safetensors; + +/// The capability string of the safetensors form. +pub use crate::codecs::CAP_PRIOR_SAFETENSORS; +/// The capability string of the GGUF form. +pub use crate::codecs::CAP_PRIOR_GGUF; +/// The capability string of the NNC form. +pub use crate::codecs::CAP_PRIOR_NNC; + +/// The integer-quantised profile's identifier (§10). +pub const PROFILE: &str = "wai-int/1"; +/// The metadata key that names the profile (§10.1). +pub const KEY_PROFILE: &str = "wai.profile"; +/// The metadata key that names the binding (§10.1). +pub const KEY_BINDING: &str = "wai.binding"; + +/// Every refusal code of §13, in the order of its table. +pub const CODES: &[&str] = &[ + "header_truncated", + "header_too_large", + "header_malformed", + "bad_magic", + "version_unsupported", + "tensor_malformed", + "dtype_unknown", + "offset_mismatch", + "header_not_canonical", + "nnc_truncated", + "nnc_malformed", + "nnc_not_read", + "profile_absent", + "binding_unknown", + "dtype_not_integer", + "binding_mismatch", + "value_out_of_range", + "native_malformed", +]; + +/// Why a file was refused: a code of §13 and the particulars. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Refusal { + pub code: &'static str, + pub why: String, +} + +impl Refusal { + pub fn new(code: &'static str, why: impl Into) -> Self { + debug_assert!(CODES.contains(&code), "{code} is not a §13 code"); + Refusal { code, why: why.into() } + } +} + +impl std::fmt::Display for Refusal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}: {}", self.code, self.why) + } +} + +impl std::error::Error for Refusal {} + +pub(crate) fn refuse(code: &'static str, why: impl Into) -> Result { + Err(Refusal::new(code, why)) +} + +/// An element type a reader knows (§9.3): the safetensors names, and the GGUF types they +/// correspond to, plus GGUF's block type `Q8_0`. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum DType { + Bool, + U8, + I8, + I16, + U16, + F16, + BF16, + I32, + U32, + F32, + F64, + I64, + U64, + F8E5M2, + F8E4M3, + /// GGUF only: blocks of 32 elements, a binary16 scale and 32 signed bytes. + Q8_0, +} + +impl DType { + /// The safetensors name, where the format has one. + pub fn safetensors_name(self) -> Option<&'static str> { + Some(match self { + DType::Bool => "BOOL", + DType::U8 => "U8", + DType::I8 => "I8", + DType::I16 => "I16", + DType::U16 => "U16", + DType::F16 => "F16", + DType::BF16 => "BF16", + DType::I32 => "I32", + DType::U32 => "U32", + DType::F32 => "F32", + DType::F64 => "F64", + DType::I64 => "I64", + DType::U64 => "U64", + DType::F8E5M2 => "F8_E5M2", + DType::F8E4M3 => "F8_E4M3", + DType::Q8_0 => return None, + }) + } + pub fn from_safetensors_name(s: &str) -> Option { + [ + DType::Bool, DType::U8, DType::I8, DType::I16, DType::U16, DType::F16, DType::BF16, + DType::I32, DType::U32, DType::F32, DType::F64, DType::I64, DType::U64, DType::F8E5M2, + DType::F8E4M3, + ] + .into_iter() + .find(|d| d.safetensors_name() == Some(s)) + } + /// The GGUF type number, where the format has one. + pub fn gguf_type(self) -> Option { + Some(match self { + DType::F32 => 0, + DType::F16 => 1, + DType::Q8_0 => 8, + DType::I8 => 24, + DType::I16 => 25, + DType::I32 => 26, + DType::I64 => 27, + DType::F64 => 28, + DType::BF16 => 30, + _ => return None, + }) + } + pub fn from_gguf_type(t: u32) -> Option { + [DType::F32, DType::F16, DType::Q8_0, DType::I8, DType::I16, DType::I32, DType::I64, DType::F64, DType::BF16] + .into_iter() + .find(|d| d.gguf_type() == Some(t)) + } + /// The bytes `n` elements take, or `None` when `n` is not a whole number of blocks or + /// the count overflows. + pub fn bytes_for(self, n: u64) -> Option { + match self { + DType::Bool | DType::U8 | DType::I8 | DType::F8E5M2 | DType::F8E4M3 => Some(n), + DType::I16 | DType::U16 | DType::F16 | DType::BF16 => n.checked_mul(2), + DType::I32 | DType::U32 | DType::F32 => n.checked_mul(4), + DType::I64 | DType::U64 | DType::F64 => n.checked_mul(8), + DType::Q8_0 => (n % 32 == 0).then(|| (n / 32).checked_mul(34)).flatten(), + } + } + /// The signed integer types of the profile (§10.1): `I8`, `I16`, `I32`, `I64`. + pub fn is_profile_integer(self) -> bool { + matches!(self, DType::I8 | DType::I16 | DType::I32 | DType::I64) + } + /// The width in bytes of a profile integer type. + pub fn int_width(self) -> Option { + match self { + DType::I8 => Some(1), + DType::I16 => Some(2), + DType::I32 => Some(4), + DType::I64 => Some(8), + _ => None, + } + } + /// The range of a profile integer type. + pub fn int_range(self) -> Option<(i64, i64)> { + self.int_width().map(|w| if w == 8 { (i64::MIN, i64::MAX) } else { (-(1i64 << (8 * w - 1)), (1i64 << (8 * w - 1)) - 1) }) + } +} + +/// One tensor: its name, element type, shape (outermost dimension first, the row-major +/// order of §9.1) and its bytes, little-endian. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Tensor { + pub name: String, + pub dtype: DType, + pub shape: Vec, + pub data: Vec, +} + +impl Tensor { + /// The number of elements. + pub fn count(&self) -> Option { + self.shape.iter().try_fold(1u64, |a, &d| a.checked_mul(d)) + } + + /// A profile integer tensor from `values`, each written at `dtype`'s width. Panics if a + /// value does not fit: callers build tensors from values they range-checked. + pub fn from_ints(name: &str, dtype: DType, shape: Vec, values: &[i64]) -> Tensor { + let w = dtype.int_width().expect("a profile integer type"); + let (lo, hi) = dtype.int_range().expect("a profile integer type"); + let mut data = Vec::with_capacity(values.len() * w); + for &v in values { + assert!(v >= lo && v <= hi, "{name}: {v} does not fit {dtype:?}"); + data.extend_from_slice(&v.to_le_bytes()[..w]); + } + Tensor { name: name.to_string(), dtype, shape, data } + } + + /// The values of a profile integer tensor, sign-extended. + pub fn ints(&self) -> Option> { + let w = self.dtype.int_width()?; + if self.data.len() % w != 0 { + return None; + } + Some( + self.data + .chunks_exact(w) + .map(|c| { + let mut b = [if c[w - 1] & 0x80 != 0 { 0xff } else { 0 }; 8]; + b[..w].copy_from_slice(c); + i64::from_le_bytes(b) + }) + .collect(), + ) + } +} + +/// A file's tensors in ascending byte order of their names, and its metadata (string +/// values only, §9.3). +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct TensorSet { + pub metadata: BTreeMap, + pub tensors: Vec, +} + +impl TensorSet { + pub fn get(&self, name: &str) -> Option<&Tensor> { + self.tensors.iter().find(|t| t.name == name) + } + /// Sort the tensors by name (the canonical order of §11). + pub fn sorted(mut self) -> Self { + self.tensors.sort_by(|a, b| a.name.as_bytes().cmp(b.name.as_bytes())); + self + } +} + +/// A registered tensor-file form. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Form { + Safetensors, + Gguf, + Nnc, +} + +impl Form { + pub const ALL: [Form; 3] = [Form::Safetensors, Form::Gguf, Form::Nnc]; + /// The form's capability string. + pub fn capability(self) -> &'static str { + match self { + Form::Safetensors => CAP_PRIOR_SAFETENSORS, + Form::Gguf => CAP_PRIOR_GGUF, + Form::Nnc => CAP_PRIOR_NNC, + } + } + pub fn from_capability(c: &str) -> Option
{ + Form::ALL.into_iter().find(|f| f.capability() == c) + } + /// The short name the corpus uses. + pub fn label(self) -> &'static str { + match self { + Form::Safetensors => "safetensors", + Form::Gguf => "gguf", + Form::Nnc => "nnc", + } + } +} + +/// Read `bytes` in `form` (the form's own rules, §9 and §12), without the canonical or +/// profile checks. +pub fn read(form: Form, bytes: &[u8]) -> Result { + match form { + Form::Safetensors => safetensors::read(bytes), + Form::Gguf => gguf::read(bytes), + Form::Nnc => nnc::read(bytes), + } +} + +/// The canonical bytes of `set` in `form` (§11). +pub fn write(form: Form, set: &TensorSet) -> Result, Refusal> { + match form { + Form::Safetensors => safetensors::write(set), + Form::Gguf => gguf::write(set), + Form::Nnc => nnc::write(set), + } +} + +/// §10.3 over a file: read it in `form`, require it canonical (§11), in the profile with a +/// registered binding (§10.1), its tensors exactly the binding's with every value in range +/// (§10.2), and return the binding's name and the native artifact's bytes. +pub fn load(form: Form, bytes: &[u8]) -> Result<(&'static str, Vec), Refusal> { + let set = read(form, bytes)?; + if write(form, &set)? != bytes { + return refuse("header_not_canonical", format!("the file is not the canonical {} serialisation of its tensors (§11)", form.label())); + } + let b = binding::of(&set, form)?; + let native = (b.to_native)(&set, form)?; + Ok((b.name, native)) +} + +/// The other way: the native artifact `native` under `binding`, as a canonical file in +/// `form` (§10.3). +pub fn encode(binding_name: &str, native: &[u8], form: Form) -> Result, Refusal> { + let b = binding::by_name(binding_name).ok_or_else(|| Refusal::new("binding_unknown", format!("no binding {binding_name:?}")))?; + let mut set = (b.from_native)(native)?; + set.metadata = binding::profile_metadata(b.name, form); + write(form, &set.sorted()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn integer_tensors_round_trip_their_values() { + for (d, v) in [(DType::I8, -128), (DType::I16, 32767), (DType::I32, -5), (DType::I64, i64::MIN)] { + let t = Tensor::from_ints("x", d, vec![2], &[v, 1]); + assert_eq!(t.ints().unwrap(), vec![v, 1]); + } + assert_eq!(DType::Q8_0.bytes_for(64), Some(68)); + assert_eq!(DType::Q8_0.bytes_for(33), None); + for d in [DType::I8, DType::F8E4M3, DType::BF16, DType::U64] { + assert_eq!(DType::from_safetensors_name(d.safetensors_name().unwrap()), Some(d)); + } + for t in [0, 1, 8, 24, 25, 26, 27, 28, 30] { + assert_eq!(DType::from_gguf_type(t).and_then(DType::gguf_type), Some(t)); + } + } +} diff --git a/wai/wai-rs/src/prior_wire/binding.rs b/wai/wai-rs/src/prior_wire/binding.rs new file mode 100644 index 000000000..0ca969d27 --- /dev/null +++ b/wai/wai-rs/src/prior_wire/binding.rs @@ -0,0 +1,564 @@ +//! The bindings of the integer-quantised profile (`extensions/prior-carriage.md` §10.2): for +//! each native integer artifact, the named integer tensors that carry it, and the exact map +//! both ways. Loading does no arithmetic but reading integers: every value is the integer +//! the kernel reads, and every scale the kernel applies (a multiplier, a shift) is itself a +//! tensor, so a prior carried in any form loads into the same native bytes. + +use std::collections::{BTreeMap, BTreeSet}; + +use super::{refuse, DType, Form, Refusal, Tensor, TensorSet, KEY_BINDING, KEY_PROFILE, PROFILE}; + +/// A registered binding. +pub struct Binding { + /// Its name, the value of `wai.binding`. + pub name: &'static str, + /// The native artifact, in words. + pub native: &'static str, + /// The native bytes as tensors (no metadata). + pub from_native: fn(&[u8]) -> Result, + /// The tensors of a file read in a form as the native bytes: §10.3 steps 4 and 5. + pub to_native: fn(&TensorSet, Form) -> Result, Refusal>, +} + +/// Every registered binding. +pub const BINDINGS: &[Binding] = &[ + Binding { name: "wsr1/1", native: "a WSR1 prior (exact-upscale §3.1)", from_native: wsr1_from, to_native: wsr1_to }, + Binding { name: "whm1/1", native: "a WHM1 model (integer-payloads §9)", from_native: whm1_from, to_native: whm1_to }, +]; + +pub fn by_name(n: &str) -> Option<&'static Binding> { + BINDINGS.iter().find(|b| b.name == n) +} + +/// The value `general.architecture` takes in a GGUF file of the profile (§10.1). +pub const GGUF_ARCHITECTURE: &str = "wai"; + +/// The metadata a file of the profile carries in `form` (§10.1). +pub fn profile_metadata(binding: &str, form: Form) -> BTreeMap { + let mut m = BTreeMap::new(); + m.insert(KEY_BINDING.to_string(), binding.to_string()); + m.insert(KEY_PROFILE.to_string(), PROFILE.to_string()); + if form == Form::Gguf { + m.insert("general.architecture".to_string(), GGUF_ARCHITECTURE.to_string()); + } + m +} + +/// §10.3 steps 1 to 3: the profile and its keys, a registered binding, integer tensors. +pub fn of(set: &TensorSet, form: Form) -> Result<&'static Binding, Refusal> { + let bname = set.metadata.get(KEY_BINDING).cloned().unwrap_or_default(); + let want = profile_metadata(&bname, form); + if set.metadata.get(KEY_PROFILE).map(String::as_str) != Some(PROFILE) + || set.metadata.keys().ne(want.keys()) + || (form == Form::Gguf && set.metadata.get("general.architecture").map(String::as_str) != Some(GGUF_ARCHITECTURE)) + { + let keys: Vec<&String> = want.keys().collect(); + return refuse("profile_absent", format!("the metadata is not exactly {keys:?} with {KEY_PROFILE} = {PROFILE}")); + } + let Some(b) = by_name(&bname) else { return refuse("binding_unknown", format!("no binding {bname:?}")) }; + for t in &set.tensors { + let ok = if form == Form::Nnc { t.dtype == DType::I32 } else { t.dtype.is_profile_integer() }; + if !ok { + return refuse("dtype_not_integer", format!("{:?} is {:?}", t.name, t.dtype)); + } + } + Ok(b) +} + +/// One tensor a binding names: its name, element type and shape. +struct Want { + name: String, + dtype: DType, + shape: Vec, +} + +/// §10.3 step 4 then 5 over the tensors a binding expects, in its table's order: the names +/// exactly these (`binding_mismatch`), then each tensor's type (the binding's, or `I32` in +/// NNC) and shape (`binding_mismatch`), then each value within the type the binding gives +/// and the range `range` gives (`value_out_of_range`). Returns the values by name. +fn check(set: &TensorSet, form: Form, wants: &[Want], range: &dyn Fn(&str, usize) -> Option<(i64, i64)>) -> Result>, Refusal> { + let have: BTreeSet<&str> = set.tensors.iter().map(|t| t.name.as_str()).collect(); + let need: BTreeSet<&str> = wants.iter().map(|w| w.name.as_str()).collect(); + if have != need { + let extra: Vec<&&str> = have.difference(&need).collect(); + let missing: Vec<&&str> = need.difference(&have).collect(); + return refuse("binding_mismatch", format!("tensors not in the binding {extra:?}, missing {missing:?}")); + } + for w in wants { + let t = set.get(&w.name).expect("present"); + let dt = if form == Form::Nnc { DType::I32 } else { w.dtype }; + if t.dtype != dt || t.shape != w.shape { + return refuse("binding_mismatch", format!("{:?} is {:?} {:?}, the binding gives {:?} {:?}", w.name, t.dtype, t.shape, dt, w.shape)); + } + } + let mut out = BTreeMap::new(); + for w in wants { + let v = set.get(&w.name).expect("present").ints().expect("an integer tensor"); + let (tlo, thi) = w.dtype.int_range().expect("an integer type"); + for (i, &x) in v.iter().enumerate() { + let (lo, hi) = range(&w.name, i).map(|(a, b)| (a.max(tlo), b.min(thi))).unwrap_or((tlo, thi)); + if x < lo || x > hi { + return refuse("value_out_of_range", format!("{}[{i}] = {x}, outside {lo}..={hi}", w.name)); + } + } + out.insert(w.name.clone(), v); + } + Ok(out) +} + +fn want(name: impl Into, dtype: DType, shape: Vec) -> Want { + Want { name: name.into(), dtype, shape } +} + +/// Indices `i` of tensors named ``, written in plain decimal +/// (`width` digits when `width` is set), in ascending order. +fn indices(set: &TensorSet, prefix: &str, suffix: &str, width: Option) -> Vec { + let mut v: Vec = set + .tensors + .iter() + .filter_map(|t| t.name.strip_prefix(prefix)?.strip_suffix(suffix).map(str::to_string)) + .filter(|d| !d.is_empty() && d.bytes().all(|c| c.is_ascii_digit()) && width.is_none_or(|w| d.len() == w) && (width.is_some() || d == "0" || !d.starts_with('0'))) + .filter_map(|d| d.parse().ok()) + .collect(); + v.sort_unstable(); + v +} + +fn native_err(why: &str) -> Refusal { + Refusal::new("native_malformed", why.to_string()) +} + +struct Rd<'a> { + b: &'a [u8], + p: usize, +} + +impl Rd<'_> { + fn take(&mut self, n: usize) -> Result<&[u8], Refusal> { + let e = self.p.checked_add(n).filter(|&e| e <= self.b.len()).ok_or_else(|| native_err("truncated"))?; + let s = &self.b[self.p..e]; + self.p = e; + Ok(s) + } + fn u8(&mut self) -> Result { + Ok(self.take(1)?[0]) + } + fn u16(&mut self) -> Result { + Ok(u16::from_le_bytes(self.take(2)?.try_into().expect("2"))) + } + fn u32(&mut self) -> Result { + Ok(u32::from_le_bytes(self.take(4)?.try_into().expect("4"))) + } + fn ints(&mut self, n: usize, w: usize) -> Result, Refusal> { + let bytes = self.take(n.checked_mul(w).ok_or_else(|| native_err("a size that overflows"))?)?; + Ok(bytes + .chunks_exact(w) + .map(|c| { + let mut b = [if c[w - 1] & 0x80 != 0 { 0xff } else { 0 }; 8]; + b[..w].copy_from_slice(c); + i64::from_le_bytes(b) + }) + .collect()) + } +} + +// --------------------------------------------------------------------------------------- +// wsr1/1 — the WSR1 prior of exact-upscale §3.1 +// --------------------------------------------------------------------------------------- + +/// `layer..`, `i` in plain decimal. +fn wsr_name(i: usize, part: &str) -> String { + format!("layer.{i}.{part}") +} + +fn wsr1_wants(n: usize, couts: &[u64], ks: &[u64]) -> Vec { + let mut w = vec![want("act_hi", DType::I16, vec![1])]; + let mut cin = 3u64; + for i in 0..n { + let (cout, k) = (couts[i], ks[i]); + w.push(want(wsr_name(i, "weight"), DType::I8, vec![cout, cin, k, k])); + w.push(want(wsr_name(i, "bias"), DType::I32, vec![cout])); + w.push(want(wsr_name(i, "mult"), DType::I32, vec![cout])); + w.push(want(wsr_name(i, "shift"), DType::I8, vec![1])); + cin = cout; + } + w +} + +fn wsr1_from(b: &[u8]) -> Result { + let mut r = Rd { b, p: 0 }; + if r.take(4).map_err(|_| native_err("bad_magic"))? != b"WSR1" { + return Err(native_err("bad_magic")); + } + if r.u8().map_err(|_| native_err("truncated"))? != 1 { + return Err(native_err("version")); + } + let n = r.u8()? as usize; + let act_hi = r.u16()?; + if !(1..=8).contains(&n) || !(1..=32767).contains(&act_hi) { + return Err(native_err("field_range")); + } + let mut set = TensorSet::default(); + set.tensors.push(Tensor::from_ints("act_hi", DType::I16, vec![1], &[i64::from(act_hi)])); + let mut cin = 3u64; + for i in 0..n { + let (k, shift, cout) = (r.u8()?, r.u8()?, u64::from(r.u16()?)); + let last = i + 1 == n; + let cout_ok = if last { cout == 12 } else { (1..=64).contains(&cout) }; + if !matches!(k, 1 | 3 | 5) || shift > 31 || !cout_ok { + return Err(native_err("field_range")); + } + let k = u64::from(k); + let w = r.ints((cout * cin * k * k) as usize, 1)?; + let bias = r.ints(cout as usize, 4)?; + let mult: Vec = r.take(2 * cout as usize)?.chunks_exact(2).map(|c| i64::from(u16::from_le_bytes([c[0], c[1]]))).collect(); + if mult.contains(&0) { + return Err(native_err("field_range")); + } + set.tensors.push(Tensor::from_ints(&wsr_name(i, "weight"), DType::I8, vec![cout, cin, k, k], &w)); + set.tensors.push(Tensor::from_ints(&wsr_name(i, "bias"), DType::I32, vec![cout], &bias)); + set.tensors.push(Tensor::from_ints(&wsr_name(i, "mult"), DType::I32, vec![cout], &mult)); + set.tensors.push(Tensor::from_ints(&wsr_name(i, "shift"), DType::I8, vec![1], &[i64::from(shift)])); + cin = cout; + } + if r.p != b.len() { + return Err(native_err("trailing")); + } + Ok(set.sorted()) +} + +fn wsr1_to(set: &TensorSet, form: Form) -> Result, Refusal> { + let n = indices(set, "layer.", ".weight", None).len(); + let idx = indices(set, "layer.", ".weight", None); + if !(1..=8).contains(&n) || idx != (0..n).collect::>() { + return refuse("binding_mismatch", format!("layers {idx:?}: wsr1/1 has layers 0 to n − 1, n from 1 to 8")); + } + // The shapes the binding gives follow from the weights' own: cout and k from each + // weight, cin from the layer before; each must also be a shape exact-upscale admits. + let (mut couts, mut ks) = (Vec::new(), Vec::new()); + for i in 0..n { + let t = set.get(&wsr_name(i, "weight")).expect("indexed"); + let (cout, k) = (t.shape.first().copied().unwrap_or(0), t.shape.last().copied().unwrap_or(0)); + let cout_ok = if i + 1 == n { cout == 12 } else { (1..=64).contains(&cout) }; + if !matches!(k, 1 | 3 | 5) || !cout_ok { + return refuse("binding_mismatch", format!("{}: shape {:?} (k 1, 3 or 5; cout 1 to 64, 12 in the last layer)", t.name, t.shape)); + } + couts.push(cout); + ks.push(k); + } + let wants = wsr1_wants(n, &couts, &ks); + let range = |name: &str, _i: usize| -> Option<(i64, i64)> { + if name == "act_hi" { + Some((1, 32767)) + } else if name.ends_with(".mult") { + Some((1, 65535)) + } else if name.ends_with(".shift") { + Some((0, 31)) + } else { + None + } + }; + let v = check(set, form, &wants, &range)?; + let mut o = b"WSR1".to_vec(); + o.push(1); + o.push(n as u8); + o.extend_from_slice(&(v["act_hi"][0] as u16).to_le_bytes()); + for i in 0..n { + o.extend_from_slice(&[ks[i] as u8, v[&wsr_name(i, "shift")][0] as u8]); + o.extend_from_slice(&(couts[i] as u16).to_le_bytes()); + o.extend(v[&wsr_name(i, "weight")].iter().map(|&x| x as i8 as u8)); + for &x in &v[&wsr_name(i, "bias")] { + o.extend_from_slice(&(x as i32).to_le_bytes()); + } + for &x in &v[&wsr_name(i, "mult")] { + o.extend_from_slice(&(x as u16).to_le_bytes()); + } + } + Ok(o) +} + +// --------------------------------------------------------------------------------------- +// whm1/1 — the WHM1 model of integer-payloads §9 +// --------------------------------------------------------------------------------------- + +/// The most layers `whm1/1` carries: a layer index has three digits. +pub const WHM1_MAX_LAYERS: usize = 1000; + +fn whm_name(i: usize, part: &str) -> String { + format!("layer.{i:03}.{part}") +} + +/// The parts that name a layer's kind, in tag order. +const WHM_KINDS: [&str; 5] = ["convt", "conv", "igdn", "relu", "leaky"]; + +const U32_MAX: i64 = u32::MAX as i64; + +fn whm1_from(b: &[u8]) -> Result { + let mut r = Rd { b, p: 0 }; + if r.take(4).map_err(|_| native_err("bad model magic (want WHM1)"))? != b"WHM1" { + return Err(native_err("bad model magic (want WHM1)")); + } + let (afrac, gf, shift, n) = (r.u32()?, r.u32()?, r.u32()?, r.u32()? as usize); + if n > WHM1_MAX_LAYERS { + return refuse("binding_mismatch", format!("{n} layers; whm1/1 carries at most {WHM1_MAX_LAYERS}")); + } + let mut set = TensorSet::default(); + set.tensors.push(Tensor::from_ints("header", DType::I64, vec![3], &[afrac.into(), gf.into(), shift.into()])); + let nonzero = |dims: &[u64]| -> Result<(), Refusal> { + if dims.contains(&0) { + return refuse("binding_mismatch", "a layer with a zero dimension, which no form carries as a tensor"); + } + Ok(()) + }; + for i in 0..n { + match r.u8()? { + t @ (0 | 1) => { + let (inc, out, k, stride, pad) = (r.u32()?, r.u32()?, r.u32()?, r.u32()?, r.u32()?); + let opad = if t == 0 { Some(r.u32()?) } else { None }; + let (inc, out, k) = (u64::from(inc), u64::from(out), u64::from(k)); + nonzero(&[inc, out, k])?; + let count = inc.checked_mul(out).and_then(|v| v.checked_mul(k)).and_then(|v| v.checked_mul(k)).ok_or_else(|| native_err("layer size overflows"))?; + let w = r.ints(count as usize, 2)?; + let mult = r.ints(out as usize, 8)?; + let bias = r.ints(out as usize, 8)?; + let mut geom = vec![inc as i64, out as i64, k as i64, stride.into(), pad.into()]; + let (kind, wshape) = if let Some(op) = opad { + geom.push(op.into()); + ("convt", vec![inc, out, k, k]) + } else { + ("conv", vec![out, inc, k, k]) + }; + set.tensors.push(Tensor::from_ints(&whm_name(i, kind), DType::I64, vec![geom.len() as u64], &geom)); + set.tensors.push(Tensor::from_ints(&whm_name(i, "weight"), DType::I16, wshape, &w)); + set.tensors.push(Tensor::from_ints(&whm_name(i, "mult"), DType::I64, vec![out], &mult)); + set.tensors.push(Tensor::from_ints(&whm_name(i, "bias"), DType::I64, vec![out], &bias)); + } + 2 => { + let c = u64::from(r.u32()?); + nonzero(&[c])?; + let gamma = r.ints(c.checked_mul(c).ok_or_else(|| native_err("layer size overflows"))? as usize, 4)?; + let beta = r.ints(c as usize, 8)?; + set.tensors.push(Tensor::from_ints(&whm_name(i, "igdn"), DType::I64, vec![1], &[c as i64])); + set.tensors.push(Tensor::from_ints(&whm_name(i, "gamma"), DType::I32, vec![c, c], &gamma)); + set.tensors.push(Tensor::from_ints(&whm_name(i, "beta"), DType::I64, vec![c], &beta)); + } + 3 => set.tensors.push(Tensor::from_ints(&whm_name(i, "relu"), DType::I8, vec![1], &[0])), + 4 => { + let q = r.ints(1, 8)?[0]; + let frac = r.u32()?; + set.tensors.push(Tensor::from_ints(&whm_name(i, "leaky"), DType::I64, vec![2], &[q, frac.into()])); + } + _ => return Err(native_err("bad layer tag")), + } + } + if r.p != b.len() { + return Err(native_err("bytes follow the last layer")); + } + Ok(set.sorted()) +} + +fn whm1_to(set: &TensorSet, form: Form) -> Result, Refusal> { + // The layers: each index holds exactly one kind tensor; indices 0 to n − 1. + let mut kinds: BTreeMap = BTreeMap::new(); + for kind in WHM_KINDS { + for i in indices(set, "layer.", &format!(".{kind}"), Some(3)) { + if kinds.insert(i, kind).is_some() { + return refuse("binding_mismatch", format!("layer {i:03} has two kinds")); + } + } + } + let n = kinds.len(); + if kinds.keys().copied().ne(0..n) { + return refuse("binding_mismatch", format!("layers {:?}: whm1/1 has layers 000 to n − 1", kinds.keys().collect::>())); + } + // The geometry first, from each kind tensor, so the shapes of the rest follow from it. + let geom = |i: usize, kind: &str, len: u64| -> Result, Refusal> { + let t = set.get(&whm_name(i, kind)).expect("indexed"); + let dt = if form == Form::Nnc { DType::I32 } else { DType::I64 }; + if t.dtype != dt || t.shape != vec![len] { + return refuse("binding_mismatch", format!("{:?} is {:?} {:?}, the binding gives {dt:?} [{len}]", t.name, t.dtype, t.shape)); + } + Ok(t.ints().expect("integer")) + }; + let mut wants = vec![want("header", DType::I64, vec![3])]; + let mut dims: Vec> = Vec::new(); + for (&i, &kind) in &kinds { + let len = match kind { + "convt" => 6, + "conv" => 5, + "igdn" => 1, + "relu" => 1, + _ => 2, + }; + if kind == "relu" { + wants.push(want(whm_name(i, "relu"), DType::I8, vec![1])); + dims.push(vec![]); + continue; + } + let g = geom(i, kind, len)?; + let pos = |x: i64| -> Result { + if (1..=U32_MAX).contains(&x) { Ok(x as u64) } else { refuse("value_out_of_range", format!("{}: a dimension {x}, outside 1..=4294967295", whm_name(i, kind))) } + }; + match kind { + "convt" | "conv" => { + let (inc, out, k) = (pos(g[0])?, pos(g[1])?, pos(g[2])?); + wants.push(want(whm_name(i, kind), DType::I64, vec![len])); + let ws = if kind == "convt" { vec![inc, out, k, k] } else { vec![out, inc, k, k] }; + wants.push(want(whm_name(i, "weight"), DType::I16, ws)); + wants.push(want(whm_name(i, "mult"), DType::I64, vec![out])); + wants.push(want(whm_name(i, "bias"), DType::I64, vec![out])); + } + "igdn" => { + let c = pos(g[0])?; + wants.push(want(whm_name(i, "igdn"), DType::I64, vec![1])); + wants.push(want(whm_name(i, "gamma"), DType::I32, vec![c, c])); + wants.push(want(whm_name(i, "beta"), DType::I64, vec![c])); + } + _ => wants.push(want(whm_name(i, "leaky"), DType::I64, vec![2])), + } + dims.push(g); + } + let range = |name: &str, i: usize| -> Option<(i64, i64)> { + if name == "header" { + Some((0, U32_MAX)) + } else if name.ends_with(".convt") || name.ends_with(".conv") || name.ends_with(".igdn") { + Some(if i < 3 || name.ends_with(".igdn") { (1, U32_MAX) } else { (0, U32_MAX) }) + } else if name.ends_with(".relu") { + Some((0, 0)) + } else if name.ends_with(".leaky") && i == 1 { + Some((0, U32_MAX)) + } else { + None + } + }; + let v = check(set, form, &wants, &range)?; + let mut o = b"WHM1".to_vec(); + for &x in &v["header"] { + o.extend_from_slice(&(x as u32).to_le_bytes()); + } + o.extend_from_slice(&(n as u32).to_le_bytes()); + let put = |o: &mut Vec, vals: &[i64], w: usize| { + for &x in vals { + o.extend_from_slice(&x.to_le_bytes()[..w]); + } + }; + for (&i, &kind) in &kinds { + match kind { + "convt" | "conv" => { + o.push(if kind == "convt" { 0 } else { 1 }); + for &x in &v[&whm_name(i, kind)] { + o.extend_from_slice(&(x as u32).to_le_bytes()); + } + put(&mut o, &v[&whm_name(i, "weight")], 2); + put(&mut o, &v[&whm_name(i, "mult")], 8); + put(&mut o, &v[&whm_name(i, "bias")], 8); + } + "igdn" => { + o.push(2); + o.extend_from_slice(&(v[&whm_name(i, "igdn")][0] as u32).to_le_bytes()); + put(&mut o, &v[&whm_name(i, "gamma")], 4); + put(&mut o, &v[&whm_name(i, "beta")], 8); + } + "relu" => o.push(3), + _ => { + o.push(4); + let l = &v[&whm_name(i, "leaky")]; + o.extend_from_slice(&l[0].to_le_bytes()); + o.extend_from_slice(&(l[1] as u32).to_le_bytes()); + } + } + } + let _ = dims; + Ok(o) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::prior_wire::{encode, load}; + + fn tiny_wsr() -> Vec { + let mut o = b"WSR1".to_vec(); + o.extend_from_slice(&[1, 1]); + o.extend_from_slice(&200u16.to_le_bytes()); + o.extend_from_slice(&[1, 7]); + o.extend_from_slice(&12u16.to_le_bytes()); + o.extend((0..36).map(|i| (i as i8 - 18) as u8)); + for i in 0..12i32 { + o.extend_from_slice(&(i * 1000 - 6000).to_le_bytes()); + } + for i in 0..12u16 { + o.extend_from_slice(&(i * 5000 + 1).to_le_bytes()); + } + o + } + + fn tiny_whm() -> Vec { + let mut o = b"WHM1".to_vec(); + for v in [12u32, 16, 24, 4] { + o.extend_from_slice(&v.to_le_bytes()); + } + // conv 2 → 3, k 1 + o.push(1); + for v in [2u32, 3, 1, 1, 0] { + o.extend_from_slice(&v.to_le_bytes()); + } + for w in [1i16, -2, 3, -4, 5, -6] { + o.extend_from_slice(&w.to_le_bytes()); + } + for m in [100i64, 200, 300, -1, -2, -3] { + o.extend_from_slice(&m.to_le_bytes()); + } + o.push(3); + o.push(2); + o.extend_from_slice(&3u32.to_le_bytes()); + for g in 0..9i32 { + o.extend_from_slice(&(g * 7).to_le_bytes()); + } + for b in [1i64, 2, 3] { + o.extend_from_slice(&b.to_le_bytes()); + } + o.push(4); + o.extend_from_slice(&655i64.to_le_bytes()); + o.extend_from_slice(&16u32.to_le_bytes()); + o + } + + #[test] + fn every_binding_round_trips_through_every_form() { + for (b, native) in [("wsr1/1", tiny_wsr()), ("whm1/1", tiny_whm())] { + for form in Form::ALL { + let file = encode(b, &native, form).unwrap(); + let (name, back) = load(form, &file).unwrap(); + assert_eq!((name, &back), (b, &native), "{b} in {}", form.label()); + } + } + } + + #[test] + fn the_profile_checks_run_in_order() { + let native = tiny_wsr(); + let set = |f: &dyn Fn(&mut TensorSet)| { + let mut s = wsr1_from(&native).unwrap(); + s.metadata = profile_metadata("wsr1/1", Form::Safetensors); + f(&mut s); + crate::prior_wire::safetensors::write(&s).unwrap() + }; + let code = |b: Vec| load(Form::Safetensors, &b).err().map(|r| r.code); + assert_eq!(code(set(&|_| {})), None); + assert_eq!(code(set(&|s| { s.metadata.remove(KEY_PROFILE); })), Some("profile_absent")); + assert_eq!(code(set(&|s| { s.metadata.insert(KEY_BINDING.into(), "nope/1".into()); })), Some("binding_unknown")); + assert_eq!(code(set(&|s| { s.tensors[0].dtype = DType::F32; s.tensors[0].data = vec![0; 4]; })), Some("dtype_not_integer")); + assert_eq!(code(set(&|s| { s.tensors.pop(); })), Some("binding_mismatch")); + assert_eq!(code(set(&|s| { let t = s.tensors.iter_mut().find(|t| t.name == "layer.0.shift").unwrap(); t.data = vec![40]; })), Some("value_out_of_range")); + assert_eq!(code(set(&|s| { let t = s.tensors.iter_mut().find(|t| t.name == "layer.0.mult").unwrap(); t.data[..4].copy_from_slice(&0i32.to_le_bytes()); })), Some("value_out_of_range")); + } + + #[test] + fn a_native_artifact_that_does_not_read_is_named() { + let mut bad = tiny_wsr(); + bad.push(0); + assert_eq!(encode("wsr1/1", &bad, Form::Gguf).unwrap_err().code, "native_malformed"); + assert_eq!(encode("whm1/1", b"WHMX", Form::Gguf).unwrap_err().code, "native_malformed"); + } +} diff --git a/wai/wai-rs/src/prior_wire/cabac.rs b/wai/wai-rs/src/prior_wire/cabac.rs new file mode 100644 index 000000000..c83e7b9b9 --- /dev/null +++ b/wai/wai-rs/src/prior_wire/cabac.rs @@ -0,0 +1,473 @@ +//! The context-adaptive binary arithmetic coding of an NNC integer tensor +//! (`extensions/prior-carriage.md` §12.4): the arithmetic decoder and encoder, the +//! two-rate probability estimate of each context, the context selection, and the +//! binarisation of an integer (significance, sign, greater-than flags, an exponential +//! Golomb remainder), for the case §12 reads: dependent quantisation off (`dq_flag` 0) and +//! the row-wise scan (`scan_order` 0). +//! +//! Everything is integer arithmetic on fixed widths, so a decoder that follows it gives the +//! same integers on every machine. The tables are the coding's own; §12.4 says where they +//! were read from. + +use super::{refuse, Refusal}; + +/// The range of the least probable symbol, by `(|state| >> 7) + (range & 0xe0)`. +#[rustfmt::skip] +pub const RLPS: [u8; 256] = [ + 128, 112, 97, 84, 74, 65, 57, 50, 45, 39, 34, 30, 27, 23, 20, 18, 15, 14, 12, 11, 10, 9, 7, 7, 5, 5, 4, 4, 3, 3, 2, 2, + 142, 125, 108, 93, 82, 72, 63, 56, 50, 43, 38, 33, 30, 26, 22, 20, 17, 16, 13, 12, 11, 10, 8, 8, 6, 6, 5, 5, 3, 3, 2, 2, + 156, 137, 119, 103, 90, 79, 70, 61, 55, 48, 42, 37, 33, 28, 24, 22, 19, 17, 15, 13, 12, 11, 9, 9, 6, 6, 5, 5, 4, 4, 2, 2, + 171, 150, 130, 112, 99, 87, 76, 67, 60, 52, 46, 40, 36, 31, 27, 24, 21, 19, 16, 15, 13, 12, 10, 10, 7, 7, 6, 6, 4, 4, 3, 3, + 185, 162, 141, 121, 107, 94, 82, 73, 65, 56, 50, 43, 39, 34, 29, 26, 22, 21, 17, 16, 14, 13, 11, 11, 8, 8, 6, 6, 4, 4, 3, 3, + 199, 175, 152, 131, 115, 101, 89, 78, 70, 61, 54, 47, 42, 36, 31, 28, 24, 22, 19, 17, 15, 14, 12, 12, 8, 8, 7, 7, 5, 5, 3, 3, + 213, 187, 163, 140, 123, 108, 95, 84, 75, 65, 58, 50, 45, 39, 33, 30, 26, 24, 20, 18, 16, 15, 13, 13, 9, 9, 7, 7, 5, 5, 3, 3, + 228, 200, 174, 150, 132, 116, 102, 90, 80, 70, 62, 54, 48, 42, 36, 32, 28, 26, 22, 20, 18, 16, 14, 14, 10, 10, 8, 8, 6, 6, 4, 4, +]; + +/// The adaptation curve, indexed by `16 + (sign · state >> k)`. +#[rustfmt::skip] +pub const CURVE: [i32; 32] = [ + 2512, 2288, 2064, 1840, 1616, 1392, 1168, 944, 720, 560, 464, 368, 272, 208, 144, 80, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, +]; + +/// The nine initialisations a context may be given: two adaptation shifts (each plus a +/// base) and the starting value of the combined state and of the fast state. +#[rustfmt::skip] +pub const INIT: [[i32; 4]; 9] = [ + [1, 4, 0, 0], [1, 4, -1310, -41], [1, 4, 3039, 95], [0, 5, 0, 0], [2, 6, 962, 30], + [2, 6, 3039, 95], [2, 6, -673, -21], [3, 5, 0, 0], [3, 5, 962, 30], +]; + +/// The number of contexts for `num_gtx` greater-than flags: 24 significance (three for +/// each of eight quantiser states), three sign, two per greater-than flag, 32 for the +/// remainder's prefix, and four more (the one at `len − 2` codes the initialisations). +pub fn context_count(num_gtx: u32) -> usize { + 8 * 3 + 3 + num_gtx as usize * 2 + 32 + 4 +} + +/// One context: the fast state (a signed byte), the combined state (a signed 16-bit +/// value) and the two shifts. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Ctx { + s0: i8, + s01: i16, + sh0: u32, + sh1: u32, +} + +impl Ctx { + pub fn new(init: usize) -> Ctx { + let e = INIT[init]; + Ctx { s0: e[3] as i8, s01: e[2] as i16, sh0: (e[0] + 4) as u32, sh1: e[1] as u32 } + } + /// The most probable symbol. + pub fn mps(&self) -> u32 { + if self.s01 >= 0 { 1 } else { 0 } + } + fn rlps(&self, range: u32) -> Result { + let i = (i32::from(self.s01) >> 7).unsigned_abs() + (range & 0xe0); + RLPS.get(i as usize).map(|&v| u32::from(v)).ok_or_else(|| Refusal::new("nnc_malformed", "a context state outside its table")) + } + fn update(&mut self, bin: u32) -> Result<(), Refusal> { + let sign: i32 = if bin == 1 { 1 } else { -1 }; + let (s0, s1) = (i32::from(self.s0), i32::from(self.s01) - i32::from(self.s0) * 16); + let c = |x: i32, k: u32| -> Result { + let i = 16 + ((sign * x) >> k); + CURVE.get(usize::try_from(i).unwrap_or(usize::MAX)).copied().ok_or_else(|| Refusal::new("nnc_malformed", "a context state outside its curve")) + }; + let s0n = (s0 + sign * (c(s0, 3)? >> self.sh0)) as i8; + let s1n = s1 + sign * (c(s1, 7)? >> self.sh1); + self.s0 = s0n; + self.s01 = (s1n + 16 * i32::from(s0n)) as i16; + Ok(()) + } +} + +/// The arithmetic decoder over the bytes of one entropy-coded payload. +pub struct Decoder<'a> { + b: &'a [u8], + p: usize, + range: u32, + value: u32, + bits_needed: i32, +} + +impl<'a> Decoder<'a> { + pub fn new(b: &'a [u8]) -> Result, Refusal> { + if b.len() < 2 { + return refuse("nnc_truncated", "an entropy-coded payload of fewer than 2 bytes"); + } + Ok(Decoder { b, p: 2, range: 510, value: 256 * u32::from(b[0]) + u32::from(b[1]), bits_needed: -8 }) + } + /// The bytes read so far. + pub fn bytes_read(&self) -> usize { + self.p + } + fn byte(&mut self) -> Result { + let v = *self.b.get(self.p).ok_or_else(|| Refusal::new("nnc_truncated", "the entropy-coded payload ends inside its unit"))?; + self.p += 1; + Ok(u32::from(v)) + } + pub fn bin(&mut self, ctx: &mut Ctx) -> Result { + let rlps = ctx.rlps(self.range)?; + let rmps = self.range - rlps; + let bin = if self.value < (rmps << 7) { + self.range = rmps; + ctx.mps() + } else { + self.value -= rmps << 7; + self.range = rlps; + 1 - ctx.mps() + }; + let n = self.range.leading_zeros() - 23; + self.range <<= n; + self.value <<= n; + self.bits_needed += n as i32; + if self.bits_needed >= 0 { + self.value += self.byte()? << self.bits_needed; + self.bits_needed -= 8; + } + ctx.update(bin)?; + Ok(bin) + } + pub fn bin_ep(&mut self) -> Result { + self.value += self.value; + self.bits_needed += 1; + if self.bits_needed >= 0 { + self.value += self.byte()?; + self.bits_needed = -8; + } + let sr = self.range << 7; + if self.value >= sr { + self.value -= sr; + Ok(1) + } else { + Ok(0) + } + } + pub fn bins_ep(&mut self, n: u32) -> Result { + let mut v = 0u32; + for _ in 0..n { + v = (v << 1) | self.bin_ep()?; + } + Ok(v) + } + /// The terminating bin, then the stop pattern; returns the bytes the payload took. + pub fn finish(&mut self) -> Result { + self.range -= 2; + if self.value < (self.range << 7) { + return refuse("nnc_malformed", "the entropy-coded payload does not end with its terminating bin"); + } + let last = u32::from(self.b[self.p - 1]); + if ((last << (8 + self.bits_needed)) & 0xff) != 0x80 { + return refuse("nnc_malformed", "no stop pattern at the end of the entropy-coded payload"); + } + Ok(self.p) + } +} + +/// The arithmetic encoder. +pub struct Encoder { + out: Vec, + low: u32, + range: u32, + bits_left: u32, + buffered: u8, + n_buffered: u32, +} + +impl Default for Encoder { + fn default() -> Self { + Encoder { out: Vec::new(), low: 0, range: 510, bits_left: 23, buffered: 0, n_buffered: 0 } + } +} + +impl Encoder { + fn write_out(&mut self) { + let lead = self.low >> (24 - self.bits_left); + self.bits_left += 8; + self.low &= 0xffff_ffffu32 >> self.bits_left; + if lead == 0xff { + self.n_buffered += 1; + } else if self.n_buffered > 0 { + let carry = (lead >> 8) as u8; + self.out.push(self.buffered.wrapping_add(carry)); + self.buffered = (lead & 0xff) as u8; + let fill = 0xffu8.wrapping_add(carry); + while self.n_buffered > 1 { + self.out.push(fill); + self.n_buffered -= 1; + } + } else { + self.n_buffered = 1; + self.buffered = lead as u8; + } + } + pub fn bin(&mut self, bin: u32, ctx: &mut Ctx) -> Result<(), Refusal> { + let rlps = ctx.rlps(self.range)?; + self.range -= rlps; + if bin == ctx.mps() { + if self.range < 256 { + self.range += self.range; + self.low = self.low.wrapping_add(self.low); + self.bits_left -= 1; + if self.bits_left < 12 { + self.write_out(); + } + } + } else { + let n = rlps.leading_zeros() - 23; + self.low = self.low.wrapping_add(self.range); + self.range = rlps << n; + self.low = self.low.wrapping_shl(n); + self.bits_left -= n; + if self.bits_left < 12 { + self.write_out(); + } + } + ctx.update(bin) + } + pub fn bin_ep(&mut self, bin: u32) { + self.low = self.low.wrapping_shl(1); + if bin == 1 { + self.low = self.low.wrapping_add(self.range); + } + self.bits_left -= 1; + if self.bits_left < 12 { + self.write_out(); + } + } + pub fn bins_ep(&mut self, v: u32, n: u32) { + for i in (0..n).rev() { + self.bin_ep((v >> i) & 1); + } + } + /// The terminating bin and the flush; returns the payload's bytes. + pub fn finish(mut self) -> Vec { + self.range -= 2; + self.low = self.low.wrapping_add(self.range); + self.low = self.low.wrapping_shl(7); + self.range = 2 << 7; + self.bits_left -= 7; + if self.bits_left < 12 { + self.write_out(); + } + if (self.low >> (32 - self.bits_left)) != 0 { + self.out.push(self.buffered.wrapping_add(1)); + while self.n_buffered > 1 { + self.out.push(0); + self.n_buffered -= 1; + } + self.low = self.low.wrapping_sub(1 << (32 - self.bits_left)); + } else { + if self.n_buffered > 0 { + self.out.push(self.buffered); + } + while self.n_buffered > 1 { + self.out.push(0xff); + self.n_buffered -= 1; + } + } + self.low >>= 8; + self.low <<= 1; + self.low += 1; + self.bits_left -= 1; + self.low = self.low.wrapping_shl(32 - (24 - self.bits_left)); + let mut i = 0; + while i < 24 - self.bits_left { + self.out.push(((self.low >> (24 - i)) & 0xff) as u8); + i += 8; + } + self.out + } +} + +/// The context of a significance or sign flag after a neighbour of value `prev`. +fn neighbour(prev: i64) -> usize { + match prev.signum() { + 0 => 0, + -1 => 1, + _ => 2, + } +} + +/// Decode `n` integers coded with `num_gtx` greater-than flags (§12.4), from the start of +/// `b`; returns them and the bytes the payload took. +pub fn decode_ints(b: &[u8], n: u64, num_gtx: u32) -> Result<(Vec, usize), Refusal> { + let mut d = Decoder::new(b)?; + let len = context_count(num_gtx); + let mut ctx: Vec = (0..len).map(|_| Ctx::new(0)).collect(); + let sel = len - 2; + for i in 0..len - 4 { + if (3..24).contains(&i) { + continue; // the significance contexts of the other quantiser states + } + let mut c = ctx[sel]; + let mut init = 0usize; + if d.bin(&mut c)? == 1 { + init = 1 + d.bins_ep(3)? as usize; + } + ctx[sel] = c; + ctx[i] = Ctx::new(init); + } + let base_rem = 27 + 2 * num_gtx as usize; + // The count is the caller's (bounded by the reader, §12.2); reserve no more than the + // bytes could plausibly hold, and grow if a run of zeros codes denser than that. + let mut out = Vec::with_capacity((n.min(b.len() as u64 * 8)) as usize); + let mut prev = 0i64; + for _ in 0..n { + let mut v: i64 = 0; + if d.bin(&mut ctx[neighbour(prev)])? == 1 { + v = 1; + let neg = d.bin(&mut ctx[24 + neighbour(prev)])? == 1; + let side = usize::from(neg); + let mut gr = d.bin(&mut ctx[27 + side])?; + let mut coded = 1u32; + while gr == 1 && coded < num_gtx { + v += 1; + gr = d.bin(&mut ctx[27 + side + 2 * coded as usize])?; + coded += 1; + } + if gr == 1 { + v += 1; + let mut rem: i64 = 0; + let mut idx = base_rem; + if d.bin(&mut ctx[idx])? == 1 { + rem += 1; + idx += 1; + let mut log2 = 1u32; + loop { + if idx >= base_rem + 32 { + return refuse("nnc_malformed", "a remainder prefix of more than 32 bins"); + } + if d.bin(&mut ctx[idx])? == 0 { + break; + } + rem += 1i64 << log2; + idx += 1; + log2 += 1; + } + rem += i64::from(d.bins_ep(log2)?); + } + v += rem; + } + if v > i64::from(i32::MAX) { + return refuse("nnc_malformed", "an integer past the 32-bit range"); + } + if neg { + v = -v; + } + } + out.push(v); + prev = v; + } + let used = d.finish()?; + Ok((out, used)) +} + +/// Encode `values` (each within ±(2^31 − 1)) with `num_gtx` greater-than flags, every +/// context at its first initialisation (§12.4). +pub fn encode_ints(values: &[i64], num_gtx: u32) -> Result, Refusal> { + let mut e = Encoder::default(); + let len = context_count(num_gtx); + let mut ctx: Vec = (0..len).map(|_| Ctx::new(0)).collect(); + let sel = len - 2; + for i in 0..len - 4 { + if (3..24).contains(&i) { + continue; + } + let mut c = ctx[sel]; + e.bin(0, &mut c)?; + ctx[sel] = c; + } + let base_rem = 27 + 2 * num_gtx as usize; + let mut prev = 0i64; + for &v in values { + if v.unsigned_abs() > i32::MAX as u64 { + return refuse("value_out_of_range", format!("{v} is past the 32-bit range NNC's integer payload carries")); + } + e.bin(u32::from(v != 0), &mut ctx[neighbour(prev)])?; + if v != 0 { + let side = usize::from(v < 0); + e.bin(side as u32, &mut ctx[24 + neighbour(prev)])?; + let mut rem = v.unsigned_abs() - 1; + let mut gr = u32::from(rem > 0); + e.bin(gr, &mut ctx[27 + side])?; + let mut coded = 1u32; + while gr == 1 && coded < num_gtx { + rem -= 1; + gr = u32::from(rem > 0); + e.bin(gr, &mut ctx[27 + side + 2 * coded as usize])?; + coded += 1; + } + if gr == 1 { + rem -= 1; + let mut idx = base_rem; + if rem == 0 { + e.bin(0, &mut ctx[idx])?; + } else { + e.bin(1, &mut ctx[idx])?; + let mut base = 1u64; + let mut log2 = 1u32; + idx += 1; + while rem > base + (1u64 << log2) - 1 { + e.bin(1, &mut ctx[idx])?; + base += 1u64 << log2; + idx += 1; + log2 += 1; + } + e.bin(0, &mut ctx[idx])?; + e.bins_ep((rem - base) as u32, log2); + } + } + } + prev = v; + } + Ok(e.finish()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn integers_round_trip_through_the_coder() { + let mut vals: Vec = vec![0, 0, 1, -1, 2, -2, 11, 12, 13, -13, 100, -1000, 65535, -70000, i32::MAX as i64, -(i32::MAX as i64), 0, 5]; + let mut s = 7u64; + for _ in 0..2000 { + s = s.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407); + let r = (s >> 33) as i64; + vals.push(match r % 5 { + 0 => 0, + 1 => r % 3 - 1, + 2 => r % 41 - 20, + 3 => r % 70001 - 35000, + _ => (r << 3) % (i32::MAX as i64), + }); + } + for gtx in [1u32, 4, 11] { + let b = encode_ints(&vals, gtx).unwrap(); + let (back, used) = decode_ints(&b, vals.len() as u64, gtx).unwrap(); + assert_eq!(back, vals, "gtx {gtx}"); + assert_eq!(used, b.len(), "gtx {gtx}: the decoder reads exactly the payload"); + } + let b = encode_ints(&[], 11).unwrap(); + assert_eq!(decode_ints(&b, 0, 11).unwrap(), (vec![], b.len())); + assert_eq!(encode_ints(&[1i64 << 31], 11).unwrap_err().code, "value_out_of_range"); + } + + #[test] + fn a_damaged_payload_is_refused_or_decodes_to_other_integers() { + let vals: Vec = (0..300).map(|i| (i * 37 % 23) - 11).collect(); + let b = encode_ints(&vals, 11).unwrap(); + assert_eq!(decode_ints(&b[..b.len() - 1], 300, 11).err().map(|r| r.code).is_some(), true); + for i in 0..b.len() { + let mut m = b.clone(); + m[i] ^= 0x10; + if let Ok((v, used)) = decode_ints(&m, 300, 11) { + assert!(v != vals || used != b.len() || m == b, "a flip at byte {i} went unnoticed"); + } + } + } +} diff --git a/wai/wai-rs/src/prior_wire/corpus.rs b/wai/wai-rs/src/prior_wire/corpus.rs new file mode 100644 index 000000000..3aed91851 --- /dev/null +++ b/wai/wai-rs/src/prior_wire/corpus.rs @@ -0,0 +1,399 @@ +//! The conformance corpus of `extensions/prior-carriage.md` §14: +//! `live-conformance/prior-forms/`. +//! +//! [`generate`] writes it from native artifacts other corpora commit, each pinned here by its +//! SHA-256 ([`INPUT_NATIVES`]): the two trained `WSR1` priors of the exact-upscale corpus, its +//! seeded one, and four generated `WHM1` models of the integer-operators corpus. Every file is +//! a function of those bytes, so the corpus is the same bytes on every machine. Each vector +//! states its outcome; the generator asserts that this module reaches it before writing it, +//! and that the corpus gives every code of §13. +//! +//! [`verify`] checks a corpus directory against this module. +//! `live-conformance/prior_forms_verify.py` checks the same files from the extension's text, +//! sharing no code with this module. + +use std::collections::BTreeMap; + +use serde_json::{json, Value}; + +use super::{binding, encode, gguf, load, nnc, safetensors, DType, Form, Tensor, TensorSet, CODES}; + +/// The native artifacts the corpus reads: `(name, binding, path from the corpus directory, +/// SHA-256)`. +pub const INPUT_NATIVES: &[(&str, &str, &str, &str)] = &[ + ("generic", "wsr1/1", "../upscale/priors/generic.wsr", "0106ab198efcbc009a32c4fbf85da4a557daf827d1f92d8de9c7939953329915"), + ("title", "wsr1/1", "../upscale/priors/title.wsr", "9ea90833e37dc07982682799abf95840b32fa4e152eebc1830776a5b0c843a07"), + ("seeded", "wsr1/1", "../upscale/priors/seeded.wsr", "18b634abf31169c3c5126e258da73ca6cb0c4ba219220fca6f7736f869b94807"), + ("hyper_g_s", "whm1/1", "../../ops-conformance/sets/hyper/g_s.whm", "fbd6b941d412e7bd0bc73fef90e4131780942aeb06b441abe887b4eb75768d9e"), + ("hyper_h_s", "whm1/1", "../../ops-conformance/sets/hyper/h_s.whm", "0aad151f7f4a27f9c28fa3452692642802a9146ac102fcf664fdf182c80670a7"), + ("world_t", "whm1/1", "../../ops-conformance/sets/world/T.whm", "a47ad9f8d12f8c0e8edde8e61cabe31794a8d7cdb6a4768c95db6a5816713c02"), + ("world_g_s", "whm1/1", "../../ops-conformance/sets/world/g_s.whm", "1c13bee0387fd312580e44ad5f726ada6bd8f6cd06546dd92d5d66a72d9ab3e1"), +]; + +fn sha_hex(b: &[u8]) -> String { + crate::pin::sha256_hex(b) +} + +/// The inputs whose bytes are missing or do not match their pins. +pub fn check_inputs(natives: &BTreeMap>) -> Vec { + INPUT_NATIVES + .iter() + .filter(|(n, _, _, d)| natives.get(*n).map(|b| sha_hex(b)) != Some(d.to_string())) + .map(|(n, ..)| n.to_string()) + .collect() +} + +fn ext(form: Form) -> &'static str { + form.label() +} + +/// A safetensors file with header text `h` (padded with spaces to 8 bytes) and `data`. +fn st_raw(h: &str, data: &[u8]) -> Vec { + let mut h = h.as_bytes().to_vec(); + while (8 + h.len()) % 8 != 0 { + h.push(b' '); + } + let mut o = (h.len() as u64).to_le_bytes().to_vec(); + o.extend(h); + o.extend_from_slice(data); + o +} + +fn data_of(set: &TensorSet) -> Vec { + set.clone().sorted().tensors.iter().flat_map(|t| t.data.clone()).collect() +} + +/// The profile tensors of the native `n` under `b`, with the metadata of `form`. +fn profile_set(b: &str, native: &[u8], form: Form) -> TensorSet { + let bd = binding::by_name(b).expect("registered"); + let mut s = (bd.from_native)(native).expect("a native that reads"); + s.metadata = binding::profile_metadata(b, form); + s.sorted() +} + +fn edit(set: &TensorSet, name: &str, f: impl FnOnce(&mut Tensor)) -> TensorSet { + let mut s = set.clone(); + f(s.tensors.iter_mut().find(|t| t.name == name).expect("a tensor of the set")); + s +} + +/// The units of an NNC bitstream, as `(offset, size)`. +fn nnc_units(b: &[u8]) -> Vec<(usize, usize)> { + let mut v = Vec::new(); + let mut at = 0; + while at < b.len() { + let size = if b[at] & 0x80 == 0 { (u16::from_be_bytes([b[at], b[at + 1]]) & 0x7fff) as usize } else { (u32::from_be_bytes([b[at], b[at + 1], b[at + 2], b[at + 3]]) & 0x7fff_ffff) as usize }; + v.push((at, size)); + at += size; + } + v +} + +/// One case: a vector entry and the files it adds. +struct Case { + v: Value, +} + +/// Generate the corpus from `natives` (by name, matching [`INPUT_NATIVES`]): file name → +/// bytes, `vectors.json` included. +pub fn generate(natives: &BTreeMap>) -> BTreeMap> { + assert!(check_inputs(natives).is_empty(), "the inputs must match their pins"); + let mut files: BTreeMap> = BTreeMap::new(); + let mut cases: Vec = Vec::new(); + let add = |files: &mut BTreeMap>, path: String, bytes: Vec| { + assert!(files.insert(path.clone(), bytes).is_none(), "{path} twice"); + path + }; + + // Every native in every form: the encode, then the load. + for &(name, b, _, _) in INPUT_NATIVES { + let native = &natives[name]; + for form in Form::ALL { + let file = encode(b, native, form).unwrap_or_else(|e| panic!("{name} in {}: {e}", form.label())); + let (got_b, got) = load(form, &file).expect("loads"); + assert_eq!((got_b, &got), (b, native)); + let path = add(&mut files, format!("files/{name}.{}", ext(form)), file); + cases.push(Case { v: json!({"name": format!("{name}_{}_encode", ext(form)), "op": "encode", "form": form.capability(), "native": name, "binding": b, "outcome": "accept", "file": path}) }); + cases.push(Case { v: json!({"name": format!("{name}_{}_load", ext(form)), "op": "load", "form": form.capability(), "file": path, "outcome": "accept", "binding": b, "native": name}) }); + } + } + + // Native artifacts that do not read, and one no NNC integer payload carries. + let mut trailing = natives["seeded"].clone(); + trailing.push(0); + let mut badtag = natives["hyper_h_s"].clone(); + badtag[20] = 9; // the first layer's tag + let mut wide = natives["hyper_h_s"].clone(); + // hyper_h_s: a conv layer first (tag 1, five u32), then its i16 weights, then mult and + // bias (i64): make the first bias 2^40. + let (inc, out, k) = (u32::from_le_bytes(wide[21..25].try_into().unwrap()) as usize, u32::from_le_bytes(wide[25..29].try_into().unwrap()) as usize, u32::from_le_bytes(wide[29..33].try_into().unwrap()) as usize); + let bias0 = 21 + 20 + 2 * inc * out * k * k + 8 * out; + wide[bias0..bias0 + 8].copy_from_slice(&(1i64 << 40).to_le_bytes()); + let mut gen_natives: Vec<(&str, &str, Vec)> = vec![("trailing", "wsr1/1", trailing), ("badtag", "whm1/1", badtag), ("wide", "whm1/1", wide)]; + for (n, b, bytes) in gen_natives.drain(..) { + let ext_n = if b == "wsr1/1" { "wsr" } else { "whm" }; + let path = add(&mut files, format!("files/natives/{n}.{ext_n}"), bytes.clone()); + for form in Form::ALL { + let r = encode(b, &bytes, form); + let v = match r { + Ok(file) => { + let fpath = add(&mut files, format!("files/{n}.{}", ext(form)), file); + json!({"name": format!("{n}_{}_encode", ext(form)), "op": "encode", "form": form.capability(), "native_file": path, "binding": b, "outcome": "accept", "file": fpath}) + } + Err(e) => json!({"name": format!("{n}_{}_encode", ext(form)), "op": "encode", "form": form.capability(), "native_file": path, "binding": b, "outcome": "refuse", "code": e.code}), + }; + cases.push(Case { v }); + } + } + + // Files refused on load: built from the seeded prior's tensors. + let seeded = &natives["seeded"]; + let st_set = profile_set("wsr1/1", seeded, Form::Safetensors); + let st = safetensors::write(&st_set).unwrap(); + let st_h = safetensors::header(&st_set).unwrap(); + let st_d = data_of(&st_set); + let gg_set = profile_set("wsr1/1", seeded, Form::Gguf); + let gg = gguf::write(&gg_set).unwrap(); + let nn_set = profile_set("wsr1/1", seeded, Form::Nnc); + let nn = nnc::write(&nn_set).unwrap(); + + let mut neg: Vec<(&str, Form, Vec)> = Vec::new(); + // safetensors + neg.push(("st_truncated", Form::Safetensors, st[..6].to_vec())); + let mut x = st.clone(); + x[..8].copy_from_slice(&(safetensors::MAX_HEADER + 1).to_le_bytes()); + neg.push(("st_header_too_large", Form::Safetensors, x)); + let mut x = st.clone(); + x[..8].copy_from_slice(&(st.len() as u64).to_le_bytes()); + neg.push(("st_header_past_end", Form::Safetensors, x)); + let first = st_h.find("\"act_hi\":").expect("act_hi"); + let act = &st_h[first..st_h[first..].find('}').map(|e| first + e + 1).expect("}")]; + neg.push(("st_duplicate_member", Form::Safetensors, st_raw(&format!("{},{act}}}", &st_h[..st_h.len() - 1]), &st_d))); + neg.push(("st_not_json", Form::Safetensors, st_raw("{\"act_hi\":", &st_d))); + neg.push(("st_extra_member", Form::Safetensors, st_raw(&st_h.replacen("\"data_offsets\":[0,2]}", "\"data_offsets\":[0,2],\"x\":0}", 1), &st_d))); + neg.push(("st_dtype_unknown", Form::Safetensors, st_raw(&st_h.replacen("\"dtype\":\"I16\"", "\"dtype\":\"Q4_0\"", 1), &st_d))); + let mut gap = st_d.clone(); + gap.insert(0, 0); + neg.push(("st_offset_gap", Form::Safetensors, st_raw(&st_h, &gap))); + neg.push(("st_leading_space", Form::Safetensors, st_raw(&format!(" {st_h}"), &st_d))); + { + // The tensors' members in descending order of their names; offsets unchanged. + let doc = crate::strict_json::read(st_h.as_bytes()).unwrap(); + let mut m: Vec = doc.as_obj().unwrap().iter().filter(|(k, _)| k != safetensors::METADATA).map(|(k, _)| { + let s = st_h.find(&format!("{}:", crate::strict_json::quote(k))).unwrap(); + let e = s + st_h[s..].find('}').unwrap() + 1; + st_h[s..e].to_string() + }).collect(); + m.reverse(); + let meta_end = st_h.find("},").unwrap() + 1; + neg.push(("st_member_order", Form::Safetensors, st_raw(&format!("{},{}}}", &st_h[..meta_end], m.join(",")), &st_d))); + } + let mut s = st_set.clone(); + s.metadata.remove(super::KEY_PROFILE); + neg.push(("st_profile_absent", Form::Safetensors, safetensors::write(&s).unwrap())); + let mut s = st_set.clone(); + s.metadata.insert(super::KEY_BINDING.into(), "wsr9/1".into()); + neg.push(("st_binding_unknown", Form::Safetensors, safetensors::write(&s).unwrap())); + let s = edit(&st_set, "act_hi", |t| t.dtype = DType::F16); + neg.push(("st_float_tensor", Form::Safetensors, safetensors::write(&s).unwrap())); + let mut s = st_set.clone(); + s.tensors.retain(|t| t.name != "layer.0.shift"); + neg.push(("st_missing_tensor", Form::Safetensors, safetensors::write(&s).unwrap())); + let s = edit(&st_set, "act_hi", |t| t.shape = vec![1, 1]); + neg.push(("st_wrong_shape", Form::Safetensors, safetensors::write(&s).unwrap())); + let s = edit(&st_set, "layer.0.shift", |t| t.data = vec![40]); + neg.push(("st_shift_range", Form::Safetensors, safetensors::write(&s).unwrap())); + let s = edit(&st_set, "layer.0.mult", |t| t.data[..4].copy_from_slice(&0i32.to_le_bytes())); + neg.push(("st_mult_zero", Form::Safetensors, safetensors::write(&s).unwrap())); + // GGUF + let mut x = gg.clone(); + x[3] = b'G'; + neg.push(("gg_bad_magic", Form::Gguf, x)); + let mut x = gg.clone(); + x[4..8].copy_from_slice(&2u32.to_le_bytes()); + neg.push(("gg_version_2", Form::Gguf, x)); + neg.push(("gg_truncated_header", Form::Gguf, gg[..40].to_vec())); + { + // Keys are written in order from byte 24: general.architecture, wai.binding, + // wai.profile. Rename the second to the third: a key twice. + let kv2 = 24 + 8 + "general.architecture".len() + 4 + 8 + "wai".len(); + let mut x = gg.clone(); + x[kv2 + 8..kv2 + 8 + 11].copy_from_slice(b"wai.profile"); + neg.push(("gg_duplicate_key", Form::Gguf, x)); + let mut x = gg.clone(); + x[kv2 + 8] = b'W'; + neg.push(("gg_bad_key", Form::Gguf, x)); + } + let tinfo = gg.windows(8 + 6).position(|w| w == [&6u64.to_le_bytes()[..], b"act_hi"].concat().as_slice()).expect("act_hi info"); + let mut x = gg.clone(); + x[tinfo + 14..tinfo + 18].copy_from_slice(&5u32.to_le_bytes()); + neg.push(("gg_five_dims", Form::Gguf, x)); + let ty_at = tinfo + 14 + 4 + 8; + let mut x = gg.clone(); + x[ty_at..ty_at + 4].copy_from_slice(&2u32.to_le_bytes()); + neg.push(("gg_q4_0", Form::Gguf, x)); + let mut x = gg.clone(); + // The second tensor's offset (act_hi is first, at 0; layer.0.bias follows at 32). + let off2 = gg.windows(8 + 12).position(|w| w == [&12u64.to_le_bytes()[..], b"layer.0.bias"].concat().as_slice()).expect("layer.0.bias info") + 20 + 4 + 8 + 4; + x[off2..off2 + 8].copy_from_slice(&33u64.to_le_bytes()); + neg.push(("gg_misaligned", Form::Gguf, x)); + { + // general.alignment = 32 written explicitly, after general.architecture: reads, is + // not canonical. + let mut kv = 17u64.to_le_bytes().to_vec(); + kv.extend_from_slice(b"general.alignment"); + kv.extend_from_slice(&4u32.to_le_bytes()); + kv.extend_from_slice(&32u32.to_le_bytes()); + let at = 24 + 8 + "general.architecture".len() + 4 + 8 + 3; + neg.push(("gg_alignment_explicit", Form::Gguf, gguf_insert_kv(&gg, at, &kv))); + } + let mut x = gg.clone(); + x.push(0); + neg.push(("gg_trailing_byte", Form::Gguf, x)); + let mut s = gg_set.clone(); + s.metadata.remove("general.architecture"); + neg.push(("gg_no_architecture", Form::Gguf, gguf::write(&s).unwrap())); + { + let (hend, dstart) = (gguf_header_end(&gg), gguf_data_start(&gg)); + assert!(hend < dstart, "the seeded prior's GGUF header is padded"); + let mut x = gg.clone(); + x[hend] = 1; + neg.push(("gg_nonzero_padding", Form::Gguf, x)); + } + // NNC + let units = nnc_units(&nn); + neg.push(("nnc_truncated", Form::Nnc, nn[..nn.len() - 1].to_vec())); + neg.push(("nnc_starts_mps", Form::Nnc, nn[units[1].0..].to_vec())); + let mut x = nn.clone(); + let mps_last = units[1].0 + units[1].1 - 1; + assert_eq!(x[mps_last], 0x80); + x[mps_last] = 0; + neg.push(("nnc_alignment_bit", Form::Nnc, x)); + let ndu0 = units[3]; + let mut x = nn.clone(); + x[ndu0.0 + 3] = (x[ndu0.0 + 3] & 0x07) | ((nnc::NNR_PT_FLOAT as u8) << 3); + neg.push(("nnc_pt_float", Form::Nnc, x)); + { + // dq_flag: the first bit after the topology element name's zero byte. + let name_end = nn[ndu0.0 + 4..].iter().position(|&c| c == 0).unwrap() + ndu0.0 + 4; + let mut x = nn.clone(); + x[name_end + 1] |= 0x80; + neg.push(("nnc_dq_flag", Form::Nnc, x)); + } + { + // An NNR_LPS unit after NNR_MPS: size 4, type 2, independently decodable, then + // lps_self_contained_flag 1 and seven reserved zero bits. + let mut x = nn[..units[1].0 + units[1].1].to_vec(); + x.extend_from_slice(&[0x00, 0x04, (nnc::NNR_LPS as u8) << 2 | 0x02, 0x80]); + x.extend_from_slice(&nn[units[2].0..]); + neg.push(("nnc_lps_unit", Form::Nnc, x)); + } + { + // NNR_STR in the 31-bit size form: reads, is not canonical. + let mut x = vec![0x80, 0x00, 0x00, 0x06]; + x.extend_from_slice(&nn[2..4]); + x.extend_from_slice(&nn[4..]); + neg.push(("nnc_long_size", Form::Nnc, x)); + } + let raw = edit(&nn_set, "act_hi", |t| { + t.dtype = DType::F32; + t.data = 200f32.to_le_bytes().to_vec(); + }); + neg.push(("nnc_raw_float", Form::Nnc, nnc::write(&raw).unwrap())); + { + // One bit of the entropy-coded payload of the first weight tensor flipped. + let w0 = units.iter().copied().find(|&(o, s)| nn[o..o + s].windows(15).any(|w| w == b"layer.0.weight\0")).unwrap(); + let mut x = nn.clone(); + x[w0.0 + w0.1 - 5] ^= 0x08; + neg.push(("nnc_payload_bit", Form::Nnc, x)); + } + + let mut seen_codes = std::collections::BTreeSet::new(); + for c in &cases { + if let Some(code) = c.v.get("code").and_then(Value::as_str) { + seen_codes.insert(code.to_string()); + } + } + for (name, form, bytes) in neg { + let code = load(form, &bytes).expect_err(name).code; + seen_codes.insert(code.to_string()); + let path = add(&mut files, format!("files/neg/{name}.{}", ext(form)), bytes); + cases.push(Case { v: json!({"name": name, "op": "load", "form": form.capability(), "file": path, "outcome": "refuse", "code": code}) }); + } + let missing: Vec<&&str> = CODES.iter().filter(|c| !seen_codes.contains(**c)).collect(); + assert!(missing.is_empty(), "the corpus gives no vector the codes {missing:?}"); + + let natives_v: Vec = INPUT_NATIVES.iter().map(|(n, b, p, d)| json!({"name": n, "binding": b, "path": p, "sha256": d})).collect(); + let files_v: Vec = files.iter().map(|(p, b)| json!({"file": p, "sha256": sha_hex(b)})).collect(); + let doc = json!({ + "about": "the conformance corpus of extensions/prior-carriage.md §14, written by wai_prior_forms_vectors gen", + "natives": natives_v, + "files": files_v, + "vectors": cases.into_iter().map(|c| c.v).collect::>(), + }); + let mut text = serde_json::to_string_pretty(&doc).expect("json"); + text.push('\n'); + files.insert("vectors.json".into(), text.into_bytes()); + files +} + +/// The end of a GGUF file's tensor descriptions. +fn gguf_header_end(gg: &[u8]) -> usize { + let rd = |p: usize| u64::from_le_bytes(gg[p..p + 8].try_into().unwrap()) as usize; + let (n_t, n_kv) = (rd(8), rd(16)); + let mut p = 24usize; + for _ in 0..n_kv { + p += 8 + rd(p) + 4; // the key and the value type (every value here is a string) + p += 8 + rd(p); + } + for _ in 0..n_t { + p += 8 + rd(p); + let nd = u32::from_le_bytes(gg[p..p + 4].try_into().unwrap()) as usize; + p += 4 + 8 * nd + 4 + 8; + } + p +} + +/// The offset of a canonical GGUF file's tensor data: the header's end, rounded up to 32. +fn gguf_data_start(gg: &[u8]) -> usize { + gguf_header_end(gg).div_ceil(32) * 32 +} + +/// `gg` with the key-value pair `kv` inserted at byte `at` of its header, the pair count +/// raised by one and the header re-padded so the data still begins at a multiple of 32. +fn gguf_insert_kv(gg: &[u8], at: usize, kv: &[u8]) -> Vec { + let mut hdr = gg[..gguf_header_end(gg)].to_vec(); + hdr.splice(at..at, kv.iter().copied()); + let n = u64::from_le_bytes(hdr[16..24].try_into().unwrap()) + 1; + hdr[16..24].copy_from_slice(&n.to_le_bytes()); + while hdr.len() % 32 != 0 { + hdr.push(0); + } + hdr.extend_from_slice(&gg[gguf_data_start(gg)..]); + hdr +} + +/// Check a corpus directory's files (`files`, by path, with the natives under their +/// [`INPUT_NATIVES`] names) against this module: every file regenerates byte for byte, and +/// every vector reaches its outcome. Returns the failures. +pub fn verify(corpus: &BTreeMap>, natives: &BTreeMap>) -> Vec { + let mut bad = Vec::new(); + let missing = check_inputs(natives); + if !missing.is_empty() { + return vec![format!("natives missing or not matching their pins: {missing:?}")]; + } + let want = generate(natives); + for (p, b) in &want { + match corpus.get(p) { + Some(x) if x == b => {} + Some(_) => bad.push(format!("{p} does not regenerate byte for byte")), + None => bad.push(format!("{p} is missing")), + } + } + for p in corpus.keys() { + if !want.contains_key(p) { + bad.push(format!("{p} is not a file of the corpus")); + } + } + bad +} diff --git a/wai/wai-rs/src/prior_wire/gguf.rs b/wai/wai-rs/src/prior_wire/gguf.rs new file mode 100644 index 000000000..e3908ff19 --- /dev/null +++ b/wai/wai-rs/src/prior_wire/gguf.rs @@ -0,0 +1,314 @@ +//! The GGUF form (`extensions/prior-carriage.md` §9.2): version 3, little-endian — a header +//! of key-value metadata and tensor descriptions, then the tensors' bytes, each at a +//! multiple of the file's alignment. + +use super::{refuse, DType, Refusal, Tensor, TensorSet}; + +/// The four bytes a GGUF file begins with. +pub const MAGIC: [u8; 4] = *b"GGUF"; +/// The version this reader reads. +pub const VERSION: u32 = 3; +/// The alignment when `general.alignment` is absent. +pub const DEFAULT_ALIGNMENT: u64 = 32; +/// The longest tensor name, in bytes. +pub const MAX_NAME: usize = 64; +/// The most dimensions a tensor has. +pub const MAX_DIMS: usize = 4; +/// The metadata value type of a string. +pub const TYPE_STRING: u32 = 8; +const TYPE_ARRAY: u32 = 9; + +struct Cur<'a> { + b: &'a [u8], + p: usize, +} + +impl<'a> Cur<'a> { + fn take(&mut self, n: u64) -> Result<&'a [u8], Refusal> { + let n = usize::try_from(n).map_err(|_| Refusal::new("header_truncated", "a length past the address space"))?; + let end = self.p.checked_add(n).filter(|&e| e <= self.b.len()); + let Some(end) = end else { return refuse("header_truncated", format!("{n} bytes due at byte {} of {}", self.p, self.b.len())) }; + let s = &self.b[self.p..end]; + self.p = end; + Ok(s) + } + fn u32(&mut self) -> Result { + Ok(u32::from_le_bytes(self.take(4)?.try_into().expect("4"))) + } + fn u64(&mut self) -> Result { + Ok(u64::from_le_bytes(self.take(8)?.try_into().expect("8"))) + } + fn string(&mut self) -> Result { + let n = self.u64()?; + let s = self.take(n)?; + String::from_utf8(s.to_vec()).map_err(|_| Refusal::new("header_malformed", "a string that is not UTF-8")) + } +} + +/// The size of a fixed-size metadata value type, or `None` for string and array. +fn fixed_size(t: u32) -> Option { + match t { + 0 | 1 | 7 => Some(1), + 2 | 3 => Some(2), + 4..=6 => Some(4), + 10..=12 => Some(8), + _ => None, + } +} + +/// Read one metadata value of type `t`; returns the string when it is one. +fn value(c: &mut Cur<'_>, t: u32) -> Result, Refusal> { + if let Some(n) = fixed_size(t) { + let v = c.take(n)?; + if t == 7 && v[0] > 1 { + return refuse("header_malformed", "a boolean that is neither 0 nor 1"); + } + return Ok(None); + } + match t { + TYPE_STRING => Ok(Some(c.string()?)), + TYPE_ARRAY => { + let et = c.u32()?; + if et == TYPE_ARRAY { + return refuse("header_malformed", "an array of arrays (this reader reads one level of array)"); + } + if et != TYPE_STRING && fixed_size(et).is_none() { + return refuse("header_malformed", format!("an array of value type {et}")); + } + let n = c.u64()?; + // Each element takes at least one byte (a string at least eight), so a count + // past the bytes left is a truncated header, refused before any reservation. + let min = fixed_size(et).unwrap_or(8); + if n.checked_mul(min).is_none_or(|need| need > (c.b.len() - c.p) as u64) { + return refuse("header_truncated", format!("an array of {n} elements past the end")); + } + for _ in 0..n { + value(c, et)?; + } + Ok(None) + } + _ => refuse("header_malformed", format!("a metadata value type {t}")), + } +} + +fn key_ok(k: &str) -> bool { + !k.is_empty() && k.len() <= 65535 && k.split('.').all(|s| !s.is_empty() && s.bytes().all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_')) +} + +/// Read a GGUF file (§9.2): every rule of the format, no canonical check. String metadata +/// goes to the set's metadata; a value of another type is read and checked but not kept, +/// so a file that holds one is not canonical (§11.2). +pub fn read(b: &[u8]) -> Result { + if b.len() < 4 { + return refuse("header_truncated", "fewer than the 4 bytes of the magic"); + } + if b[..4] != MAGIC { + return refuse("bad_magic", "the file does not begin with GGUF"); + } + let mut c = Cur { b, p: 4 }; + let version = c.u32()?; + if version != VERSION { + return refuse("version_unsupported", format!("version {version} (this reader reads version 3, little-endian)")); + } + let n_tensors = c.u64()?; + let n_kv = c.u64()?; + // A key-value pair takes at least 12 bytes, a tensor description at least 28. + if n_kv.checked_mul(12).is_none_or(|v| v > b.len() as u64) || n_tensors.checked_mul(28).is_none_or(|v| v > b.len() as u64) { + return refuse("header_truncated", format!("{n_kv} key-value pairs and {n_tensors} tensors past the end")); + } + let mut set = TensorSet::default(); + let mut keys = std::collections::BTreeSet::new(); + let mut alignment = DEFAULT_ALIGNMENT; + for _ in 0..n_kv { + let k = c.string()?; + if !key_ok(&k) { + return refuse("header_malformed", format!("key {k:?} is not lower_snake_case segments joined by '.'")); + } + if !keys.insert(k.clone()) { + return refuse("header_malformed", format!("key {k:?} twice")); + } + let t = c.u32()?; + let at = c.p; + let s = value(&mut c, t)?; + if k == "general.alignment" { + let v = if t == 4 { u32::from_le_bytes(b[at..at + 4].try_into().expect("4")) as u64 } else { 0 }; + if t != 4 || v == 0 || v % 8 != 0 { + return refuse("header_malformed", "general.alignment is not a non-zero uint32 multiple of 8"); + } + alignment = v; + } + if let Some(s) = s { + set.metadata.insert(k, s); + } + } + struct Info { + offset: u64, + len: u64, + } + let mut infos = Vec::new(); + let mut names = std::collections::BTreeSet::new(); + for _ in 0..n_tensors { + let name = c.string()?; + if name.is_empty() || name.len() > MAX_NAME { + return refuse("tensor_malformed", format!("tensor name {name:?} is empty or longer than {MAX_NAME} bytes")); + } + if !names.insert(name.clone()) { + return refuse("tensor_malformed", format!("tensor {name:?} twice")); + } + let nd = c.u32()? as usize; + if !(1..=MAX_DIMS).contains(&nd) { + return refuse("tensor_malformed", format!("{name:?}: {nd} dimensions")); + } + let mut dims = Vec::with_capacity(nd); + for _ in 0..nd { + dims.push(c.u64()?); + } + if dims.contains(&0) { + return refuse("tensor_malformed", format!("{name:?}: a dimension of 0")); + } + let ty = c.u32()?; + let Some(dtype) = DType::from_gguf_type(ty) else { return refuse("dtype_unknown", format!("{name:?}: type {ty}")) }; + let offset = c.u64()?; + if offset % alignment != 0 { + return refuse("offset_mismatch", format!("{name:?}: offset {offset} is not a multiple of {alignment}")); + } + if dtype == DType::Q8_0 && dims[0] % 32 != 0 { + return refuse("tensor_malformed", format!("{name:?}: Q8_0 rows of {} elements", dims[0])); + } + let count = dims.iter().try_fold(1u64, |a, &d| a.checked_mul(d)); + let Some(len) = count.and_then(|n| dtype.bytes_for(n)) else { + return refuse("tensor_malformed", format!("{name:?}: the element count overflows")); + }; + let shape: Vec = dims.iter().rev().copied().collect(); + infos.push(Info { offset, len }); + set.tensors.push(Tensor { name, dtype, shape, data: Vec::new() }); + } + let header_end = c.p as u64; + let data_start = header_end.div_ceil(alignment) * alignment; + if data_start > b.len() as u64 { + return refuse("header_truncated", "the file ends before its tensor data begins"); + } + if b[header_end as usize..data_start as usize].iter().any(|&x| x != 0) { + return refuse("header_malformed", "the padding after the header is not zero"); + } + let data = &b[data_start as usize..]; + let mut order: Vec = (0..infos.len()).collect(); + order.sort_by_key(|&i| infos[i].offset); + let mut at = 0u64; + for &i in &order { + let (o, l) = (infos[i].offset, infos[i].len); + if o < at { + return refuse("offset_mismatch", format!("{:?} overlaps the tensor before it", set.tensors[i].name)); + } + let end = o.checked_add(l).filter(|&e| e <= data.len() as u64); + let Some(end) = end else { return refuse("offset_mismatch", format!("{:?} runs past the end of the file", set.tensors[i].name)) }; + set.tensors[i].data = data[o as usize..end as usize].to_vec(); + at = end; + } + Ok(set.sorted()) +} + +/// The canonical bytes of `set` (§11.2): string metadata in ascending byte order of the +/// keys, the tensors in ascending byte order of their names, alignment 32 left implicit, +/// each tensor at the first multiple of 32 after the one before, zero padding, and nothing +/// after the last tensor. +pub fn write(set: &TensorSet) -> Result, Refusal> { + let set = set.clone().sorted(); + let mut o = MAGIC.to_vec(); + o.extend_from_slice(&VERSION.to_le_bytes()); + o.extend_from_slice(&(set.tensors.len() as u64).to_le_bytes()); + o.extend_from_slice(&(set.metadata.len() as u64).to_le_bytes()); + let s = |o: &mut Vec, v: &str| { + o.extend_from_slice(&(v.len() as u64).to_le_bytes()); + o.extend_from_slice(v.as_bytes()); + }; + for (k, v) in &set.metadata { + if !key_ok(k) || k == "general.alignment" { + return refuse("header_malformed", format!("key {k:?} cannot be written as a string")); + } + s(&mut o, k); + o.extend_from_slice(&TYPE_STRING.to_le_bytes()); + s(&mut o, v); + } + let mut offsets = Vec::with_capacity(set.tensors.len()); + let mut at = 0u64; + for (i, t) in set.tensors.iter().enumerate() { + if i > 0 && set.tensors[i - 1].name == t.name { + return refuse("tensor_malformed", format!("{:?} named twice", t.name)); + } + if t.name.is_empty() || t.name.len() > MAX_NAME || !(1..=MAX_DIMS).contains(&t.shape.len()) || t.shape.contains(&0) { + return refuse("tensor_malformed", format!("{:?} cannot be a GGUF tensor (name of 1 to 64 bytes, 1 to 4 dimensions, none 0)", t.name)); + } + let Some(ty) = t.dtype.gguf_type() else { return refuse("dtype_unknown", format!("{:?}: {:?} has no GGUF type", t.name, t.dtype)) }; + if t.count().and_then(|n| t.dtype.bytes_for(n)) != Some(t.data.len() as u64) { + return refuse("offset_mismatch", format!("{:?}: {} bytes for its shape and dtype", t.name, t.data.len())); + } + at = at.div_ceil(DEFAULT_ALIGNMENT) * DEFAULT_ALIGNMENT; + offsets.push(at); + s(&mut o, &t.name); + o.extend_from_slice(&(t.shape.len() as u32).to_le_bytes()); + for d in t.shape.iter().rev() { + o.extend_from_slice(&d.to_le_bytes()); + } + o.extend_from_slice(&ty.to_le_bytes()); + o.extend_from_slice(&at.to_le_bytes()); + at += t.data.len() as u64; + } + let pad = |o: &mut Vec, base: usize| { + while (o.len() - base) as u64 % DEFAULT_ALIGNMENT != 0 { + o.push(0); + } + }; + pad(&mut o, 0); + let base = o.len(); + for (t, &off) in set.tensors.iter().zip(&offsets) { + o.resize(base + off as usize, 0); + o.extend_from_slice(&t.data); + } + Ok(o) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn sample() -> TensorSet { + let mut s = TensorSet::default(); + s.metadata.insert("wai.binding".into(), "wsr1/1".into()); + s.tensors.push(Tensor::from_ints("w", DType::I8, vec![2, 3], &[1, 2, 3, -4, -5, -6])); + s.tensors.push(Tensor::from_ints("b", DType::I32, vec![2], &[7, -8])); + s + } + + #[test] + fn writes_canonically_and_reads_back() { + let bytes = write(&sample()).unwrap(); + assert_eq!(&bytes[..4], b"GGUF"); + let back = read(&bytes).unwrap(); + assert_eq!(back, sample().sorted()); + assert_eq!(write(&back).unwrap(), bytes); + // Shape [2, 3] is written as dimensions [3, 2], innermost first. + assert!(bytes.windows(9).any(|w| w == b"w\x02\x00\x00\x00\x03\x00\x00\x00")); + } + + #[test] + fn refuses_what_the_format_forbids() { + let good = write(&sample()).unwrap(); + let code = |b: &[u8]| read(b).err().map(|r| r.code); + assert_eq!(code(b"GG"), Some("header_truncated")); + let mut m = good.clone(); + m[0] = b'X'; + assert_eq!(code(&m), Some("bad_magic")); + let mut v = good.clone(); + v[4..8].copy_from_slice(&2u32.to_le_bytes()); + assert_eq!(code(&v), Some("version_unsupported")); + let mut cnt = good.clone(); + cnt[8..16].copy_from_slice(&(u64::MAX / 2).to_le_bytes()); + assert_eq!(code(&cnt), Some("header_truncated")); + assert_eq!(code(&good[..good.len() - 1]), Some("offset_mismatch")); + // Trailing bytes read, but are not canonical. + let mut t = good.clone(); + t.push(0); + assert_ne!(write(&read(&t).unwrap()).unwrap(), t); + } +} diff --git a/wai/wai-rs/src/prior_wire/nnc.rs b/wai/wai-rs/src/prior_wire/nnc.rs new file mode 100644 index 000000000..42dc2a14d --- /dev/null +++ b/wai/wai-rs/src/prior_wire/nnc.rs @@ -0,0 +1,489 @@ +//! The NNC form (`extensions/prior-carriage.md` §12): an ISO/IEC 15938-17 bitstream of NNR +//! units, read and written for the subset §12.1 states — a start unit, one model parameter +//! set, at most one topology unit, and one compressed data unit per tensor, each of payload +//! type `NNR_PT_INT` (integers coded as §12.4 codes them, [`super::cabac`]) or +//! `NNR_PT_RAW_FLOAT` (32-bit floats, raw). Everything outside the subset is refused with +//! `nnc_not_read`, naming what it met. + +use super::cabac; +use super::{refuse, DType, Refusal, Tensor, TensorSet}; +use crate::strict_json::{self, quote, Json}; + +/// NNR unit types. +pub const NNR_STR: u32 = 0; +pub const NNR_MPS: u32 = 1; +pub const NNR_LPS: u32 = 2; +pub const NNR_TPL: u32 = 3; +pub const NNR_QNT: u32 = 4; +pub const NNR_NDU: u32 = 5; +pub const NNR_AGG: u32 = 6; +/// Compressed data unit payload types. +pub const NNR_PT_INT: u32 = 0; +pub const NNR_PT_FLOAT: u32 = 1; +pub const NNR_PT_RAW_FLOAT: u32 = 2; +pub const NNR_PT_BLOCK: u32 = 3; +/// The topology storage format "unrecognised", whose topology is a string; the writer puts +/// the metadata there (§12.3). +pub const NNR_TPL_UNREC: u32 = 0; +/// The topology storage formats this reader reads, each carrying its topology as a string. +pub const TPL_STRING_FORMATS: [u32; 3] = [0, 3, 4]; +/// The unary length the writer codes with: `cabac_unary_length_minus1`. +pub const UNARY_LENGTH_MINUS1: u32 = 10; +/// The most elements one tensor may have at this reader. +pub const MAX_ELEMENTS: u64 = 1 << 28; + +struct Bits<'a> { + b: &'a [u8], + bit: usize, +} + +impl Bits<'_> { + fn u(&mut self, n: u32) -> Result { + let mut v = 0u64; + for _ in 0..n { + let byte = *self.b.get(self.bit / 8).ok_or_else(|| Refusal::new("nnc_truncated", "a unit ends inside its header"))?; + v = (v << 1) | u64::from((byte >> (7 - self.bit % 8)) & 1); + self.bit += 1; + } + Ok(v) + } + fn ue(&mut self, mut k: u32) -> Result { + let mut x: u64 = 0; + loop { + if k >= 40 { + return refuse("nnc_malformed", "an exponential-Golomb code longer than 40 bits"); + } + if self.u(1)? == 1 { + break; + } + x += 1u64 << k; + k += 1; + } + Ok(x + self.u(k)?) + } + fn align(&mut self) -> Result<(), Refusal> { + if self.u(1)? != 1 { + return refuse("nnc_malformed", "a byte alignment that does not begin with a 1 bit"); + } + while self.bit % 8 != 0 { + if self.u(1)? != 0 { + return refuse("nnc_malformed", "a byte alignment padded with a 1 bit"); + } + } + Ok(()) + } + fn st(&mut self) -> Result { + if self.bit % 8 != 0 { + return refuse("nnc_malformed", "a string that does not begin on a byte"); + } + let at = self.bit / 8; + let Some(n) = self.b[at.min(self.b.len())..].iter().position(|&c| c == 0) else { + return refuse("nnc_truncated", "a string with no terminating zero byte"); + }; + let s = String::from_utf8(self.b[at..at + n].to_vec()).map_err(|_| Refusal::new("nnc_malformed", "a string that is not UTF-8"))?; + self.bit += (n + 1) * 8; + Ok(s) + } +} + +#[derive(Default)] +struct W { + b: Vec, + bit: usize, +} + +impl W { + fn u(&mut self, n: u32, v: u64) { + for i in (0..n).rev() { + if self.bit % 8 == 0 { + self.b.push(0); + } + if (v >> i) & 1 == 1 { + let last = self.b.len() - 1; + self.b[last] |= 1 << (7 - self.bit % 8); + } + self.bit += 1; + } + } + fn ue(&mut self, mut k: u32, mut x: u64) { + while x >= (1u64 << k) { + self.u(1, 0); + x -= 1u64 << k; + k += 1; + } + self.u(1, 1); + self.u(k, x); + } + fn align(&mut self) { + self.u(1, 1); + while self.bit % 8 != 0 { + self.u(1, 0); + } + } + fn st(&mut self, s: &str) { + debug_assert!(self.bit % 8 == 0); + self.b.extend_from_slice(s.as_bytes()); + self.b.push(0); + self.bit = self.b.len() * 8; + } +} + +/// Prefix `body` (a unit's header and payload, after its size) with `nnr_unit_size`: the +/// 15-bit form when the whole unit is under 32768 bytes, else the 31-bit form (§12.2). +fn sized(body: Vec) -> Result, Refusal> { + let mut w = W::default(); + if body.len() + 2 < 32768 { + w.u(1, 0); + w.u(15, (body.len() + 2) as u64); + } else if body.len() + 4 < (1usize << 31) { + w.u(1, 1); + w.u(31, (body.len() + 4) as u64); + } else { + return refuse("value_out_of_range", "an NNR unit past 2^31 − 1 bytes"); + } + w.b.extend_from_slice(&body); + Ok(w.b) +} + +/// The common start of a unit header: its type and two flags, both zero but +/// `independently_decodable_flag`. +fn unit_head(w: &mut W, ty: u32) { + w.u(6, u64::from(ty)); + w.u(1, 1); + w.u(1, 0); +} + +/// The canonical metadata text a topology unit carries (§12.3): a JSON object of the +/// metadata's strings, keys in ascending byte order. +pub fn metadata_text(set: &TensorSet) -> String { + let m: Vec = set.metadata.iter().map(|(k, v)| format!("{}:{}", quote(k), quote(v))).collect(); + format!("{{{}}}", m.join(",")) +} + +/// The canonical bitstream of `set` (§11.3, §12.3). +pub fn write(set: &TensorSet) -> Result, Refusal> { + let set = set.clone().sorted(); + let mut out = Vec::new(); + // NNR_STR: general_profile_idc 0. + let mut w = W::default(); + unit_head(&mut w, NNR_STR); + w.u(8, 0); + out.extend(sized(w.b)?); + // NNR_MPS: topology carried when there is metadata; nothing else. + let tpl = !set.metadata.is_empty(); + let mut w = W::default(); + unit_head(&mut w, NNR_MPS); + w.u(1, u64::from(tpl)); + w.u(4, 0); // sparsification, pruning, unification, decomposition performance map + w.u(3, 0); // quantization method flags + w.u(1, 0); // topology indexed reference + w.u(7, 0); // reserved + w.align(); + out.extend(sized(w.b)?); + if tpl { + let text = metadata_text(&set); + if text.contains('\0') { + return refuse("header_malformed", "metadata that holds a zero byte"); + } + let mut w = W::default(); + unit_head(&mut w, NNR_TPL); + w.u(8, u64::from(NNR_TPL_UNREC)); + w.u(8, 0); // topology_compression_format: raw + w.st(&text); + out.extend(sized(w.b)?); + } + for (i, t) in set.tensors.iter().enumerate() { + if i > 0 && set.tensors[i - 1].name == t.name { + return refuse("tensor_malformed", format!("{:?} named twice", t.name)); + } + if t.name.is_empty() || t.name.contains('\0') || t.shape.is_empty() || t.shape.contains(&0) { + return refuse("tensor_malformed", format!("{:?} cannot be an NNC tensor (a name without a zero byte, at least one dimension, none 0)", t.name)); + } + let n = t.count().filter(|&n| n <= MAX_ELEMENTS).ok_or_else(|| Refusal::new("value_out_of_range", format!("{:?}: more than 2^28 elements", t.name)))?; + let pt = match t.dtype { + d if d.is_profile_integer() => NNR_PT_INT, + DType::F32 => NNR_PT_RAW_FLOAT, + d => return refuse("dtype_unknown", format!("{:?}: {d:?} is carried by neither NNR_PT_INT nor NNR_PT_RAW_FLOAT", t.name)), + }; + if t.data.len() as u64 != t.dtype.bytes_for(n).unwrap_or(u64::MAX) { + return refuse("offset_mismatch", format!("{:?}: {} bytes for its shape and dtype", t.name, t.data.len())); + } + let mut w = W::default(); + unit_head(&mut w, NNR_NDU); + w.u(5, u64::from(pt)); + w.u(1, 0); // multiple topology elements + w.u(1, 0); // decompressed data format present + w.u(1, 1); // input parameters present + w.st(&t.name); + if pt == NNR_PT_INT { + w.u(1, 0); // dq_flag + } + w.u(1, 1); // tensor_dimensions_flag + w.u(1, 1); // cabac_unary_length_flag + w.u(4, 0); // compressed_parameter_types + w.ue(1, t.shape.len() as u64); + for &d in &t.shape { + w.ue(7, d); + } + w.u(8, u64::from(UNARY_LENGTH_MINUS1)); + if t.shape.len() > 1 { + w.u(4, 0); // scan_order + } + w.align(); + let mut body = w.b; + if pt == NNR_PT_INT { + let v = t.ints().expect("an integer tensor"); + body.extend(cabac::encode_ints(&v, UNARY_LENGTH_MINUS1 + 1)?); + } else { + body.extend_from_slice(&t.data); + } + out.extend(sized(body)?); + } + Ok(out) +} + +/// Read an NNC bitstream (§12.2): the subset of §12.1, every unit's size held to what it +/// carries. Tensors of `NNR_PT_INT` come back as `I32`, of `NNR_PT_RAW_FLOAT` as `F32`; a +/// topology unit of the unrecognised format whose text is a JSON object of strings gives +/// the metadata. +pub fn read(b: &[u8]) -> Result { + let mut set = TensorSet::default(); + let mut at = 0usize; + let (mut seen_mps, mut seen_tpl, mut seen_ndu, mut first) = (false, false, false, true); + while at < b.len() { + let mut h = Bits { b: &b[at..], bit: 0 }; + let long = h.u(1)?; + let size = h.u(if long == 1 { 31 } else { 15 })? as usize; + if size < 3 || at + size > b.len() { + return refuse("nnc_truncated", format!("a unit of {size} bytes at byte {at} of {}", b.len())); + } + let unit = &b[at..at + size]; + let mut h = Bits { b: unit, bit: h.bit }; + let ty = h.u(6)? as u32; + let _independently_decodable = h.u(1)?; + let partial = h.u(1)?; + if first && ty != NNR_STR { + return refuse("nnc_malformed", "the first unit is not NNR_STR"); + } + if partial == 1 { + return refuse("nnc_not_read", format!("unit type {ty} with a partial data counter")); + } + let header_done = |h: &Bits<'_>, what: &str| -> Result<(), Refusal> { + if h.bit != unit.len() * 8 { + return refuse("nnc_malformed", format!("{what} carries {} bits and its size says {}", h.bit, unit.len() * 8)); + } + Ok(()) + }; + match ty { + NNR_STR => { + if !first { + return refuse("nnc_malformed", "a second NNR_STR"); + } + h.u(8)?; // general_profile_idc + header_done(&h, "NNR_STR")?; + } + NNR_MPS => { + if seen_mps || seen_ndu { + return refuse("nnc_malformed", "NNR_MPS twice, or after a data unit"); + } + seen_mps = true; + let _topology = h.u(1)?; + let maps = h.u(4)?; + let quant = h.u(3)?; + let indexed = h.u(1)?; + if h.u(7)? != 0 { + return refuse("nnc_malformed", "reserved bits of NNR_MPS are not zero"); + } + if maps != 0 { + return refuse("nnc_not_read", "a performance map in NNR_MPS"); + } + if indexed != 0 { + return refuse("nnc_not_read", "topology elements referenced by index"); + } + if quant != 0 { + h.u(3)?; // mps_qp_density + h.u(13)?; // mps_quantization_parameter + } + h.align()?; + header_done(&h, "NNR_MPS")?; + } + NNR_TPL => { + if seen_tpl || !seen_mps { + return refuse("nnc_malformed", "NNR_TPL twice, or before NNR_MPS"); + } + seen_tpl = true; + let fmt = h.u(8)? as u32; + let comp = h.u(8)?; + if !TPL_STRING_FORMATS.contains(&fmt) || comp != 0 { + return refuse("nnc_not_read", format!("topology storage format {fmt}, compression {comp}")); + } + let text = h.st()?; + header_done(&h, "NNR_TPL")?; + if fmt == NNR_TPL_UNREC + && let Ok(Json::Obj(m)) = strict_json::read(text.as_bytes()) + && m.iter().all(|(_, v)| v.as_str().is_some()) + { + for (k, v) in m { + set.metadata.insert(k, v.as_str().expect("checked").to_string()); + } + } + } + NNR_NDU => { + if !seen_mps { + return refuse("nnc_malformed", "a data unit before NNR_MPS"); + } + seen_ndu = true; + set.tensors.push(ndu(&mut h, unit)?); + } + NNR_LPS | NNR_QNT | NNR_AGG => return refuse("nnc_not_read", format!("unit type {ty}")), + _ => return refuse("nnc_malformed", format!("unit type {ty}, which the syntax does not define")), + } + first = false; + at += size; + } + if first { + return refuse("nnc_truncated", "an empty bitstream"); + } + let set = set.sorted(); + for w in set.tensors.windows(2) { + if w[0].name == w[1].name { + return refuse("nnc_malformed", format!("{:?} carried twice", w[0].name)); + } + } + Ok(set) +} + +fn ndu(h: &mut Bits<'_>, unit: &[u8]) -> Result { + let pt = h.u(5)? as u32; + let multiple = h.u(1)?; + let fmt_present = h.u(1)?; + let inband = h.u(1)?; + if !matches!(pt, NNR_PT_INT | NNR_PT_RAW_FLOAT) { + let what = match pt { + NNR_PT_FLOAT => "NNR_PT_FLOAT (quantised parameters)".to_string(), + NNR_PT_BLOCK => "NNR_PT_BLOCK".to_string(), + p => format!("payload type {p}"), + }; + return refuse("nnc_not_read", what); + } + if multiple != 0 { + return refuse("nnc_not_read", "several topology elements in one data unit"); + } + let name = h.st()?; + if name.is_empty() { + return refuse("nnc_malformed", "a data unit with an empty topology element name"); + } + if pt == NNR_PT_INT && h.u(1)? != 0 { + return refuse("nnc_not_read", format!("{name:?}: dependent quantisation (dq_flag 1)")); + } + if fmt_present == 1 { + let f = h.u(7)?; + let want = if pt == NNR_PT_INT { 0 } else { 1 }; + if f != want { + return refuse("nnc_not_read", format!("{name:?}: decompressed data format {f}")); + } + } + if inband == 0 { + return refuse("nnc_not_read", format!("{name:?}: input parameters out of band")); + } + let dims_flag = h.u(1)?; + let unary_flag = h.u(1)?; + let cpt = h.u(4)?; + if cpt != 0 { + return refuse("nnc_not_read", format!("{name:?}: compressed parameter types {cpt}")); + } + if dims_flag == 0 { + return refuse("nnc_not_read", format!("{name:?}: tensor dimensions out of band")); + } + let nd = h.ue(1)?; + if nd == 0 || nd > 16 { + return refuse("nnc_malformed", format!("{name:?}: {nd} dimensions")); + } + let mut shape = Vec::with_capacity(nd as usize); + for _ in 0..nd { + shape.push(h.ue(7)?); + } + let count = shape.iter().try_fold(1u64, |a, &d| a.checked_mul(d)).filter(|&n| n > 0); + let Some(count) = count else { return refuse("nnc_malformed", format!("{name:?}: a dimension of 0, or a count that overflows")) }; + if count > MAX_ELEMENTS { + return refuse("nnc_not_read", format!("{name:?}: {count} elements, past this reader's 2^28")); + } + let unary = if unary_flag == 1 { Some(h.u(8)? as u32) } else { None }; + if nd > 1 { + let scan = h.u(4)?; + if scan != 0 { + return refuse("nnc_not_read", format!("{name:?}: scan_order {scan} (block scan with entry points)")); + } + } + h.align()?; + let start = h.bit / 8; + let rest = &unit[start..]; + if pt == NNR_PT_RAW_FLOAT { + if rest.len() as u64 != count * 4 { + return refuse("nnc_malformed", format!("{name:?}: {} bytes of raw floats for {count} elements", rest.len())); + } + return Ok(Tensor { name, dtype: DType::F32, shape, data: rest.to_vec() }); + } + let Some(u) = unary else { return refuse("nnc_not_read", format!("{name:?}: the unary length out of band")) }; + let (vals, used) = cabac::decode_ints(rest, count, u + 1)?; + if used != rest.len() { + return refuse("nnc_malformed", format!("{name:?}: the entropy-coded payload takes {used} of the unit's {} bytes", rest.len())); + } + let mut data = Vec::with_capacity(vals.len() * 4); + for v in vals { + data.extend_from_slice(&(v as i32).to_le_bytes()); + } + Ok(Tensor { name, dtype: DType::I32, shape, data }) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn sample() -> TensorSet { + let mut s = TensorSet::default(); + s.metadata.insert("wai.profile".into(), "wai-int/1".into()); + s.tensors.push(Tensor::from_ints("w", DType::I32, vec![2, 3], &[1, -2, 300, -40000, 0, 7])); + s.tensors.push(Tensor { name: "f".into(), dtype: DType::F32, shape: vec![2], data: [1.5f32.to_le_bytes(), (-0.25f32).to_le_bytes()].concat() }); + s + } + + #[test] + fn writes_and_reads_back() { + let b = write(&sample()).unwrap(); + // NNR_STR first: a 15-bit size of 4, type 0, independently decodable. + assert_eq!(&b[..4], &[0x00, 0x04, 0x02, 0x00]); + let back = read(&b).unwrap(); + assert_eq!(back, sample().sorted()); + assert_eq!(write(&back).unwrap(), b); + } + + #[test] + fn narrower_integers_read_back_as_int32() { + let mut s = TensorSet::default(); + s.tensors.push(Tensor::from_ints("x", DType::I8, vec![3], &[-128, 0, 127])); + let back = read(&write(&s).unwrap()).unwrap(); + assert_eq!(back.tensors[0].dtype, DType::I32); + assert_eq!(back.tensors[0].ints().unwrap(), vec![-128, 0, 127]); + } + + #[test] + fn refuses_outside_its_subset() { + let b = write(&sample()).unwrap(); + let code = |x: &[u8]| read(x).err().map(|r| r.code); + assert_eq!(code(&b[..b.len() - 1]), Some("nnc_truncated")); + assert_eq!(code(&b[4..]), Some("nnc_malformed")); // starts at NNR_MPS + // Turn the first data unit's payload type into NNR_PT_FLOAT. + let mut f = b.clone(); + let mut at = 0; + loop { + let size = u16::from_be_bytes([f[at], f[at + 1]]) as usize; + if f[at + 2] >> 2 == NNR_NDU as u8 { + f[at + 3] = (f[at + 3] & 0x07) | ((NNR_PT_FLOAT as u8) << 3); + break; + } + at += size; + } + assert_eq!(code(&f), Some("nnc_not_read")); + } +} diff --git a/wai/wai-rs/src/prior_wire/safetensors.rs b/wai/wai-rs/src/prior_wire/safetensors.rs new file mode 100644 index 000000000..39ddc8dd8 --- /dev/null +++ b/wai/wai-rs/src/prior_wire/safetensors.rs @@ -0,0 +1,187 @@ +//! The safetensors form (`extensions/prior-carriage.md` §9.1): an 8-byte little-endian +//! header length `N`, `N` bytes of JSON header, then the tensors' bytes. + +use super::{refuse, DType, Refusal, Tensor, TensorSet}; +use crate::strict_json::{self, quote, Json}; + +/// The largest header the reader accepts, in bytes (§9.1 step 2). +pub const MAX_HEADER: u64 = 100_000_000; +/// The member of the header that holds the metadata. +pub const METADATA: &str = "__metadata__"; + +/// Read a safetensors file (§9.1): every rule of the format, no canonical check. +pub fn read(b: &[u8]) -> Result { + if b.len() < 8 { + return refuse("header_truncated", "fewer than the 8 bytes of the header length"); + } + let n = u64::from_le_bytes(b[..8].try_into().expect("8 bytes")); + if n > MAX_HEADER { + return refuse("header_too_large", format!("a header of {n} bytes, past {MAX_HEADER}")); + } + let end = 8 + n as usize; + if end > b.len() { + return refuse("header_truncated", format!("a header of {n} bytes in a file of {} bytes", b.len())); + } + let doc = strict_json::read(&b[8..end]).map_err(|e| Refusal::new("header_malformed", e))?; + let Json::Obj(members) = doc else { return refuse("header_malformed", "the header is not a JSON object") }; + let data = &b[end..]; + let mut set = TensorSet::default(); + let mut spans: Vec<(u64, u64, usize)> = Vec::new(); + for (name, v) in members { + if name == METADATA { + let Json::Obj(m) = v else { return refuse("header_malformed", "__metadata__ is not an object") }; + for (k, mv) in m { + let Json::Str(s) = mv else { return refuse("header_malformed", format!("metadata {k:?} is not a string")) }; + set.metadata.insert(k, s); + } + continue; + } + if name.is_empty() { + return refuse("tensor_malformed", "a tensor with an empty name"); + } + let Json::Obj(t) = &v else { return refuse("tensor_malformed", format!("{name:?} is not an object")) }; + if t.len() != 3 || ["dtype", "shape", "data_offsets"].iter().any(|k| v.get(k).is_none()) { + return refuse("tensor_malformed", format!("{name:?} does not have exactly dtype, shape and data_offsets")); + } + let Some(ds) = v.get("dtype").and_then(Json::as_str) else { + return refuse("tensor_malformed", format!("{name:?}: dtype is not a string")); + }; + let Some(dtype) = DType::from_safetensors_name(ds) else { + return refuse("dtype_unknown", format!("{name:?}: dtype {ds:?}")); + }; + let shape: Option> = v.get("shape").and_then(Json::as_arr).map(|a| a.iter().map(Json::as_u64).collect()).flatten(); + let Some(shape) = shape else { return refuse("tensor_malformed", format!("{name:?}: shape is not a list of integers")) }; + let offs: Option> = v.get("data_offsets").and_then(Json::as_arr).map(|a| a.iter().map(Json::as_u64).collect()).flatten(); + let Some(offs) = offs.filter(|o| o.len() == 2 && o[0] <= o[1]) else { + return refuse("tensor_malformed", format!("{name:?}: data_offsets is not [begin, end] with begin ≤ end")); + }; + let count = shape.iter().try_fold(1u64, |a, &d| a.checked_mul(d)); + let Some(need) = count.and_then(|c| dtype.bytes_for(c)) else { + return refuse("tensor_malformed", format!("{name:?}: the element count overflows")); + }; + if offs[1] - offs[0] != need { + return refuse("offset_mismatch", format!("{name:?}: data_offsets span {} bytes, its shape and dtype {need}", offs[1] - offs[0])); + } + spans.push((offs[0], offs[1], set.tensors.len())); + set.tensors.push(Tensor { name, dtype, shape, data: Vec::new() }); + } + // The spans cover the data exactly: no gap, no overlap, nothing after the last. + spans.sort(); + let mut at = 0u64; + for &(b0, e0, i) in &spans { + if b0 != at { + return refuse("offset_mismatch", format!("{:?} begins at {b0}, the data so far ends at {at}", set.tensors[i].name)); + } + at = e0; + } + if at != data.len() as u64 { + return refuse("offset_mismatch", format!("the tensors cover {at} bytes of {} bytes of data", data.len())); + } + for &(b0, e0, i) in &spans { + set.tensors[i].data = data[b0 as usize..e0 as usize].to_vec(); + } + Ok(set.sorted()) +} + +/// The canonical header text of `set` (§11.1), before its padding. +pub fn header(set: &TensorSet) -> Result { + let mut parts: Vec = Vec::new(); + if !set.metadata.is_empty() { + let m: Vec = set.metadata.iter().map(|(k, v)| format!("{}:{}", quote(k), quote(v))).collect(); + parts.push(format!("{}:{{{}}}", quote(METADATA), m.join(","))); + } + let mut at = 0u64; + for t in &set.tensors { + let Some(ds) = t.dtype.safetensors_name() else { + return refuse("dtype_unknown", format!("{:?}: {:?} has no safetensors name", t.name, t.dtype)); + }; + let end = at + t.data.len() as u64; + let shape: Vec = t.shape.iter().map(u64::to_string).collect(); + parts.push(format!("{}:{{\"dtype\":\"{ds}\",\"shape\":[{}],\"data_offsets\":[{at},{end}]}}", quote(&t.name), shape.join(","))); + at = end; + } + Ok(format!("{{{}}}", parts.join(","))) +} + +/// The canonical bytes of `set` (§11.1): the tensors in ascending byte order of their +/// names, the header padded with spaces so that the data begins at a multiple of 8. +pub fn write(set: &TensorSet) -> Result, Refusal> { + let set = set.clone().sorted(); + for w in set.tensors.windows(2) { + if w[0].name == w[1].name { + return refuse("tensor_malformed", format!("{:?} named twice", w[0].name)); + } + } + for t in &set.tensors { + if t.name.is_empty() || t.name == METADATA { + return refuse("tensor_malformed", format!("{:?} is not a tensor name", t.name)); + } + let need = t.count().and_then(|c| t.dtype.bytes_for(c)); + if need != Some(t.data.len() as u64) { + return refuse("offset_mismatch", format!("{:?}: {} bytes for its shape and dtype", t.name, t.data.len())); + } + } + let mut h = header(&set)?.into_bytes(); + while (8 + h.len()) % 8 != 0 { + h.push(b' '); + } + let mut out = (h.len() as u64).to_le_bytes().to_vec(); + out.extend_from_slice(&h); + for t in &set.tensors { + out.extend_from_slice(&t.data); + } + Ok(out) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn sample() -> TensorSet { + let mut s = TensorSet::default(); + s.metadata.insert("wai.profile".into(), "wai-int/1".into()); + s.tensors.push(Tensor::from_ints("b", DType::I32, vec![2], &[-1, 7])); + s.tensors.push(Tensor::from_ints("a", DType::I8, vec![1, 3], &[1, -2, 3])); + s + } + + #[test] + fn writes_canonically_and_reads_back() { + let bytes = write(&sample()).unwrap(); + let n = u64::from_le_bytes(bytes[..8].try_into().unwrap()) as usize; + assert_eq!((8 + n) % 8, 0); + assert_eq!( + std::str::from_utf8(&bytes[8..8 + n]).unwrap().trim_end(), + r#"{"__metadata__":{"wai.profile":"wai-int/1"},"a":{"dtype":"I8","shape":[1,3],"data_offsets":[0,3]},"b":{"dtype":"I32","shape":[2],"data_offsets":[3,11]}}"# + ); + let back = read(&bytes).unwrap(); + assert_eq!(back, sample().sorted()); + assert_eq!(write(&back).unwrap(), bytes); + } + + #[test] + fn refuses_what_the_format_forbids() { + let good = write(&sample()).unwrap(); + let code = |b: &[u8]| read(b).err().map(|r| r.code); + assert_eq!(code(&good[..5]), Some("header_truncated")); + let mut big = good.clone(); + big[..8].copy_from_slice(&(MAX_HEADER + 1).to_le_bytes()); + assert_eq!(code(&big), Some("header_too_large")); + let with_header = |h: &str, data: &[u8]| { + let mut o = (h.len() as u64).to_le_bytes().to_vec(); + o.extend_from_slice(h.as_bytes()); + o.extend_from_slice(data); + o + }; + assert_eq!(code(&with_header(r#"{"a":{"dtype":"I8","shape":[1],"data_offsets":[0,1]},"a":{}}"#, &[0])), Some("header_malformed")); + assert_eq!(code(&with_header(r#"{"a":{"dtype":"Q4","shape":[1],"data_offsets":[0,1]}}"#, &[0])), Some("dtype_unknown")); + assert_eq!(code(&with_header(r#"{"a":{"dtype":"I8","shape":[2],"data_offsets":[0,1]}}"#, &[0])), Some("offset_mismatch")); + assert_eq!(code(&with_header(r#"{"a":{"dtype":"I8","shape":[1],"data_offsets":[0,1]}}"#, &[0, 0])), Some("offset_mismatch")); + assert_eq!(code(&with_header(r#"{"a":{"dtype":"I8","shape":[1],"data_offsets":[0,1],"x":1}}"#, &[0])), Some("tensor_malformed")); + assert_eq!(code(&with_header(r#"{"__metadata__":{"k":1}}"#, &[])), Some("header_malformed")); + // Reads, and is not canonical: a space before the object. + let loose = with_header(r#" {"a":{"dtype":"I8","shape":[1],"data_offsets":[0,1]}}"#, &[5]); + let set = read(&loose).unwrap(); + assert_ne!(write(&set).unwrap(), loose); + } +} diff --git a/wai/wai-rs/src/strict_json.rs b/wai/wai-rs/src/strict_json.rs new file mode 100644 index 000000000..0e2e2e3a0 --- /dev/null +++ b/wai/wai-rs/src/strict_json.rs @@ -0,0 +1,338 @@ +//! A strict reader of JSON text (RFC 8259) for the documents the tensor-file and scene +//! mappings read: `extensions/prior-carriage.md` §9 (a safetensors header) and +//! `extensions/scene-carriage.md` §3 (a glTF document). +//! +//! It keeps what a lenient parser loses: members in the order they were written, a +//! member named twice (refused), and each number's spelling (a number is never rounded on +//! the way in; the caller reads the spelling as it needs). Nesting is limited to +//! [`MAX_DEPTH`] levels. Invalid UTF-8, an unpaired surrogate escape, a control character +//! in a string and anything after the value are refused. + +/// The most levels of nesting a document may have. +pub const MAX_DEPTH: usize = 64; + +/// A JSON value with its members in written order and its numbers as spelled. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Json { + Null, + Bool(bool), + /// The number's spelling, as RFC 8259 §6 admits it. + Num(String), + Str(String), + Arr(Vec), + Obj(Vec<(String, Json)>), +} + +impl Json { + /// The member `k` of an object. + pub fn get(&self, k: &str) -> Option<&Json> { + match self { + Json::Obj(m) => m.iter().find(|(n, _)| n == k).map(|(_, v)| v), + _ => None, + } + } + pub fn as_str(&self) -> Option<&str> { + match self { + Json::Str(s) => Some(s), + _ => None, + } + } + pub fn as_arr(&self) -> Option<&[Json]> { + match self { + Json::Arr(a) => Some(a), + _ => None, + } + } + pub fn as_obj(&self) -> Option<&[(String, Json)]> { + match self { + Json::Obj(m) => Some(m), + _ => None, + } + } + /// A number spelled as a plain non-negative decimal integer (no sign, fraction or + /// exponent, no leading zero) that fits `u64`. + pub fn as_u64(&self) -> Option { + match self { + Json::Num(s) if is_plain_uint(s) => s.parse().ok(), + _ => None, + } + } + /// A number spelled as a plain decimal integer, optionally negative, that fits `i64`. + pub fn as_i64(&self) -> Option { + match self { + Json::Num(s) => { + let d = s.strip_prefix('-').unwrap_or(s); + if is_plain_uint(d) && s != "-0" { s.parse().ok() } else { None } + } + _ => None, + } + } +} + +fn is_plain_uint(s: &str) -> bool { + !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit()) && (s == "0" || !s.starts_with('0')) +} + +/// Read `bytes` as one JSON text. The error says where and why. +pub fn read(bytes: &[u8]) -> Result { + let text = std::str::from_utf8(bytes).map_err(|e| format!("not UTF-8 at byte {}", e.valid_up_to()))?; + let mut p = P { b: text.as_bytes(), i: 0 }; + p.ws(); + let v = p.value(0)?; + p.ws(); + if p.i != p.b.len() { + return Err(format!("bytes follow the value at byte {}", p.i)); + } + Ok(v) +} + +struct P<'a> { + b: &'a [u8], + i: usize, +} + +impl P<'_> { + fn ws(&mut self) { + while let Some(&c) = self.b.get(self.i) { + if matches!(c, b' ' | b'\t' | b'\n' | b'\r') { + self.i += 1; + } else { + break; + } + } + } + fn err(&self, why: &str) -> Result { + Err(format!("{why} at byte {}", self.i)) + } + fn lit(&mut self, s: &str, v: Json) -> Result { + if self.b[self.i..].starts_with(s.as_bytes()) { + self.i += s.len(); + Ok(v) + } else { + self.err("not a JSON value") + } + } + fn value(&mut self, depth: usize) -> Result { + if depth > MAX_DEPTH { + return self.err("nested too deeply"); + } + match self.b.get(self.i) { + None => self.err("the text ends where a value is due"), + Some(b'{') => { + self.i += 1; + let mut m: Vec<(String, Json)> = Vec::new(); + self.ws(); + if self.b.get(self.i) == Some(&b'}') { + self.i += 1; + return Ok(Json::Obj(m)); + } + loop { + self.ws(); + if self.b.get(self.i) != Some(&b'"') { + return self.err("a member name is due"); + } + let k = self.string()?; + if m.iter().any(|(n, _)| *n == k) { + return self.err(&format!("member {k:?} named twice")); + } + self.ws(); + if self.b.get(self.i) != Some(&b':') { + return self.err("':' is due"); + } + self.i += 1; + self.ws(); + let v = self.value(depth + 1)?; + m.push((k, v)); + self.ws(); + match self.b.get(self.i) { + Some(b',') => self.i += 1, + Some(b'}') => { + self.i += 1; + return Ok(Json::Obj(m)); + } + _ => return self.err("',' or '}' is due"), + } + } + } + Some(b'[') => { + self.i += 1; + let mut a = Vec::new(); + self.ws(); + if self.b.get(self.i) == Some(&b']') { + self.i += 1; + return Ok(Json::Arr(a)); + } + loop { + self.ws(); + a.push(self.value(depth + 1)?); + self.ws(); + match self.b.get(self.i) { + Some(b',') => self.i += 1, + Some(b']') => { + self.i += 1; + return Ok(Json::Arr(a)); + } + _ => return self.err("',' or ']' is due"), + } + } + } + Some(b'"') => Ok(Json::Str(self.string()?)), + Some(b't') => self.lit("true", Json::Bool(true)), + Some(b'f') => self.lit("false", Json::Bool(false)), + Some(b'n') => self.lit("null", Json::Null), + Some(_) => self.number(), + } + } + fn number(&mut self) -> Result { + let s = self.i; + if self.b.get(self.i) == Some(&b'-') { + self.i += 1; + } + match self.b.get(self.i) { + Some(b'0') => self.i += 1, + Some(b'1'..=b'9') => { + while self.b.get(self.i).is_some_and(u8::is_ascii_digit) { + self.i += 1; + } + } + _ => return self.err("not a JSON value"), + } + if self.b.get(self.i) == Some(&b'.') { + self.i += 1; + if !self.b.get(self.i).is_some_and(u8::is_ascii_digit) { + return self.err("a digit is due after '.'"); + } + while self.b.get(self.i).is_some_and(u8::is_ascii_digit) { + self.i += 1; + } + } + if matches!(self.b.get(self.i), Some(b'e' | b'E')) { + self.i += 1; + if matches!(self.b.get(self.i), Some(b'+' | b'-')) { + self.i += 1; + } + if !self.b.get(self.i).is_some_and(u8::is_ascii_digit) { + return self.err("a digit is due in the exponent"); + } + while self.b.get(self.i).is_some_and(u8::is_ascii_digit) { + self.i += 1; + } + } + Ok(Json::Num(String::from_utf8(self.b[s..self.i].to_vec()).expect("ASCII"))) + } + fn hex4(&mut self) -> Result { + let h = self.b.get(self.i..self.i + 4).ok_or_else(|| format!("a \\u escape is cut short at byte {}", self.i))?; + let s = std::str::from_utf8(h).map_err(|_| "a \\u escape is not hexadecimal".to_string())?; + if !s.bytes().all(|c| c.is_ascii_hexdigit()) { + return self.err("a \\u escape is not hexadecimal"); + } + self.i += 4; + Ok(u32::from_str_radix(s, 16).expect("hex")) + } + fn string(&mut self) -> Result { + self.i += 1; // the opening quote + let mut out = String::new(); + loop { + let Some(&c) = self.b.get(self.i) else { return self.err("a string is not closed") }; + match c { + b'"' => { + self.i += 1; + return Ok(out); + } + b'\\' => { + self.i += 1; + let Some(&e) = self.b.get(self.i) else { return self.err("an escape is cut short") }; + self.i += 1; + match e { + b'"' => out.push('"'), + b'\\' => out.push('\\'), + b'/' => out.push('/'), + b'b' => out.push('\u{8}'), + b'f' => out.push('\u{c}'), + b'n' => out.push('\n'), + b'r' => out.push('\r'), + b't' => out.push('\t'), + b'u' => { + let hi = self.hex4()?; + let cp = if (0xD800..0xDC00).contains(&hi) { + if self.b.get(self.i..self.i + 2) != Some(b"\\u") { + return self.err("an escape leaves a surrogate unpaired"); + } + self.i += 2; + let lo = self.hex4()?; + if !(0xDC00..0xE000).contains(&lo) { + return self.err("an escape leaves a surrogate unpaired"); + } + 0x10000 + ((hi - 0xD800) << 10) + (lo - 0xDC00) + } else if (0xDC00..0xE000).contains(&hi) { + return self.err("an escape leaves a surrogate unpaired"); + } else { + hi + }; + out.push(char::from_u32(cp).expect("a scalar value")); + } + _ => return self.err("not a JSON escape"), + } + } + 0x00..=0x1f => return self.err("a control character in a string"), + _ => { + // Copy one UTF-8 character (the text is valid UTF-8). + let len = match c { + 0x00..=0x7f => 1, + 0xc0..=0xdf => 2, + 0xe0..=0xef => 3, + _ => 4, + }; + out.push_str(std::str::from_utf8(&self.b[self.i..self.i + len]).expect("UTF-8")); + self.i += len; + } + } + } + } +} + +/// `s` as a JSON string, escaped as RFC 8785 §3.2.2.2 escapes it: `"` and `\` with a +/// backslash, U+0008, U+0009, U+000A, U+000C and U+000D by their short escapes, every other +/// control character as `\u00xx` with lowercase hexadecimal digits, and nothing else. +pub fn quote(s: &str) -> String { + let mut o = String::with_capacity(s.len() + 2); + o.push('"'); + for c in s.chars() { + match c { + '"' => o.push_str("\\\""), + '\\' => o.push_str("\\\\"), + '\u{8}' => o.push_str("\\b"), + '\t' => o.push_str("\\t"), + '\n' => o.push_str("\\n"), + '\u{c}' => o.push_str("\\f"), + '\r' => o.push_str("\\r"), + c if (c as u32) < 0x20 => o.push_str(&format!("\\u{:04x}", c as u32)), + c => o.push(c), + } + } + o.push('"'); + o +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn keeps_order_and_spelling_and_refuses_what_it_must() { + let v = read(br#" {"b":[1,-2.50e+3,true,null],"a":"x\u00e9\ud83d\ude00"} "#).unwrap(); + let m = v.as_obj().unwrap(); + assert_eq!(m[0].0, "b"); + assert_eq!(m[1].1.as_str(), Some("x\u{e9}\u{1f600}")); + assert_eq!(m[0].1.as_arr().unwrap()[1], Json::Num("-2.50e+3".into())); + for bad in [&br#"{"a":1,"a":2}"#[..], b"[1,]", b"01", b"\"\\ud800\"", b"{} x", b"\"\x01\"", b"1.", b"-", b"\xff"] { + assert!(read(bad).is_err(), "{:?}", String::from_utf8_lossy(bad)); + } + assert_eq!(Json::Num("12".into()).as_u64(), Some(12)); + assert_eq!(Json::Num("012".into()).as_u64(), None); + assert_eq!(Json::Num("-0".into()).as_i64(), None); + assert_eq!(quote("a\"\u{1}\n"), "\"a\\\"\\u0001\\n\""); + let deep = "[".repeat(MAX_DEPTH + 2) + &"]".repeat(MAX_DEPTH + 2); + assert!(read(deep.as_bytes()).is_err()); + } +} diff --git a/wai/wai-rs/tests/prior_wire.rs b/wai/wai-rs/tests/prior_wire.rs new file mode 100644 index 000000000..4cc5efbc7 --- /dev/null +++ b/wai/wai-rs/tests/prior_wire.rs @@ -0,0 +1,84 @@ +//! The tensor-file prior forms (`extensions/prior-carriage.md` §9 to §14): the committed +//! corpus regenerates byte for byte, the extension's §13 lists the codes the reference +//! reports, and a prior carried in every form loads into the integer kernels as the native +//! artifact does: the same `WSR1` prior and the same derivation output, the same `WHM1` +//! model. + +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; + +use wai::prior_wire::{self, corpus, Form, CODES}; + +fn root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../live-conformance/prior-forms") +} + +fn walk(root: &Path, rel: &str, out: &mut BTreeMap>) { + for e in std::fs::read_dir(root.join(rel)).unwrap().flatten() { + let name = e.file_name().to_string_lossy().into_owned(); + let key = if rel.is_empty() { name } else { format!("{rel}/{name}") }; + if e.path().is_dir() { + walk(root, &key, out); + } else if key != "README.md" { + out.insert(key, std::fs::read(e.path()).unwrap()); + } + } +} + +fn natives() -> BTreeMap> { + corpus::INPUT_NATIVES.iter().map(|(n, _, p, _)| (n.to_string(), std::fs::read(root().join(p)).unwrap())).collect() +} + +#[test] +fn the_committed_corpus_regenerates_byte_for_byte() { + let mut files = BTreeMap::new(); + walk(&root(), "", &mut files); + let bad = corpus::verify(&files, &natives()); + assert!(bad.is_empty(), "{bad:#?}"); +} + +#[test] +fn the_extension_lists_every_code_the_reference_reports() { + let text = std::fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("../extensions/prior-carriage.md")).unwrap(); + let sec = text.split("\n## 13.").nth(1).expect("§13").split("\n## ").next().unwrap(); + let listed: Vec<&str> = sec.lines().filter_map(|l| l.strip_prefix("| `")).filter_map(|l| l.split('`').next()).collect(); + assert_eq!(listed, CODES, "§13's table, in order"); +} + +#[test] +fn a_prior_in_every_form_loads_into_the_kernels_as_the_native_does() { + let nat = natives(); + // A picture, every sample an integer function of its position. + let (w, h) = (12usize, 9usize); + let pic: Vec = (0..w * h * 3).map(|i| ((i * 37 + i / 7 * 11) % 251) as u8).collect(); + for &(name, binding, _, _) in corpus::INPUT_NATIVES { + let native = &nat[name]; + for form in Form::ALL { + let file = prior_wire::encode(binding, native, form).unwrap(); + let (b, back) = prior_wire::load(form, &file).unwrap(); + assert_eq!(b, binding); + assert_eq!(&back, native, "{name} in {}: the native bytes", form.label()); + if binding == "wsr1/1" { + assert_eq!(wai::int_sr::parse(&back).unwrap(), wai::int_sr::parse(native).unwrap()); + let (g0, g1) = (wai::int_sr::load(native).unwrap(), wai::int_sr::load(&back).unwrap()); + let (o0, o1) = (wai::int_sr::sr2(&g0, &pic, w, h).unwrap(), wai::int_sr::sr2(&g1, &pic, w, h).unwrap()); + assert_eq!(o0, o1, "{name} in {}: the derivation's output", form.label()); + } else { + // The same bytes, so the kernel's model reads the same; it reads at all. + let (m0, m1) = (wai::int_hyper_synth::IntModel::from_bytes(native).unwrap(), wai::int_hyper_synth::IntModel::from_bytes(&back).unwrap()); + assert_eq!((m0.afrac, m0.gf, m0.shift, m0.layers.len()), (m1.afrac, m1.gf, m1.shift, m1.layers.len())); + } + } + } +} + +#[test] +fn one_prior_has_one_file_per_form_and_three_digests() { + let nat = natives(); + let native = &nat["generic"]; + let digests: Vec = Form::ALL.iter().map(|&f| wai::pin::sha256_hex(&prior_wire::encode("wsr1/1", native, f).unwrap())).collect(); + assert_eq!(digests.iter().collect::>().len(), 3); + for &f in &Form::ALL { + assert_eq!(prior_wire::encode("wsr1/1", native, f).unwrap(), prior_wire::encode("wsr1/1", native, f).unwrap()); + } +} -- 2.54.0 (Apple Git-157) From 08f4d23146df9fe4b45adc81486de52ab3e19684 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:22:48 -0400 Subject: [PATCH 008/242] =?UTF-8?q?integer-operators=20=C2=A79.5:=20the=20?= =?UTF-8?q?CR's=20sections=20cited=20as=20such,=20and=20the=20work=20count?= =?UTF-8?q?ed=20in=20the=20core's=20units?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- wai/extensions/integer-operators.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/wai/extensions/integer-operators.md b/wai/extensions/integer-operators.md index 8455c8b2b..eb6690d7f 100644 --- a/wai/extensions/integer-operators.md +++ b/wai/extensions/integer-operators.md @@ -1008,7 +1008,7 @@ These rewrites are exact: each gives the same integers as the operator it replac ### 9.5 `wai-ops/1-npu` on neural processors (informative) An operator implementation of `wai-ops/1-npu` for neural processors is `wai/wai-ops-npu`. It is a kernel for one compute core of a tile array, compiled for the core by the array's open toolchain. Its host side is built against the device runtime. The interpreter runs each graph and asks the core for the outputs of every operator that computes values (§6): -- `conv` and `conv_transpose`, as sums over taps. The host gathers each position's taps, with zero where padding, or a transposed convolution's stride, leaves no input. The core accumulates them from the `pre` bias and requantises the sum. The operand width is read from the certificate's `mac` record (§8.5). Operands that fit 8 or 16 bits go into the vector unit's 32-bit accumulator lanes, and 32-bit operands into its 64-bit lanes. A sum of more taps than one dispatch holds is split, and each part's accumulators carry into the next. +- `conv` and `conv_transpose`, as sums over taps. The host gathers each position's taps, with zero where padding, or a transposed convolution's stride, leaves no input. The core accumulates them from the `pre` bias and requantises the sum. The operand width is read from the certificate's `mac` record (§8.5). Operands that fit 8 or 16 bits go into the vector unit's 32-bit accumulator lanes, and 32-bit operands into its 64-bit lanes. A sum of more taps than the core takes at once is split, and each part's accumulators carry into the next. - `rescale`, `chscale` and `mul`, through the vector unit's multiply and its shift-round step. - `add`, `chunk_add`, `clamp`, `relu`, `abs` and `leaky`. - `lut16`. The table goes to the core one block of 4,096 entries at a time, with the inputs whose entries lie in that block. @@ -1037,7 +1037,7 @@ The vector unit's requantisation, with the scalar route for shifts of 60 to 63, | neural processor | toolchain and runtime | result | |---|---|---| -| PCI vendor 0x1022, device 0x17f0, revision 0x11: its tile array's second generation, firmware 1.1.2.65; one compute core used | the tile array's open toolchain 1.4.3 with its core compiler 22.0.0, device runtime 2.21.75, Linux 7.0 x86_64 | every graph vector in the subset: 733 vectors and 1,005 decodes, 32 of them refusals, with no difference. 302 of the vectors move data only, so the device ran nothing for them. Each operand width and each kind of work ran: 77 sums at 8 bits, 4,160 at 16 bits and 34 at 32 bits, 687 requantisations, 458 elementwise operations and 23 table blocks. Every one of twelve edits of the kernel was caught by the corpus. Measured 2026-10-22, release builds, rustc 1.98.0 | +| PCI vendor 0x1022, device 0x17f0, revision 0x11: its tile array's second generation, firmware 1.1.2.65; one compute core used | the tile array's open toolchain 1.4.3 with its core compiler 22.0.0, device runtime 2.21.75, Linux 7.0 x86_64 | every graph vector in the subset: 733 vectors and 1,005 decodes, 32 of them refusals, with no difference. 302 of the vectors move data only, so the device ran nothing for them. Each operand width and each kind of work ran, counted in the units the core takes at once: 77 sums at 8 bits, 4,160 at 16 bits and 34 at 32 bits, 687 requantisations, 458 elementwise runs and 23 table blocks. Every one of twelve edits of the kernel was caught by the corpus. Measured 2026-10-22, release builds, rustc 1.98.0 | | any other neural processor, including the neural engine of an arm64 desktop | | not run; nothing is claimed | *Time and energy* (`ops-npu-bench --secs 3 --rounds 3`, the same host, 2026-10-22). Every device result was first compared with the interpreter's. Each figure below is the range over three windows of 3 s: @@ -1052,18 +1052,18 @@ The vector unit's requantisation, with the scalar route for shifts of 60 to 63, - The device runtime also reports a power figure that it calls an estimate. Integrated over a window, that is a model, class `ModelBased`. It gave 2.0–2.6 J per device window. In idle windows it gave 0.3–1.2 J, higher right after device work, so it lags. - Per pass, the device path costs more energy than the CPU on these workloads. No energy claim is made for the device. -**The Web Neural Network API.** Whether a browser could run the subset exactly through this API was decided from the W3C Candidate Recommendation Draft of 21 October 2026 (`CRD-webnn-20261021`, section numbers below). No implementation of it was run. The API has no integer multiply-accumulate operator, and its requantisation operator rounds in floating point. So the subset is exact there only when each operator is built from the API's integer element-wise operations and reductions. Then every value stays within i32, which `acc32` certifies. The CR states no result for an integer value that leaves its type, and the certificate excludes one. +**The Web Neural Network API.** Whether a browser could run the subset exactly through this API was decided from the W3C Candidate Recommendation Draft of 21 October 2026 (`CRD-webnn-20261021`; its sections are cited below as CR §). No implementation of it was run. The API has no integer multiply-accumulate operator, and its requantisation operator rounds in floating point. So the subset is exact there only when each operator is built from the API's integer element-wise operations and reductions. Then every value stays within i32, which `acc32` certifies. The CR states no result for an integer value that leaves its type, and the certificate excludes one. | operator | through the API | why | |---|---|---| -| `conv`, `conv_transpose` | not through `conv2d` (§8.9.10), `convTranspose2d` (§8.9.11), `matmul` (§8.9.35) or `gemm` (§8.9.24) | Each accepts only `"float32"`, `"float16"` and `"bfloat16"` operands. A float32 sum of integer products is exact only while every partial sum stays within 2^24, and the CR does not fix the precision of intermediate results. Exact by composition: gather the taps (`slice` §8.9.47, `reshape` §8.9.42, `transpose` §8.9.54), then `mul` (§8.9.13) and `reduceSum` (§8.9.39), all on `"int32"`, the type each is required to support. 8-bit inputs widen first by `cast` (§8.9.7), which keeps an in-range integer's value. | -| `rescale`, `chscale`, `mul` (the requantisation of §5.1) | not through `quantizeLinear` (§8.9.17) or `dequantizeLinear` (§8.9.16) | `quantizeLinear` divides in floating point and rounds by `roundEven`. That gives `R_half_even` only for a power-of-two scale and a value within 2^24, and no form of `R_floor` or `R_half_up`. Its required outputs are 8-bit. `dequantizeLinear` gives a floating-point result. Exact by composition: the API has no shift. Its integer `div` truncates toward zero (§8.9.13), so a floor needs a correction built from `lesser` (§8.9.14) and `where` (§8.9.56). A multiplier forms a product up to 2^62, which needs `"int64"`: the CR allows that type for `mul` but does not require it, and `opSupportLimits()` (§8.3.9) says whether an implementation has it. A shift of 63 has no `"int64"` divisor. | -| `lut16` | exact through `gather` (§8.9.20) | The table is an `"int32"` input, and the index `v + 32768` is an `"int32"` `add`. Every index lies in the table (§5.6). | -| `add`, `chunk_add` | exact through `add` (§8.9.13), with `slice` for `chunk_add` | `"int32"` is a required type. | -| `abs` | exact (§8.9.15) | `"int32"` is a required type. | -| `relu`, `clamp` | exact through `max` and `min` (§8.9.13) on `"int32"` | `relu` (§8.9.40) and `clamp` (§8.9.8) allow `"int32"` but require only floating-point types. | -| `leaky` | not through `leakyRelu` (§8.9.31) | It takes floating-point operands and a floating-point slope. Exact by composition: `mul`, then a floor division by `2^f` built as for the requantisation, then `where`. | -| the movement operators | exact through `concat` (§8.9.9), `slice`, `reshape`, `transpose`, `pad` in its `"edge"` mode (§8.9.36) and `expand` (§8.9.19) | They move values and compute none. `pad` allows `"int32"` but requires only floating-point types. | +| `conv`, `conv_transpose` | not through `conv2d` (CR §8.9.10), `convTranspose2d` (CR §8.9.11), `matmul` (CR §8.9.35) or `gemm` (CR §8.9.24) | Each accepts only `"float32"`, `"float16"` and `"bfloat16"` operands. A float32 sum of integer products is exact only while every partial sum stays within 2^24, and the CR does not fix the precision of intermediate results. Exact by composition: gather the taps (`slice` CR §8.9.47, `reshape` CR §8.9.42, `transpose` CR §8.9.54), then `mul` (CR §8.9.13) and `reduceSum` (CR §8.9.39), all on `"int32"`, the type each is required to support. 8-bit inputs widen first by `cast` (CR §8.9.7), which keeps an in-range integer's value. | +| `rescale`, `chscale`, `mul` (the requantisation of §5.1) | not through `quantizeLinear` (CR §8.9.17) or `dequantizeLinear` (CR §8.9.16) | `quantizeLinear` divides in floating point and rounds by `roundEven`. That gives `R_half_even` only for a power-of-two scale and a value within 2^24, and no form of `R_floor` or `R_half_up`. Its required outputs are 8-bit. `dequantizeLinear` gives a floating-point result. Exact by composition: the API has no shift. Its integer `div` truncates toward zero (CR §8.9.13), so a floor needs a correction built from `lesser` (CR §8.9.14) and `where` (CR §8.9.56). A multiplier forms a product up to 2^62, which needs `"int64"`: the CR allows that type for `mul` but does not require it, and `opSupportLimits()` (CR §8.3.9) says whether an implementation has it. A shift of 63 has no `"int64"` divisor. | +| `lut16` | exact through `gather` (CR §8.9.20) | The table is an `"int32"` input, and the index `v + 32768` is an `"int32"` `add`. Every index lies in the table (§5.6). | +| `add`, `chunk_add` | exact through `add` (CR §8.9.13), with `slice` for `chunk_add` | `"int32"` is a required type. | +| `abs` | exact (CR §8.9.15) | `"int32"` is a required type. | +| `relu`, `clamp` | exact through `max` and `min` (CR §8.9.13) on `"int32"` | `relu` (CR §8.9.40) and `clamp` (CR §8.9.8) allow `"int32"` but require only floating-point types. | +| `leaky` | not through `leakyRelu` (CR §8.9.31) | It takes floating-point operands and a floating-point slope. Exact by composition: `mul`, then a floor division by `2^f` built as for the requantisation, then `where`. | +| the movement operators | exact through `concat` (CR §8.9.9), `slice`, `reshape`, `transpose`, `pad` in its `"edge"` mode (CR §8.9.36) and `expand` (CR §8.9.19) | They move values and compute none. `pad` allows `"int32"` but requires only floating-point types. | ## 10. `WHG1`: the graph form -- 2.54.0 (Apple Git-157) From d7037c7c7b0c9d38d1b73cba0dde16f0c2278f29 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:27:56 -0400 Subject: [PATCH 009/242] =?UTF-8?q?wai:=20prior-forms=20corpus=20=E2=80=94?= =?UTF-8?q?=20README,=20independent=20Python=20verifier,=20and=20the=20NNC?= =?UTF-8?q?=20subset=20measured=20against=20an=20open=20implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit prior_forms_verify.py implements prior-carriage §9 to §12 and Appendix A from the text (strict readers, canonical writers, the arithmetic coder both ways, both bindings) and agrees with wai-rs on every vector; it also flips bytes of every accepted file and requires each copy to be caught. §12.4 records the measurement: an open implementation of ISO/IEC 15938-17 reads our NNC files to the same integers, its encoder writes byte-identical data units, and our reader reads its bitstreams. tools/nnc_interop_check.py is the check; it runs that implementation's code and is not a CI step. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/prior-carriage.md | 24 +- .../prior-forms/.gitattributes | 2 + wai/live-conformance/prior-forms/README.md | 61 +- wai/live-conformance/prior_forms_verify.py | 1322 +++++++++++++++++ wai/tools/nnc_interop_check.py | 94 ++ wai/wai-rs/src/strict_json.rs | 2 +- 6 files changed, 1498 insertions(+), 7 deletions(-) create mode 100644 wai/live-conformance/prior-forms/.gitattributes create mode 100644 wai/live-conformance/prior_forms_verify.py create mode 100644 wai/tools/nnc_interop_check.py diff --git a/wai/extensions/prior-carriage.md b/wai/extensions/prior-carriage.md index c4e8bf423..92fbf86db 100644 --- a/wai/extensions/prior-carriage.md +++ b/wai/extensions/prior-carriage.md @@ -426,8 +426,8 @@ one reported. `tensor_malformed`). An integer here is written in plain decimal (no sign, fraction, exponent or leading zero) and is at most 2^64 − 1. 6. A tensor's element count is the product of its `shape` (1 for an empty `shape`); a - product past 2^64 − 1 is `tensor_malformed`. `end − begin` is that count times the - element's size (`offset_mismatch`). + count, or a size in bytes, past 2^64 − 1 is `tensor_malformed`. `end − begin` is that + count times the element's size (`offset_mismatch`). 7. Offsets count from the byte after the header. Ordered by `begin`, the tensors cover those bytes exactly: the first begins at 0, each begins where the one before ends, and the last ends at the end of the file (`offset_mismatch`). @@ -477,7 +477,7 @@ of the file is `header_truncated`. u64 each, innermost first, none 0 (`tensor_malformed`); the type, a u32 from the table below (`dtype_unknown`); the offset, a u64, a multiple of the alignment (`offset_mismatch`). A `Q8_0` tensor's innermost dimension is a multiple of 32, and - a count past 2^64 − 1 is malformed (`tensor_malformed`). + an element count or a size in bytes past 2^64 − 1 is malformed (`tensor_malformed`). 7. The tensor data begins at the first multiple of the alignment at or after the end of the descriptions; the bytes between are zero (`header_malformed`), and the file ending before the data begins is `header_truncated`. A tensor's bytes are at its @@ -652,8 +652,8 @@ states compliance with the 2024 edition (§8): its high-level syntax and its ari coder. The syntax of §12.2 is the syntax that implementation reads and writes, and Appendix A is its arithmetic coding. The standard's syntax, decoding and entropy-coding clauses were not read, so nothing here states a field's semantics beyond what that -implementation does with it, and neither the reader nor the writer was tested against -the conformance bitstreams of ISO/IEC 15938-18. +implementation does with it. The reader and writer were measured against that +implementation (§12.4), not against the conformance bitstreams of ISO/IEC 15938-18. The subset: one `NNR_STR`, first; one `NNR_MPS`, before any data unit, with no performance map and topology elements referenced by name; at most one `NNR_TPL`, after @@ -750,6 +750,20 @@ where present, `dq_flag` 0. Each unit takes the 15-bit size when the whole unit 32 768 bytes, else the 31-bit size. An integer past ±(2^31 − 1) has no `NNR_PT_INT` form (`value_out_of_range`). +### 12.4 Measured against an open implementation + +On 2026-10-22 the subset was measured against the open implementation of §12.1 (source +at commit `0e3dcd2961701572d253f175754c09952a4b44c7`, its arithmetic coder compiled from +that source; arm64): for each of the seven priors of the corpus (§14), that +implementation's decoder read the NNC file this extension's writer made to the integers +the prior holds; its encoder, given those integers (one topology element per data unit, +`dq_flag` 0, `scan_order` 0, unary length 10, no parameter optimisation), wrote every +data unit byte-identical to this writer's; and this reader read that encoder's whole +bitstream to the same integers. Its bitstream differs from this writer's only in the +topology unit, which carries §10.1's metadata here. The check is +[`tools/nnc_interop_check.py`](../tools/nnc_interop_check.py); it runs the other +implementation's code, so it is a measurement this repository records, not a CI step. + ## 13. Refusals | code | where | diff --git a/wai/live-conformance/prior-forms/.gitattributes b/wai/live-conformance/prior-forms/.gitattributes new file mode 100644 index 000000000..eff1df73a --- /dev/null +++ b/wai/live-conformance/prior-forms/.gitattributes @@ -0,0 +1,2 @@ +# Hashed files: a line-ending conversion on checkout would change every digest. +* -text diff --git a/wai/live-conformance/prior-forms/README.md b/wai/live-conformance/prior-forms/README.md index dc8613de9..974550c77 100644 --- a/wai/live-conformance/prior-forms/README.md +++ b/wai/live-conformance/prior-forms/README.md @@ -1 +1,60 @@ -# live-conformance/prior-forms +# live-conformance/prior-forms — tensor files as prior forms + +The corpus of [`extensions/prior-carriage.md`](../../extensions/prior-carriage.md) §14: +`vectors.json` and the files it names under `files/`. + +`vectors.json` holds: + +- `natives`: the native artifacts the corpus reads, each a file another corpus commits, + named by its path from this directory and pinned by its SHA-256: the trained `WSR1` + priors `generic` and `title` and the seeded one `seeded` of + [`../upscale/priors/`](../upscale/priors/), and the generated `WHM1` models `hyper_g_s`, + `hyper_h_s`, `world_t` and `world_g_s` of + [`../../ops-conformance/sets/`](../../ops-conformance/sets/); +- `files`: every file of `files/` with its SHA-256; +- `vectors`, each one of: + - `encode`: a native artifact (`native`, or a file of `files/natives/` in `native_file`) + under a `binding`, written in a `form` (§10.3, §11). `accept` gives the `file` the + writer must produce, byte for byte; `refuse` gives the `code` (§13); + - `load`: a `file` in a `form`, loaded by §10.3. `accept` gives the `binding` and the + `native` artifact the load must produce, byte for byte; `refuse` gives the `code`. + +`files/.` are the canonical files of each native artifact in each form +(`safetensors`, `gguf`, `nnc`); `files/natives/` holds native artifacts that do not read +(`trailing.wsr`, `badtag.whm`) and one whose integers NNC's integer payload cannot carry +(`wide.whm`); `files/neg/` holds the negative controls, each a file refused on load with +the code its vector states, so that the corpus gives every code of §13. + +**Exactness.** A verifier conforms when it reaches every vector's outcome, its bytes and +its code. A prior loaded from any of its three files is its native artifact, byte for byte. + +**Provenance.** `wai_prior_forms_vectors gen` writes `vectors.json` and `files/` from the +seven native artifacts, which it reads and checks against their pins +(`prior_wire::corpus::INPUT_NATIVES`). Every file is a function of those bytes: the +encodes, and each negative control, made from the seeded prior's files by an edit +written in code (`prior_wire::corpus::generate`). The two trained priors are the +exact-upscale corpus's, trained there on generated pictures; no other data is in the +corpus. + +``` +python3 -I ../prior_forms_verify.py . +``` + +**Measured against another implementation (not in CI).** prior-carriage §12.4 records a +measurement of the NNC files against an open implementation of ISO/IEC 15938-17 that +states compliance with its 2024 edition: its decoder reads them to the same integers, its +encoder writes the same data units, and our reader reads its bitstreams. To repeat it, +with that implementation's source at the commit §12.4 names (its licence is a BSD licence +that grants no patent rights; its source is not in this repository): + +``` +python3 -m venv venv && venv/bin/pip install numpy==2.0.2 pybind11==2.13.6 +cd && c++ -O2 -std=c++17 -shared -fPIC -undefined dynamic_lookup \ + $(../venv/bin/python -m pybind11 --includes) -Iextensions/deepCABAC/source \ + -Iextensions/deepCABAC/source/Lib -Iextensions/deepCABAC/source/Lib/CommonLib \ + extensions/deepCABAC/source/*.cpp extensions/deepCABAC/source/Lib/*/*.cpp \ + -o /deepCABAC$(../venv/bin/python -c 'import sysconfig; print(sysconfig.get_config_var("EXT_SUFFIX"))') +PYTHONPATH= venv/bin/python /tools/nnc_interop_check.py /live-conformance /live-conformance/prior-forms +``` + +It prints `ALL_OK` when every prior passes all three checks. diff --git a/wai/live-conformance/prior_forms_verify.py b/wai/live-conformance/prior_forms_verify.py new file mode 100644 index 000000000..659823ea8 --- /dev/null +++ b/wai/live-conformance/prior_forms_verify.py @@ -0,0 +1,1322 @@ +#!/usr/bin/env python3 +"""Independent verifier of the prior-forms corpus (extensions/prior-carriage.md §14). + +Written from the extension's text: §9 (the safetensors and GGUF layouts), §10 (the +integer-quantised profile and its bindings), §11 (the canonical forms), §12 (the NNC +subset) and Appendix A (the integer payload's arithmetic coding). It shares no code with +wai-rs, which generates the corpus, and reads §13's table of codes from the extension. + +For every vector it requires the outcome the corpus states: for an encode, the file's +bytes or the code; for a load, the binding and the native artifact's bytes or the code. +Then it flips one byte of each accepted file, in several places, and requires every copy +to be refused or to load to other bytes. + + python3 -I prior_forms_verify.py + +Exit status 0 only if every vector verifies and every mutated copy is caught. +""" +import hashlib +import json +import os +import re +import struct +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +EXTENSION = os.path.join(HERE, "..", "extensions", "prior-carriage.md") + + +class Refuse(Exception): + def __init__(self, code, why=""): + super().__init__(f"{code}: {why}") + self.code = code + + +def sha(b): + return hashlib.sha256(b).hexdigest() + + +# ------------------------------------------------------------------ §13 codes + +def codes(): + text = open(EXTENSION, encoding="utf-8").read() + sec = text.split("\n## 13.")[1].split("\n## ")[0] + return [m.group(1) for m in re.finditer(r"^\| `([a-z_]+)` \|", sec, re.M)] + + +# ------------------------------------------------------------------ strict JSON (§9.1 step 3) + +def strict_json(b): + """RFC 8259 over UTF-8, no member named twice, at most 64 levels; numbers kept as spelt.""" + try: + text = b.decode("utf-8") + except UnicodeDecodeError: + raise Refuse("header_malformed", "not UTF-8") + + def pairs(ps): + seen = set() + for k, _ in ps: + if k in seen: + raise Refuse("header_malformed", f"member {k!r} twice") + seen.add(k) + return ("obj", ps) + + def bad_const(c): + raise Refuse("header_malformed", f"{c} is not JSON") + + try: + v = json.loads(text, object_pairs_hook=pairs, parse_int=lambda s: ("num", s), + parse_float=lambda s: ("num", s), parse_constant=bad_const) + except Refuse: + raise + except (ValueError, RecursionError) as e: + raise Refuse("header_malformed", str(e)) + + def walk(x, d): + if d > 64: + raise Refuse("header_malformed", "nested too deeply") + if isinstance(x, str): + try: + x.encode("utf-8") + except UnicodeEncodeError: + raise Refuse("header_malformed", "an unpaired surrogate") + elif isinstance(x, list): + for y in x: + walk(y, d + 1) + elif isinstance(x, tuple) and x[0] == "obj": + for k, y in x[1]: + walk(k, d) + walk(y, d + 1) + + walk(v, 1) + return v + + +def plain_uint(x): + """An integer in plain decimal, at most 2^64 - 1 (§9.1 step 5), or None.""" + if not (isinstance(x, tuple) and x[0] == "num"): + return None + s = x[1] + if not re.fullmatch(r"0|[1-9][0-9]*", s): + return None + v = int(s) + return v if v < 1 << 64 else None + + +def jquote(s): + """A JSON string as RFC 8785 §3.2.2.2 escapes it.""" + return json.dumps(s, ensure_ascii=False) + + +# ------------------------------------------------------------------ element types (§9.1, §9.2) + +ST_SIZE = {"BOOL": 1, "U8": 1, "I8": 1, "U16": 2, "I16": 2, "F16": 2, "BF16": 2, "U32": 4, "I32": 4, + "F32": 4, "U64": 8, "I64": 8, "F64": 8, "F8_E5M2": 1, "F8_E4M3": 1} +GG_TYPES = {0: "F32", 1: "F16", 8: "Q8_0", 24: "I8", 25: "I16", 26: "I32", 27: "I64", 28: "F64", 30: "BF16"} +GG_NUMBER = {v: k for k, v in GG_TYPES.items()} +INT_WIDTH = {"I8": 1, "I16": 2, "I32": 4, "I64": 8} + + +def nbytes(dtype, n): + if dtype == "Q8_0": + return None if n % 32 else n // 32 * 34 + return n * ST_SIZE[dtype] + + +def product(shape): + p = 1 + for d in shape: + p *= d + return p + + +class TSet: + """Metadata (str -> str) and tensors name -> (dtype, shape, bytes).""" + + def __init__(self): + self.meta = {} + self.t = {} + + +# ------------------------------------------------------------------ §9.1 safetensors + +def st_read(b): + if len(b) < 8: + raise Refuse("header_truncated", "under 8 bytes") + n = struct.unpack_from(" 100_000_000: + raise Refuse("header_too_large", str(n)) + if 8 + n > len(b): + raise Refuse("header_truncated", "header past the end") + doc = strict_json(b[8:8 + n]) + if not (isinstance(doc, tuple) and doc[0] == "obj"): + raise Refuse("header_malformed", "not an object") + data = b[8 + n:] + s = TSet() + spans = [] + for name, v in doc[1]: + if name == "__metadata__": + if not (isinstance(v, tuple) and v[0] == "obj"): + raise Refuse("header_malformed", "__metadata__") + for k, mv in v[1]: + if not isinstance(mv, str): + raise Refuse("header_malformed", "metadata not a string") + s.meta[k] = mv + continue + if name == "": + raise Refuse("tensor_malformed", "empty name") + if not (isinstance(v, tuple) and v[0] == "obj") or sorted(k for k, _ in v[1]) != ["data_offsets", "dtype", "shape"]: + raise Refuse("tensor_malformed", name) + m = dict(v[1]) + if not isinstance(m["dtype"], str): + raise Refuse("tensor_malformed", "dtype not a string") + if m["dtype"] not in ST_SIZE: + raise Refuse("dtype_unknown", m["dtype"]) + if not isinstance(m["shape"], list) or any(plain_uint(d) is None for d in m["shape"]): + raise Refuse("tensor_malformed", "shape") + shape = [plain_uint(d) for d in m["shape"]] + offs = m["data_offsets"] + if not isinstance(offs, list) or len(offs) != 2 or any(plain_uint(o) is None for o in offs): + raise Refuse("tensor_malformed", "data_offsets") + b0, e0 = plain_uint(offs[0]), plain_uint(offs[1]) + if b0 > e0: + raise Refuse("tensor_malformed", "begin past end") + cnt = product(shape) + if cnt >= 1 << 64 or nbytes(m["dtype"], cnt) >= 1 << 64: + raise Refuse("tensor_malformed", "count overflows") + if e0 - b0 != nbytes(m["dtype"], cnt): + raise Refuse("offset_mismatch", name) + spans.append((b0, e0, name)) + s.t[name] = [m["dtype"], shape, None] + at = 0 + for b0, e0, name in sorted(spans): + if b0 != at: + raise Refuse("offset_mismatch", f"{name} at {b0}, data so far {at}") + at = e0 + if at != len(data): + raise Refuse("offset_mismatch", "uncovered data") + for b0, e0, name in spans: + s.t[name][2] = data[b0:e0] + return s + + +def st_write(s): + """§11.1.""" + parts = [] + if s.meta: + parts.append('"__metadata__":{' + ",".join(f"{jquote(k)}:{jquote(s.meta[k])}" for k in sorted(s.meta, key=lambda x: x.encode())) + "}") + at = 0 + names = sorted(s.t, key=lambda x: x.encode()) + for name in names: + dt, shape, data = s.t[name] + parts.append(f'{jquote(name)}:{{"dtype":"{dt}","shape":[{",".join(str(d) for d in shape)}],"data_offsets":[{at},{at + len(data)}]}}') + at += len(data) + h = ("{" + ",".join(parts) + "}").encode() + while (8 + len(h)) % 8: + h += b" " + return struct.pack(" len(self.b): + raise Refuse("header_truncated", f"{n} bytes at {self.p}") + s = self.b[self.p:self.p + n] + self.p += n + return s + + def u32(self): + return struct.unpack(" 1: + raise Refuse("header_malformed", "boolean") + return None + if t == 8: + return c.string() + if t == 9: + et = c.u32() + if et == 9: + raise Refuse("header_malformed", "array of arrays") + if et != 8 and et not in FIXED: + raise Refuse("header_malformed", f"array of {et}") + n = c.u64() + if n * FIXED.get(et, 8) > len(c.b) - c.p: + raise Refuse("header_truncated", "array past the end") + for _ in range(n): + gg_value(c, et) + return None + raise Refuse("header_malformed", f"value type {t}") + + +KEY = re.compile(r"[a-z0-9_]+(\.[a-z0-9_]+)*") + + +def gg_read(b): + if len(b) < 4: + raise Refuse("header_truncated", "under 4 bytes") + if b[:4] != b"GGUF": + raise Refuse("bad_magic") + c = Cur(b) + c.p = 4 + if c.u32() != 3: + raise Refuse("version_unsupported") + nt, nkv = c.u64(), c.u64() + if nkv * 12 > len(b) or nt * 28 > len(b): + raise Refuse("header_truncated", "counts") + s = TSet() + keys = set() + align = 32 + for _ in range(nkv): + k = c.string() + if not (len(k.encode()) <= 65535 and KEY.fullmatch(k)): + raise Refuse("header_malformed", f"key {k!r}") + if k in keys: + raise Refuse("header_malformed", f"key {k!r} twice") + keys.add(k) + t = c.u32() + at = c.p + v = gg_value(c, t) + if k == "general.alignment": + a = struct.unpack_from("= 1 << 64 or nbytes(dt, cnt) >= 1 << 64: + raise Refuse("tensor_malformed", "count") + infos.append((off, nbytes(dt, cnt), name)) + s.t[name] = [dt, list(reversed(dims)), None] + hend = c.p + dstart = -(-hend // align) * align + if dstart > len(b): + raise Refuse("header_truncated", "data begins past the end") + if any(b[hend:dstart]): + raise Refuse("header_malformed", "padding") + data = b[dstart:] + at = 0 + for off, ln, name in sorted(infos): + if off < at: + raise Refuse("offset_mismatch", "overlap") + if off + ln > len(data): + raise Refuse("offset_mismatch", "past the end") + s.t[name][2] = data[off:off + ln] + at = off + ln + return s + + +def gg_write(s): + """§11.2.""" + def st(x): + x = x.encode() + return struct.pack("> (bits - 1) else x + + +class Ctx: + def __init__(self, j=0): + b0, b1, v01, v0 = INIT[j] + self.s0, self.s01, self.h0, self.h1 = v0, v01, b0 + 4, b1 + + def mps(self): + return 1 if self.s01 >= 0 else 0 + + def rlps(self, rng): + i = abs(self.s01 >> 7) + (rng & 0xE0) + if not 0 <= i < len(T): + raise Refuse("nnc_malformed", "state outside table A.1") + return T[i] + + def update(self, b): + sg = 1 if b == 1 else -1 + s1 = self.s01 - 16 * self.s0 + + def cv(x, k): + i = 16 + ((sg * x) >> k) + if not 0 <= i < len(C): + raise Refuse("nnc_malformed", "state outside table A.2") + return C[i] + s0 = wrap(self.s0 + sg * (cv(self.s0, 3) >> self.h0), 8) + s1 = s1 + sg * (cv(s1, 7) >> self.h1) + self.s0, self.s01 = s0, wrap(s1 + 16 * s0, 16) + + +def lz(x): + return 32 - x.bit_length() + + +class Dec: + def __init__(self, b): + if len(b) < 2: + raise Refuse("nnc_truncated", "payload under 2 bytes") + self.b, self.p = b, 2 + self.rng, self.val, self.need = 510, 256 * b[0] + b[1], -8 + + def byte(self): + if self.p >= len(self.b): + raise Refuse("nnc_truncated", "payload ends inside its unit") + v = self.b[self.p] + self.p += 1 + return v + + def bin(self, ctx): + r = ctx.rlps(self.rng) + m = self.rng - r + if self.val < m * 128: + b, self.rng = ctx.mps(), m + else: + self.val -= m * 128 + self.rng, b = r, 1 - ctx.mps() + n = lz(self.rng) - 23 + self.rng <<= n + self.val <<= n + self.need += n + if self.need >= 0: + self.val += self.byte() << self.need + self.need -= 8 + ctx.update(b) + return b + + def bypass(self, k=1): + v = 0 + for _ in range(k): + self.val *= 2 + self.need += 1 + if self.need >= 0: + self.val += self.byte() + self.need = -8 + bit = 0 + if self.val >= self.rng * 128: + self.val -= self.rng * 128 + bit = 1 + v = 2 * v + bit + return v + + def finish(self): + self.rng -= 2 + if self.val < self.rng * 128: + raise Refuse("nnc_malformed", "no terminating bin") + if ((self.b[self.p - 1] << (8 + self.need)) & 0xFF) != 0x80: + raise Refuse("nnc_malformed", "no stop pattern") + return self.p + + +def cpos(p): + return 0 if p == 0 else (1 if p < 0 else 2) + + +def a_decode(b, n, g): + d = Dec(b) + L = 24 + 3 + 2 * g + 32 + 4 + ctx = [Ctx() for _ in range(L)] + for i in range(L - 4): + if 3 <= i <= 23: + continue + j = 1 + d.bypass(3) if d.bin(ctx[L - 2]) == 1 else 0 + ctx[i] = Ctx(j) + out, p = [], 0 + for _ in range(n): + v = 0 + if d.bin(ctx[cpos(p)]) == 1: + v = 1 + s = d.bin(ctx[24 + cpos(p)]) + gg = d.bin(ctx[27 + s]) + k = 1 + while gg == 1 and k < g: + v += 1 + gg = d.bin(ctx[27 + s + 2 * k]) + k += 1 + if gg == 1: + v += 1 + q = 27 + 2 * g + rem = 0 + if d.bin(ctx[q]) == 1: + rem, q, e = 1, q + 1, 1 + while True: + if q >= 59 + 2 * g: + raise Refuse("nnc_malformed", "remainder prefix") + if d.bin(ctx[q]) == 0: + break + rem += 1 << e + q += 1 + e += 1 + rem += d.bypass(e) + v += rem + if v > (1 << 31) - 1: + raise Refuse("nnc_malformed", "past 2^31 - 1") + if s == 1: + v = -v + out.append(v) + p = v + return out, d.finish() + + +class Enc: + M = 0xFFFFFFFF + + def __init__(self): + self.low, self.rng, self.left, self.B, self.n, self.o = 0, 510, 23, 0, 0, bytearray() + + def out(self): + lead = self.low >> (24 - self.left) + self.left += 8 + self.low &= self.M >> self.left + if lead == 0xFF: + self.n += 1 + elif self.n > 0: + c = lead >> 8 + self.o.append((self.B + c) % 256) + self.B = lead % 256 + for _ in range(self.n - 1): + self.o.append((0xFF + c) % 256) + self.n = 1 + else: + self.n, self.B = 1, lead + + def bin(self, b, ctx): + r = ctx.rlps(self.rng) + self.rng -= r + if b == ctx.mps(): + if self.rng < 256: + self.rng *= 2 + self.low = (2 * self.low) & self.M + self.left -= 1 + if self.left < 12: + self.out() + else: + k = lz(r) - 23 + self.low = (self.low + self.rng) & self.M + self.rng = r << k + self.low = (self.low << k) & self.M + self.left -= k + if self.left < 12: + self.out() + ctx.update(b) + + def bypass(self, v, k): + for i in reversed(range(k)): + self.low = (2 * self.low) & self.M + if (v >> i) & 1: + self.low = (self.low + self.rng) & self.M + self.left -= 1 + if self.left < 12: + self.out() + + def finish(self): + self.rng -= 2 + self.low = ((self.low + self.rng) << 7) & self.M + self.rng = 256 + self.left -= 7 + if self.left < 12: + self.out() + if self.low >> (32 - self.left): + self.o.append((self.B + 1) % 256) + self.o += b"\0" * (self.n - 1) + self.low -= 1 << (32 - self.left) + else: + if self.n > 0: + self.o.append(self.B) + self.o += b"\xff" * max(self.n - 1, 0) + self.low = 2 * (self.low >> 8) + 1 + self.left -= 1 + self.low = (self.low << (32 - (24 - self.left))) & self.M + i = 0 + while i < 24 - self.left: + self.o.append((self.low >> (24 - i)) & 0xFF) + i += 8 + return bytes(self.o) + + +def a_encode(vals, g): + e = Enc() + L = 24 + 3 + 2 * g + 32 + 4 + ctx = [Ctx() for _ in range(L)] + for i in range(L - 4): + if 3 <= i <= 23: + continue + e.bin(0, ctx[L - 2]) + p = 0 + for v in vals: + if abs(v) > (1 << 31) - 1: + raise Refuse("value_out_of_range", "no NNR_PT_INT form") + e.bin(int(v != 0), ctx[cpos(p)]) + if v != 0: + s = 1 if v < 0 else 0 + e.bin(s, ctx[24 + cpos(p)]) + r = abs(v) - 1 + gg = int(r > 0) + e.bin(gg, ctx[27 + s]) + k = 1 + while gg == 1 and k < g: + r -= 1 + gg = int(r > 0) + e.bin(gg, ctx[27 + s + 2 * k]) + k += 1 + if gg == 1: + r -= 1 + q = 27 + 2 * g + if r == 0: + e.bin(0, ctx[q]) + else: + e.bin(1, ctx[q]) + base, ex = 1, 1 + q += 1 + while r > base + (1 << ex) - 1: + e.bin(1, ctx[q]) + base += 1 << ex + q += 1 + ex += 1 + e.bin(0, ctx[q]) + e.bypass(r - base, ex) + p = v + return e.finish() + + +# ------------------------------------------------------------------ §12 NNC + +class Bits: + def __init__(self, b, bit=0): + self.b, self.bit = b, bit + + def u(self, n): + v = 0 + for _ in range(n): + if self.bit // 8 >= len(self.b): + raise Refuse("nnc_truncated", "a field past its unit") + v = 2 * v + ((self.b[self.bit // 8] >> (7 - self.bit % 8)) & 1) + self.bit += 1 + return v + + def i(self, n): + v = self.u(n) + return v - (1 << n) if v >> (n - 1) else v + + def ue(self, k): + x = 0 + while True: + if k >= 40: + raise Refuse("nnc_malformed", "exp-Golomb too long") + if self.u(1) == 1: + break + x += 1 << k + k += 1 + return x + self.u(k) + + def align(self): + if self.u(1) != 1: + raise Refuse("nnc_malformed", "alignment bit") + while self.bit % 8: + if self.u(1) != 0: + raise Refuse("nnc_malformed", "alignment padding") + + def st(self): + if self.bit % 8: + raise Refuse("nnc_malformed", "string not on a byte") + at = self.bit // 8 + z = self.b.find(b"\0", at) + if z < 0: + raise Refuse("nnc_truncated", "string without zero byte") + try: + s = self.b[at:z].decode("utf-8") + except UnicodeDecodeError: + raise Refuse("nnc_malformed", "string not UTF-8") + self.bit = (z + 1) * 8 + return s + + +def nnc_read(b): + s = TSet() + at, mps, tpl, ndu, first = 0, False, False, False, True + names = [] + while at < len(b): + h = Bits(b[at:]) + flag = h.u(1) + size = h.u(31 if flag else 15) + if size < 3 or at + size > len(b): + raise Refuse("nnc_truncated", "unit size") + unit = b[at:at + size] + h = Bits(unit, h.bit) + ty = h.u(6) + h.u(1) + partial = h.u(1) + if first and ty != 0: + raise Refuse("nnc_malformed", "first unit") + if partial: + raise Refuse("nnc_not_read", "partial data counter") + + def done(): + if h.bit != 8 * len(unit): + raise Refuse("nnc_malformed", "unit size") + if ty == 0: + if not first: + raise Refuse("nnc_malformed", "second STR") + h.u(8) + done() + elif ty == 1: + if mps or ndu: + raise Refuse("nnc_malformed", "MPS") + mps = True + h.u(1) + maps, q, idx = h.u(4), h.u(3), h.u(1) + if h.u(7): + raise Refuse("nnc_malformed", "reserved") + if maps: + raise Refuse("nnc_not_read", "performance map") + if idx: + raise Refuse("nnc_not_read", "indexed reference") + if q: + h.u(3) + h.i(13) + h.align() + done() + elif ty == 3: + if tpl or not mps: + raise Refuse("nnc_malformed", "TPL") + tpl = True + fmt, comp = h.u(8), h.u(8) + if fmt not in (0, 3, 4) or comp: + raise Refuse("nnc_not_read", "topology format") + text = h.st() + done() + if fmt == 0: + try: + obj = strict_json(text.encode()) + except Refuse: + obj = None + if isinstance(obj, tuple) and obj[0] == "obj" and all(isinstance(v, str) for _, v in obj[1]): + for k, v in obj[1]: + s.meta[k] = v + elif ty == 5: + if not mps: + raise Refuse("nnc_malformed", "NDU before MPS") + ndu = True + name, t = nnc_ndu(h, unit) + names.append(name) + s.t[name] = t + elif ty in (2, 4, 6): + raise Refuse("nnc_not_read", f"unit type {ty}") + else: + raise Refuse("nnc_malformed", f"unit type {ty}") + first = False + at += size + if first: + raise Refuse("nnc_truncated", "empty") + if len(set(names)) != len(names): + raise Refuse("nnc_malformed", "a name twice") + return s + + +def nnc_ndu(h, unit): + pt, multi, fmtp, inband = h.u(5), h.u(1), h.u(1), h.u(1) + if pt not in (0, 2): + raise Refuse("nnc_not_read", f"payload type {pt}") + if multi: + raise Refuse("nnc_not_read", "several elements") + name = h.st() + if name == "": + raise Refuse("nnc_malformed", "empty name") + if pt == 0 and h.u(1): + raise Refuse("nnc_not_read", "dq_flag") + if fmtp and h.u(7) != (0 if pt == 0 else 1): + raise Refuse("nnc_not_read", "decompressed data format") + if not inband: + raise Refuse("nnc_not_read", "out of band") + dflag, uflag, cpt = h.u(1), h.u(1), h.u(4) + if cpt: + raise Refuse("nnc_not_read", "compressed parameter types") + if not dflag: + raise Refuse("nnc_not_read", "dimensions out of band") + nd = h.ue(1) + if not 1 <= nd <= 16: + raise Refuse("nnc_malformed", "dimension count") + shape = [h.ue(7) for _ in range(nd)] + cnt = product(shape) + if cnt == 0 or cnt >= 1 << 64: + raise Refuse("nnc_malformed", "count") + if cnt > 1 << 28: + raise Refuse("nnc_not_read", "too many elements") + unary = h.u(8) if uflag else None + if nd > 1 and h.u(4): + raise Refuse("nnc_not_read", "scan order") + h.align() + rest = unit[h.bit // 8:] + if pt == 2: + if len(rest) != 4 * cnt: + raise Refuse("nnc_malformed", "raw floats") + return name, ["F32", shape, bytes(rest)] + if unary is None: + raise Refuse("nnc_not_read", "unary length out of band") + vals, used = a_decode(rest, cnt, unary + 1) + if used != len(rest): + raise Refuse("nnc_malformed", "payload length") + return name, ["I32", shape, b"".join(struct.pack("> i) & 1: + self.o[-1] |= 1 << (7 - self.bit % 8) + self.bit += 1 + + def ue(self, k, x): + while x >= 1 << k: + self.u(1, 0) + x -= 1 << k + k += 1 + self.u(1, 1) + self.u(k, x) + + def align(self): + self.u(1, 1) + while self.bit % 8: + self.u(1, 0) + + def st(self, s): + self.o += s.encode() + b"\0" + self.bit = 8 * len(self.o) + + +def sized(body): + w = W() + if len(body) + 2 < 32768: + w.u(1, 0) + w.u(15, len(body) + 2) + else: + w.u(1, 1) + w.u(31, len(body) + 4) + return bytes(w.o) + bytes(body) + + +def head(w, ty): + w.u(6, ty) + w.u(1, 1) + w.u(1, 0) + + +def ints_of(dt, data): + k = INT_WIDTH[dt] + return [int.from_bytes(data[i:i + k], "little", signed=True) for i in range(0, len(data), k)] + + +def nnc_write(s): + """§12.3.""" + o = bytearray() + w = W() + head(w, 0) + w.u(8, 0) + o += sized(w.o) + w = W() + head(w, 1) + w.u(1, 1 if s.meta else 0) + w.u(4, 0) + w.u(3, 0) + w.u(1, 0) + w.u(7, 0) + w.align() + o += sized(w.o) + if s.meta: + w = W() + head(w, 3) + w.u(8, 0) + w.u(8, 0) + w.st("{" + ",".join(f"{jquote(k)}:{jquote(s.meta[k])}" for k in sorted(s.meta, key=lambda x: x.encode())) + "}") + o += sized(w.o) + for name in sorted(s.t, key=lambda x: x.encode()): + dt, shape, data = s.t[name] + pt = 0 if dt in INT_WIDTH else 2 + w = W() + head(w, 5) + w.u(5, pt) + w.u(1, 0) + w.u(1, 0) + w.u(1, 1) + w.st(name) + if pt == 0: + w.u(1, 0) + w.u(1, 1) + w.u(1, 1) + w.u(4, 0) + w.ue(1, len(shape)) + for d in shape: + w.ue(7, d) + w.u(8, 10) + if len(shape) > 1: + w.u(4, 0) + w.align() + body = bytes(w.o) + (a_encode(ints_of(dt, data), 11) if pt == 0 else bytes(data)) + o += sized(body) + return bytes(o) + + +# ------------------------------------------------------------------ §10 profile and bindings + +READ = {"wai.prior.safetensors": st_read, "wai.prior.gguf": gg_read, "wai.prior.nnc": nnc_read} +WRITE = {"wai.prior.safetensors": st_write, "wai.prior.gguf": gg_write, "wai.prior.nnc": nnc_write} +U32 = (1 << 32) - 1 + + +def profile_meta(binding, form): + m = {"wai.binding": binding, "wai.profile": "wai-int/1"} + if form == "wai.prior.gguf": + m["general.architecture"] = "wai" + return m + + +def ints_tensor(dt, shape, vals): + k = INT_WIDTH[dt] + lo, hi = -(1 << (8 * k - 1)), (1 << (8 * k - 1)) - 1 + for v in vals: + if not lo <= v <= hi: + raise Refuse("binding_mismatch", "a value wider than its type") + return [dt, list(shape), b"".join(v.to_bytes(k, "little", signed=True) for v in vals)] + + +class N: + """A cursor over a native artifact.""" + + def __init__(self, b): + self.b, self.p = b, 0 + + def take(self, n, why="truncated"): + if self.p + n > len(self.b): + raise Refuse("native_malformed", why) + s = self.b[self.p:self.p + n] + self.p += n + return s + + def u(self, k): + return int.from_bytes(self.take(k), "little") + + def ints(self, n, k): + raw = self.take(n * k) + return [int.from_bytes(raw[i:i + k], "little", signed=True) for i in range(0, len(raw), k)] + + +def wsr_from(b): + """exact-upscale §3.1 into the tensors of §10.2.""" + c = N(b) + if c.take(4, "bad_magic") != b"WSR1": + raise Refuse("native_malformed", "bad_magic") + if c.u(1) != 1: + raise Refuse("native_malformed", "version") + n, act = c.u(1), c.u(2) + if not (1 <= n <= 8 and 1 <= act <= 32767): + raise Refuse("native_malformed", "field_range") + s = TSet() + s.t["act_hi"] = ints_tensor("I16", [1], [act]) + cin = 3 + for i in range(n): + k, shift, cout = c.u(1), c.u(1), c.u(2) + cok = cout == 12 if i == n - 1 else 1 <= cout <= 64 + if k not in (1, 3, 5) or shift > 31 or not cok: + raise Refuse("native_malformed", "field_range") + w = c.ints(cout * cin * k * k, 1) + bias = c.ints(cout, 4) + mult = [c.u(2) for _ in range(cout)] + if 0 in mult: + raise Refuse("native_malformed", "field_range") + s.t[f"layer.{i}.weight"] = ints_tensor("I8", [cout, cin, k, k], w) + s.t[f"layer.{i}.bias"] = ints_tensor("I32", [cout], bias) + s.t[f"layer.{i}.mult"] = ints_tensor("I32", [cout], mult) + s.t[f"layer.{i}.shift"] = ints_tensor("I8", [1], [shift]) + cin = cout + if c.p != len(b): + raise Refuse("native_malformed", "trailing") + return s + + +def whm_from(b): + """integer-payloads §9 into the tensors of §10.2.""" + c = N(b) + if c.take(4) != b"WHM1": + raise Refuse("native_malformed", "magic") + af, gf, sh, n = c.u(4), c.u(4), c.u(4), c.u(4) + if n > 1000: + raise Refuse("binding_mismatch", "layers") + s = TSet() + s.t["header"] = ints_tensor("I64", [3], [af, gf, sh]) + for i in range(n): + tag = c.u(1) + p = f"layer.{i:03d}." + if tag in (0, 1): + geo = [c.u(4) for _ in range(6 if tag == 0 else 5)] + inc, out, k = geo[:3] + if 0 in (inc, out, k): + raise Refuse("binding_mismatch", "a zero dimension") + w = c.ints(inc * out * k * k, 2) + mult, bias = c.ints(out, 8), c.ints(out, 8) + kind = "convt" if tag == 0 else "conv" + s.t[p + kind] = ints_tensor("I64", [len(geo)], geo) + s.t[p + "weight"] = ints_tensor("I16", [inc, out, k, k] if tag == 0 else [out, inc, k, k], w) + s.t[p + "mult"] = ints_tensor("I64", [out], mult) + s.t[p + "bias"] = ints_tensor("I64", [out], bias) + elif tag == 2: + ch = c.u(4) + if ch == 0: + raise Refuse("binding_mismatch", "a zero dimension") + s.t[p + "igdn"] = ints_tensor("I64", [1], [ch]) + s.t[p + "gamma"] = ints_tensor("I32", [ch, ch], c.ints(ch * ch, 4)) + s.t[p + "beta"] = ints_tensor("I64", [ch], c.ints(ch, 8)) + elif tag == 3: + s.t[p + "relu"] = ints_tensor("I8", [1], [0]) + elif tag == 4: + q = c.ints(1, 8)[0] + s.t[p + "leaky"] = ints_tensor("I64", [2], [q, c.u(4)]) + else: + raise Refuse("native_malformed", "tag") + if c.p != len(b): + raise Refuse("native_malformed", "trailing") + return s + + +def tvals(s, name): + dt, shape, data = s.t[name] + return ints_of(dt, data) + + +def check_wants(s, form, wants, rng): + """§10.3 step 4's names, types and shapes, then step 5's values.""" + if set(s.t) != {w[0] for w in wants}: + raise Refuse("binding_mismatch", "names") + for name, dt, shape in wants: + want_dt = "I32" if form == "wai.prior.nnc" else dt + if s.t[name][0] != want_dt or s.t[name][1] != shape: + raise Refuse("binding_mismatch", name) + for name, dt, shape in wants: + k = INT_WIDTH[dt] + tlo, thi = -(1 << (8 * k - 1)), (1 << (8 * k - 1)) - 1 + for i, v in enumerate(tvals(s, name)): + r = rng(name, i) + lo, hi = (max(tlo, r[0]), min(thi, r[1])) if r else (tlo, thi) + if not lo <= v <= hi: + raise Refuse("value_out_of_range", f"{name}[{i}]") + + +def indices(s, prefix, suffix, width): + out = [] + for n in s.t: + if n.startswith(prefix) and n.endswith(suffix) and len(n) > len(prefix) + len(suffix): + d = n[len(prefix):len(n) - len(suffix)] + if re.fullmatch(r"[0-9]+", d) and (len(d) == width if width else (d == "0" or d[0] != "0")): + out.append(int(d)) + return sorted(out) + + +def wsr_to(s, form): + idx = indices(s, "layer.", ".weight", None) + n = len(idx) + if not 1 <= n <= 8 or idx != list(range(n)): + raise Refuse("binding_mismatch", "layers") + couts, ks = [], [] + for i in range(n): + shape = s.t[f"layer.{i}.weight"][1] + cout, k = (shape[0], shape[-1]) if shape else (0, 0) + cok = cout == 12 if i == n - 1 else 1 <= cout <= 64 + if k not in (1, 3, 5) or not cok: + raise Refuse("binding_mismatch", "weight shape") + couts.append(cout) + ks.append(k) + wants = [("act_hi", "I16", [1])] + cin = 3 + for i in range(n): + wants += [(f"layer.{i}.weight", "I8", [couts[i], cin, ks[i], ks[i]]), (f"layer.{i}.bias", "I32", [couts[i]]), + (f"layer.{i}.mult", "I32", [couts[i]]), (f"layer.{i}.shift", "I8", [1])] + cin = couts[i] + + def rng(name, i): + if name == "act_hi": + return (1, 32767) + if name.endswith(".mult"): + return (1, 65535) + if name.endswith(".shift"): + return (0, 31) + return None + check_wants(s, form, wants, rng) + o = bytearray(b"WSR1") + bytes([1, n]) + tvals(s, "act_hi")[0].to_bytes(2, "little") + for i in range(n): + o += bytes([ks[i], tvals(s, f"layer.{i}.shift")[0]]) + couts[i].to_bytes(2, "little") + o += bytes(v & 0xFF for v in tvals(s, f"layer.{i}.weight")) + o += b"".join(v.to_bytes(4, "little", signed=True) for v in tvals(s, f"layer.{i}.bias")) + o += b"".join(v.to_bytes(2, "little") for v in tvals(s, f"layer.{i}.mult")) + return bytes(o) + + +KINDS = ["convt", "conv", "igdn", "relu", "leaky"] + + +def whm_to(s, form): + kinds = {} + for kd in KINDS: + for i in indices(s, "layer.", "." + kd, 3): + if i in kinds: + raise Refuse("binding_mismatch", "two kinds") + kinds[i] = kd + n = len(kinds) + if sorted(kinds) != list(range(n)): + raise Refuse("binding_mismatch", "layers") + wants = [("header", "I64", [3])] + for i in range(n): + kd = kinds[i] + p = f"layer.{i:03d}." + if kd == "relu": + wants.append((p + "relu", "I8", [1])) + continue + ln = {"convt": 6, "conv": 5, "igdn": 1, "leaky": 2}[kd] + dt = "I32" if form == "wai.prior.nnc" else "I64" + if s.t[p + kd][0] != dt or s.t[p + kd][1] != [ln]: + raise Refuse("binding_mismatch", p + kd) + g = tvals(s, p + kd) + dims = g[:3] if kd in ("conv", "convt") else (g[:1] if kd == "igdn" else []) + for x in dims: + if not 1 <= x <= U32: + raise Refuse("value_out_of_range", p + kd) + wants.append((p + kd, "I64", [ln])) + if kd in ("conv", "convt"): + inc, out, k = g[:3] + wants += [(p + "weight", "I16", [inc, out, k, k] if kd == "convt" else [out, inc, k, k]), + (p + "mult", "I64", [out]), (p + "bias", "I64", [out])] + elif kd == "igdn": + wants += [(p + "gamma", "I32", [g[0], g[0]]), (p + "beta", "I64", [g[0]])] + + def rng(name, i): + if name == "header": + return (0, U32) + if name.endswith((".convt", ".conv")): + return (1, U32) if i < 3 else (0, U32) + if name.endswith(".igdn"): + return (1, U32) + if name.endswith(".relu"): + return (0, 0) + if name.endswith(".leaky") and i == 1: + return (0, U32) + return None + check_wants(s, form, wants, rng) + o = bytearray(b"WHM1") + b"".join(v.to_bytes(4, "little") for v in tvals(s, "header")) + n.to_bytes(4, "little") + for i in range(n): + kd = kinds[i] + p = f"layer.{i:03d}." + if kd in ("conv", "convt"): + o.append(0 if kd == "convt" else 1) + o += b"".join(v.to_bytes(4, "little") for v in tvals(s, p + kd)) + o += b"".join(v.to_bytes(2, "little", signed=True) for v in tvals(s, p + "weight")) + o += b"".join(v.to_bytes(8, "little", signed=True) for v in tvals(s, p + "mult") + tvals(s, p + "bias")) + elif kd == "igdn": + o.append(2) + o += tvals(s, p + "igdn")[0].to_bytes(4, "little") + o += b"".join(v.to_bytes(4, "little", signed=True) for v in tvals(s, p + "gamma")) + o += b"".join(v.to_bytes(8, "little", signed=True) for v in tvals(s, p + "beta")) + elif kd == "relu": + o.append(3) + else: + q, fr = tvals(s, p + "leaky") + o.append(4) + o += q.to_bytes(8, "little", signed=True) + fr.to_bytes(4, "little") + return bytes(o) + + +BINDINGS = {"wsr1/1": (wsr_from, wsr_to), "whm1/1": (whm_from, whm_to)} + + +def load(form, b): + """§10.3.""" + s = READ[form](b) + if WRITE[form](s) != b: + raise Refuse("header_not_canonical") + bname = s.meta.get("wai.binding", "") + want = profile_meta(bname, form) + if s.meta.get("wai.profile") != "wai-int/1" or set(s.meta) != set(want) or (form == "wai.prior.gguf" and s.meta.get("general.architecture") != "wai"): + raise Refuse("profile_absent") + if bname not in BINDINGS: + raise Refuse("binding_unknown", bname) + for name, (dt, _, _) in s.t.items(): + if (dt != "I32") if form == "wai.prior.nnc" else (dt not in INT_WIDTH): + raise Refuse("dtype_not_integer", name) + return bname, BINDINGS[bname][1](s, form) + + +def encode(binding, native, form): + s = BINDINGS[binding][0](native) + s.meta = profile_meta(binding, form) + return WRITE[form](s) + + +# ------------------------------------------------------------------ the corpus + +def run(corpus): + doc = json.load(open(os.path.join(corpus, "vectors.json"), encoding="utf-8")) + known = codes() + fails = [] + natives = {} + for nat in doc["natives"]: + b = open(os.path.join(corpus, nat["path"]), "rb").read() + if sha(b) != nat["sha256"]: + fails.append(f"native {nat['name']} does not match its pin") + natives[nat["name"]] = b + files = {} + for f in doc["files"]: + b = open(os.path.join(corpus, f["file"]), "rb").read() + if sha(b) != f["sha256"]: + fails.append(f"{f['file']} does not match its digest") + files[f["file"]] = b + listed = set(files) | {"vectors.json", "README.md", ".gitattributes"} + for root, _, fs in os.walk(corpus): + for x in fs: + rel = os.path.relpath(os.path.join(root, x), corpus).replace(os.sep, "/") + if rel not in listed: + fails.append(f"{rel} is not listed") + accepted = [] + for v in doc["vectors"]: + name, op, form = v["name"], v["op"], v["form"] + if v["outcome"] == "refuse" and v["code"] not in known: + fails.append(f"{name}: code {v['code']} is not in §13") + try: + if op == "encode": + native = natives[v["native"]] if "native" in v else files[v["native_file"]] + got = encode(v["binding"], native, form) + outcome = ("accept", got) + else: + b, native = load(form, files[v["file"]]) + outcome = ("accept", (b, native)) + except Refuse as r: + outcome = ("refuse", r.code) + if v["outcome"] == "refuse": + if outcome != ("refuse", v["code"]): + fails.append(f"{name}: wanted {v['code']}, got {outcome[0]} {outcome[1] if outcome[0] == 'refuse' else ''}") + elif outcome[0] != "accept": + fails.append(f"{name}: wanted accept, got {outcome[1]}") + elif op == "encode" and outcome[1] != files[v["file"]]: + fails.append(f"{name}: the encoded file differs from {v['file']}") + elif op == "load": + want_native = natives[v["native"]] + if outcome[1] != (v["binding"], want_native): + fails.append(f"{name}: loaded to another binding or other bytes") + else: + accepted.append((form, files[v["file"]], want_native)) + if set(known) - {v.get("code") for v in doc["vectors"]}: + fails.append(f"codes no vector gives: {sorted(set(known) - {v.get('code') for v in doc['vectors']})}") + # Mutations: one byte flipped at a few places of every accepted file. + caught = missed = 0 + for form, b, native in accepted: + for at in sorted({0, 7, len(b) // 3, len(b) // 2, len(b) - 1}): + m = bytearray(b) + m[at] ^= 0x01 + try: + _, got = load(form, bytes(m)) + ok = got != native + except Refuse: + ok = True + if ok: + caught += 1 + else: + missed += 1 + fails.append(f"a flip at byte {at} of a {form} file loaded to the same prior") + return fails, len(doc["vectors"]), caught + + +def main(): + if len(sys.argv) != 2: + print(__doc__) + sys.exit(2) + fails, n, caught = run(sys.argv[1]) + for f in fails: + print("FAIL", f) + if fails: + sys.exit(1) + print(f"prior-forms: every vector verifies ({n}), and every mutated copy is caught ({caught})") + + +if __name__ == "__main__": + main() diff --git a/wai/tools/nnc_interop_check.py b/wai/tools/nnc_interop_check.py new file mode 100644 index 000000000..88a383a2f --- /dev/null +++ b/wai/tools/nnc_interop_check.py @@ -0,0 +1,94 @@ +"""The NNC subset of extensions/prior-carriage.md §12, measured against an open implementation +of ISO/IEC 15938-17 (§12.4). Not part of the corpus and not run in CI: it runs that +implementation's own code, which this repository does not carry. + +For each prior of live-conformance/prior-forms/, it requires that the other implementation's +decoder reads our NNC file to the integers our safetensors file holds, that its encoder, +given those integers, writes data units byte-identical to ours, and that our reader +(prior_forms_verify.py) reads its bitstream to the same integers. + + python3 nnc_interop_check.py + +with the other implementation's compiled arithmetic coder importable (PYTHONPATH). The exact +setup used is in the corpus README. +""" +import json, os, struct, sys +import numpy as np +SRC, OURS, CORPUS = sys.argv[1], sys.argv[2], sys.argv[3] +sys.path.insert(0, SRC) +from nnc_core import coder # noqa: E402 (imports their compiled arithmetic coder) +from nnc_core.coder import baseline # noqa: E402 + +# Harness glue: their decoder keeps dq_flag only when the MPS signals quantisation, which an +# integer-only bitstream does not; give it the dict it writes 0 into. The bits are untouched. +_orig = baseline.decode + + +def _decode(decoder, approx_data, *a): + approx_data.setdefault("dq_flag", {}) + return _orig(decoder, approx_data, *a) + + +baseline.decode = _decode +sys.path.insert(0, OURS) +import prior_forms_verify as V # noqa: E402 + +NAMES = ["generic", "title", "seeded", "hyper_g_s", "hyper_h_s", "world_t", "world_g_s"] + + +def units(b): + out, at = [], 0 + while at < len(b): + size = (int.from_bytes(b[at:at + 2], "big") & 0x7FFF) if b[at] & 0x80 == 0 else (int.from_bytes(b[at:at + 4], "big") & 0x7FFFFFFF) + u = b[at:at + size] + out.append(u) + at += size + return out + + +def ndu_name(u): + off = 4 if u[0] & 0x80 == 0 else 6 # size field, then 1 header byte, then 1 byte of NDU flags + z = u.index(0, off) + return u[off:z].decode() + + +report = {} +all_ok = True +for n in NAMES: + ours_nnc = open(os.path.join(CORPUS, "files", n + ".nnc"), "rb").read() + st = V.st_read(open(os.path.join(CORPUS, "files", n + ".safetensors"), "rb").read()) + want = {k: np.array(V.ints_of(dt, data), dtype=np.int64).reshape(shape) for k, (dt, shape, data) in st.t.items()} + # B: their decoder on our bitstream + mi = {'parameter_type': {}, 'parameter_dimensions': {}, 'parameter_index': {}, 'block_identifier': {}, + 'topology_storage_format': None, 'topology_compression_format': None, + 'performance_maps': {"mps": {}, "lps": {}}, + 'performance_map_flags': {k: {} for k in ["mps_sparsification_flag", "lps_sparsification_flag", "mps_pruning_flag", "lps_pruning_flag", + "mps_unification_flag", "lps_unification_flag", "mps_decomposition_performance_map_flag", "lps_decomposition_performance_map_flag"]}} + dec = coder.decode(bytearray(ours_nnc), mi, {}) + got = dec["parameters"] + b_ok = set(got) == set(want) and all(np.array_equal(np.asarray(got[k], dtype=np.int64), want[k]) for k in want) + # C: their encoder on the same integers, unit by unit against ours + names = sorted(want, key=lambda x: x.encode()) + params = {k: want[k].astype(np.int32) for k in names} + model_info = {"parameter_index": {k: i for i, k in enumerate(names)}, "parameter_type": {k: "unspecified" for k in names}, + "parameter_dimensions": {k: list(params[k].shape) for k in names}, "block_identifier": {}, + "topology_storage_format": None} + approx = {"parameters": params, "approx_method": {k: "skip" for k in names}, "dq_flag": {k: 0 for k in names}, + "scan_order": {k: 0 for k in names if params[k].ndim > 1}, "compressed_parameter_types": {}} + theirs = bytes(coder.encode({"cabac_unary_length_minus1": 10, "param_opt_flag": 0}, model_info, approx)) + ours_ndus = {ndu_name(u): u for u in units(ours_nnc) if (u[2 if u[0] & 0x80 == 0 else 4] >> 2) == 5} + their_ndus = {ndu_name(u): u for u in units(theirs) if (u[2 if u[0] & 0x80 == 0 else 4] >> 2) == 5} + same = sorted(k for k in ours_ndus if their_ndus.get(k) == ours_ndus[k]) + c_ok = set(ours_ndus) == set(their_ndus) and len(same) == len(ours_ndus) + # D: our reader on their bitstream + try: + s = V.nnc_read(theirs) + d_ok = set(s.t) == set(want) and all(V.ints_of("I32", s.t[k][2]) == list(want[k].reshape(-1)) for k in want) + d_err = None + except V.Refuse as r: + d_ok, d_err = False, r.code + report[n] = {"their_decoder_reads_ours": b_ok, "data_units_identical": f"{len(same)} of {len(ours_ndus)}", "our_reader_reads_theirs": d_ok, + "our_reader_error": d_err, "elements": int(sum(v.size for v in want.values())), "their_bytes": len(theirs), "our_bytes": len(ours_nnc)} + all_ok = all_ok and b_ok and c_ok and d_ok +print(json.dumps(report, indent=1)) +print("ALL_OK" if all_ok else "MISMATCH") diff --git a/wai/wai-rs/src/strict_json.rs b/wai/wai-rs/src/strict_json.rs index 0e2e2e3a0..2cba3e188 100644 --- a/wai/wai-rs/src/strict_json.rs +++ b/wai/wai-rs/src/strict_json.rs @@ -113,7 +113,7 @@ impl P<'_> { } } fn value(&mut self, depth: usize) -> Result { - if depth > MAX_DEPTH { + if depth >= MAX_DEPTH { return self.err("nested too deeply"); } match self.b.get(self.i) { -- 2.54.0 (Apple Git-157) From 6daafc502c297ef5db4805f0dd027b9f93b126d3 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:43:34 -0400 Subject: [PATCH 010/242] =?UTF-8?q?wai:=20scene=20carriage=20=E2=80=94=20W?= =?UTF-8?q?AI=20worlds,=20audio=20scenes=20and=20timed=20media=20onto=20IS?= =?UTF-8?q?O/IEC=2023090-14=20scene=20description=20and=20a=20USD=20layer,?= =?UTF-8?q?=20both=20ways=20(work=20in=20progress)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wai-rs scene_map (feature scene_map): a bundle (a WWLD world, a WSCN audio scene, timed media items by content hash) exported as a glTF 2.0 document with MPEG_media, MPEG_buffer_circular, MPEG_accessor_timed, MPEG_audio_spatial and MPEG_texture_video, and as a USDA layer of the USD Core Specification v1.0.1 (prims, translate ops, time samples, asset references by urn:wai:asset), each carrying the bundle's bytes exactly; imports read a WAI-written document back to the same bundle (refused unless re-exporting gives the same bytes) and a foreign one to new WAI bytes, refused where a value would not carry exactly. Corpus generator wai_scene_vectors and tests/scene_map.rs. Co-Authored-By: Claude Opus 5.5 --- wai/live-conformance/scene/.gitattributes | 2 + wai/live-conformance/scene/README.md | 1 + .../scene/docs/carried_bad_hex.gltf | 1 + .../scene/docs/carried_bad_profile.usda | 70 + wai/live-conformance/scene/docs/changed.gltf | 1 + wai/live-conformance/scene/docs/changed.usda | 70 + .../scene/docs/foreign_asset_path.usda | 40 + .../scene/docs/foreign_audio.gltf | 1 + .../scene/docs/foreign_broken.usda | 39 + .../scene/docs/foreign_hoa.gltf | 1 + .../scene/docs/foreign_https.gltf | 1 + .../scene/docs/foreign_inexact.gltf | 1 + .../scene/docs/foreign_inexact.usda | 40 + .../scene/docs/foreign_layer.usda | 40 + .../scene/docs/foreign_layer_no_contract.usda | 40 + .../scene/docs/foreign_nodes.gltf | 1 + .../scene/docs/foreign_nodes_ballistic.gltf | 1 + .../scene/docs/foreign_nodes_no_contract.gltf | 1 + .../scene/docs/foreign_not_gltf2.gltf | 1 + .../scene/docs/foreign_not_json.gltf | 1 + .../scene/docs/foreign_over.usda | 40 + .../scene/docs/foreign_pregain.gltf | 1 + .../scene/docs/foreign_rotate.usda | 41 + .../scene/docs/foreign_rotation.gltf | 1 + .../scene/docs/foreign_step.gltf | 1 + .../scene/docs/foreign_time.gltf | 1 + .../scene/docs/foreign_variant.usda | 45 + .../scene/imported/foreign_audio_gltf.wscn | Bin 0 -> 401 bytes .../scene/imported/foreign_audio_gltf.wwld | Bin 0 -> 369 bytes .../scene/imported/foreign_layer_usda.wwld | Bin 0 -> 578 bytes .../scene/imported/foreign_nodes_gltf.wwld | Bin 0 -> 521 bytes .../scene/inputs/ballistic.wwld | Bin 0 -> 465 bytes .../scene/inputs/kinematic.wwld | Bin 0 -> 634 bytes .../scene/inputs/neg/descending.wscn | Bin 0 -> 312 bytes .../scene/inputs/neg/truncated.wscn | Bin 0 -> 369 bytes .../scene/inputs/neg/unordered.wwld | Bin 0 -> 634 bytes wai/live-conformance/scene/inputs/truss.wwld | Bin 0 -> 513 bytes wai/live-conformance/scene/out/ballistic.gltf | 1 + wai/live-conformance/scene/out/ballistic.usda | 23 + wai/live-conformance/scene/out/distance.gltf | 1 + wai/live-conformance/scene/out/distance.usda | 31 + .../scene/out/empty_scene.gltf | 1 + .../scene/out/empty_scene.usda | 12 + wai/live-conformance/scene/out/full.gltf | 1 + wai/live-conformance/scene/out/full.usda | 88 ++ wai/live-conformance/scene/out/kinematic.gltf | 1 + wai/live-conformance/scene/out/kinematic.usda | 70 + wai/live-conformance/scene/out/moving.gltf | 1 + wai/live-conformance/scene/out/moving.usda | 40 + wai/live-conformance/scene/out/replay.gltf | 1 + wai/live-conformance/scene/out/replay.usda | 12 + .../scene/out/stereo_pair.gltf | 1 + .../scene/out/stereo_pair.usda | 31 + wai/live-conformance/scene/out/truss.gltf | 1 + wai/live-conformance/scene/out/truss.usda | 27 + wai/live-conformance/scene/vectors.json | 1317 +++++++++++++++++ wai/wai-rs/src/bin/wai_prior_forms_vectors.rs | 2 +- wai/wai-rs/src/bin/wai_scene_vectors.rs | 78 + wai/wai-rs/src/scene_map.rs | 529 +++++++ wai/wai-rs/src/scene_map/corpus.rs | 415 ++++++ wai/wai-rs/src/scene_map/gltf.rs | 743 ++++++++++ wai/wai-rs/src/scene_map/num.rs | 193 +++ wai/wai-rs/src/scene_map/usda.rs | 840 +++++++++++ wai/wai-rs/tests/prior_wire.rs | 2 +- wai/wai-rs/tests/scene_map.rs | 41 + 65 files changed, 4984 insertions(+), 2 deletions(-) create mode 100644 wai/live-conformance/scene/.gitattributes create mode 100644 wai/live-conformance/scene/README.md create mode 100644 wai/live-conformance/scene/docs/carried_bad_hex.gltf create mode 100644 wai/live-conformance/scene/docs/carried_bad_profile.usda create mode 100644 wai/live-conformance/scene/docs/changed.gltf create mode 100644 wai/live-conformance/scene/docs/changed.usda create mode 100644 wai/live-conformance/scene/docs/foreign_asset_path.usda create mode 100644 wai/live-conformance/scene/docs/foreign_audio.gltf create mode 100644 wai/live-conformance/scene/docs/foreign_broken.usda create mode 100644 wai/live-conformance/scene/docs/foreign_hoa.gltf create mode 100644 wai/live-conformance/scene/docs/foreign_https.gltf create mode 100644 wai/live-conformance/scene/docs/foreign_inexact.gltf create mode 100644 wai/live-conformance/scene/docs/foreign_inexact.usda create mode 100644 wai/live-conformance/scene/docs/foreign_layer.usda create mode 100644 wai/live-conformance/scene/docs/foreign_layer_no_contract.usda create mode 100644 wai/live-conformance/scene/docs/foreign_nodes.gltf create mode 100644 wai/live-conformance/scene/docs/foreign_nodes_ballistic.gltf create mode 100644 wai/live-conformance/scene/docs/foreign_nodes_no_contract.gltf create mode 100644 wai/live-conformance/scene/docs/foreign_not_gltf2.gltf create mode 100644 wai/live-conformance/scene/docs/foreign_not_json.gltf create mode 100644 wai/live-conformance/scene/docs/foreign_over.usda create mode 100644 wai/live-conformance/scene/docs/foreign_pregain.gltf create mode 100644 wai/live-conformance/scene/docs/foreign_rotate.usda create mode 100644 wai/live-conformance/scene/docs/foreign_rotation.gltf create mode 100644 wai/live-conformance/scene/docs/foreign_step.gltf create mode 100644 wai/live-conformance/scene/docs/foreign_time.gltf create mode 100644 wai/live-conformance/scene/docs/foreign_variant.usda create mode 100644 wai/live-conformance/scene/imported/foreign_audio_gltf.wscn create mode 100644 wai/live-conformance/scene/imported/foreign_audio_gltf.wwld create mode 100644 wai/live-conformance/scene/imported/foreign_layer_usda.wwld create mode 100644 wai/live-conformance/scene/imported/foreign_nodes_gltf.wwld create mode 100644 wai/live-conformance/scene/inputs/ballistic.wwld create mode 100644 wai/live-conformance/scene/inputs/kinematic.wwld create mode 100644 wai/live-conformance/scene/inputs/neg/descending.wscn create mode 100644 wai/live-conformance/scene/inputs/neg/truncated.wscn create mode 100644 wai/live-conformance/scene/inputs/neg/unordered.wwld create mode 100644 wai/live-conformance/scene/inputs/truss.wwld create mode 100644 wai/live-conformance/scene/out/ballistic.gltf create mode 100644 wai/live-conformance/scene/out/ballistic.usda create mode 100644 wai/live-conformance/scene/out/distance.gltf create mode 100644 wai/live-conformance/scene/out/distance.usda create mode 100644 wai/live-conformance/scene/out/empty_scene.gltf create mode 100644 wai/live-conformance/scene/out/empty_scene.usda create mode 100644 wai/live-conformance/scene/out/full.gltf create mode 100644 wai/live-conformance/scene/out/full.usda create mode 100644 wai/live-conformance/scene/out/kinematic.gltf create mode 100644 wai/live-conformance/scene/out/kinematic.usda create mode 100644 wai/live-conformance/scene/out/moving.gltf create mode 100644 wai/live-conformance/scene/out/moving.usda create mode 100644 wai/live-conformance/scene/out/replay.gltf create mode 100644 wai/live-conformance/scene/out/replay.usda create mode 100644 wai/live-conformance/scene/out/stereo_pair.gltf create mode 100644 wai/live-conformance/scene/out/stereo_pair.usda create mode 100644 wai/live-conformance/scene/out/truss.gltf create mode 100644 wai/live-conformance/scene/out/truss.usda create mode 100644 wai/live-conformance/scene/vectors.json create mode 100644 wai/wai-rs/src/bin/wai_scene_vectors.rs create mode 100644 wai/wai-rs/src/scene_map.rs create mode 100644 wai/wai-rs/src/scene_map/corpus.rs create mode 100644 wai/wai-rs/src/scene_map/gltf.rs create mode 100644 wai/wai-rs/src/scene_map/num.rs create mode 100644 wai/wai-rs/src/scene_map/usda.rs create mode 100644 wai/wai-rs/tests/scene_map.rs diff --git a/wai/live-conformance/scene/.gitattributes b/wai/live-conformance/scene/.gitattributes new file mode 100644 index 000000000..eff1df73a --- /dev/null +++ b/wai/live-conformance/scene/.gitattributes @@ -0,0 +1,2 @@ +# Hashed files: a line-ending conversion on checkout would change every digest. +* -text diff --git a/wai/live-conformance/scene/README.md b/wai/live-conformance/scene/README.md new file mode 100644 index 000000000..b5c5e504d --- /dev/null +++ b/wai/live-conformance/scene/README.md @@ -0,0 +1 @@ +# live-conformance/scene diff --git a/wai/live-conformance/scene/docs/carried_bad_hex.gltf b/wai/live-conformance/scene/docs/carried_bad_hex.gltf new file mode 100644 index 000000000..d21dc4d47 --- /dev/null +++ b/wai/live-conformance/scene/docs/carried_bad_hex.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0","generator":"wai-scene/1"},"extensionsUsed":["MPEG_accessor_timed","MPEG_buffer_circular","MPEG_media","MPEG_texture_video"],"extensions":{"MPEG_media":{"media":[{"name":"screen","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e","mimeType":"application/wai; codecs=\"wai1.video.av1\""}]},{"name":"ambience","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4","mimeType":"application/wai; codecs=\"wai1.audio.flac\""}]}]}},"scene":0,"scenes":[{"nodes":[0,1,2]}],"nodes":[{"name":"node-227786eee6f53904e1bbd79468fff7c2","translation":[0.5,-2.25,0]},{"name":"node-60f27ae393f098439aee225f345f4081","translation":[1,2,3]},{"name":"node-7b8a8e8142b739d33850b44c3cf75504","translation":[0.3333333432674408,10,-7.75]}],"buffers":[{"byteLength":6912,"extensions":{"MPEG_buffer_circular":{"media":0}}}],"bufferViews":[{"buffer":0,"byteLength":6912}],"accessors":[{"bufferView":0,"componentType":5121,"normalized":true,"count":2304,"type":"VEC3","extensions":{"MPEG_accessor_timed":{"immutable":true}}}],"textures":[{"extensions":{"MPEG_texture_video":{"accessor":0,"width":64,"height":36,"format":"RGB"}}}],"extras":{"wai":{"profile":"wai-scene/1","world":"z7574c4404000100000000f90000000200010000e800000003e8010000630000000450020000000000007b22636865636b706f696e745f6576657279223a3630302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030227d2c2273696d223a227761692e776f726c642e73696d2e6b696e656d6174696330222c227469636b5f72617465223a36307d0000000000000003000000227786eee6f53904e1bbd79468fff7c2380000000000008000000000000000c0fdffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000060f27ae393f098439aee225f345f40813800000000000000010000000000000002000000000000000300000000000000000000000000000000000000000000000000000000000000000000007b8a8e8142b739d33850b44c3cf75504380000005555555500000000000000000a00000000000040f8ffffff000000000100000000000000000000000000000000000000070000000000000002000000b88d2383bd7689e0225c0448fbd91b58e13eead2f634d82aac4ce603c47e65330008000000637562652e676c6208340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4010d000000616d6269656e63652e666c61630000000000","media":"[{\"name\":\"screen\",\"asset\":\"739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e\",\"capability\":\"wai.video.av1\",\"width\":64,\"height\":36},{\"name\":\"ambience\",\"asset\":\"08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4\",\"capability\":\"wai.audio.flac\"}]"}}} diff --git a/wai/live-conformance/scene/docs/carried_bad_profile.usda b/wai/live-conformance/scene/docs/carried_bad_profile.usda new file mode 100644 index 000000000..d1cebd788 --- /dev/null +++ b/wai/live-conformance/scene/docs/carried_bad_profile.usda @@ -0,0 +1,70 @@ +#usda 1.0 +( + customLayerData = { + string "wai:media" = "[{\"name\":\"screen\",\"asset\":\"739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e\",\"capability\":\"wai.video.av1\",\"width\":64,\"height\":36},{\"name\":\"ambience\",\"asset\":\"08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4\",\"capability\":\"wai.audio.flac\"}]" + string "wai:profile" = "wai-scene/9" + string "wai:world" = "57574c4404000100000000f90000000200010000e800000003e8010000630000000450020000000000007b22636865636b706f696e745f6576657279223a3630302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030227d2c2273696d223a227761692e776f726c642e73696d2e6b696e656d6174696330222c227469636b5f72617465223a36307d0000000000000003000000227786eee6f53904e1bbd79468fff7c2380000000000008000000000000000c0fdffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000060f27ae393f098439aee225f345f40813800000000000000010000000000000002000000000000000300000000000000000000000000000000000000000000000000000000000000000000007b8a8e8142b739d33850b44c3cf75504380000005555555500000000000000000a00000000000040f8ffffff000000000100000000000000000000000000000000000000070000000000000002000000b88d2383bd7689e0225c0448fbd91b58e13eead2f634d82aac4ce603c47e65330008000000637562652e676c6208340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4010d000000616d6269656e63652e666c61630000000000" + } + defaultPrim = "World" +) + +def Xform "World" +{ + def Xform "node_227786eee6f53904e1bbd79468fff7c2" + { + double3 xformOp:translate = (0.5, -2.25, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "node_60f27ae393f098439aee225f345f4081" + { + double3 xformOp:translate = (1, 2, 3) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "node_7b8a8e8142b739d33850b44c3cf75504" + { + double3 xformOp:translate = (0.3333333332557231, 10, -7.75) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Scope "Assets" + { + def "asset_0" ( + prepend references = @urn:wai:asset:b88d2383bd7689e0225c0448fbd91b58e13eead2f634d82aac4ce603c47e6533@ + ) + { + custom string wai:name = "cube.glb" + custom int wai:role = 0 + } + + def "asset_1" ( + prepend references = @urn:wai:asset:08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4@ + ) + { + custom string wai:name = "ambience.flac" + custom int wai:role = 1 + } + } + + def Scope "Media" + { + def "media_0" ( + prepend references = @urn:wai:asset:739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e@ + ) + { + custom string wai:capability = "wai.video.av1" + custom int wai:height = 36 + custom string wai:name = "screen" + custom int wai:width = 64 + } + + def "media_1" ( + prepend references = @urn:wai:asset:08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4@ + ) + { + custom string wai:capability = "wai.audio.flac" + custom string wai:name = "ambience" + } + } +} diff --git a/wai/live-conformance/scene/docs/changed.gltf b/wai/live-conformance/scene/docs/changed.gltf new file mode 100644 index 000000000..de17b1e88 --- /dev/null +++ b/wai/live-conformance/scene/docs/changed.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0","generator":"wai-scene/1"},"extensionsUsed":["MPEG_accessor_timed","MPEG_buffer_circular","MPEG_media","MPEG_texture_video"],"extensions":{"MPEG_media":{"media":[{"name":"screen","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e","mimeType":"application/wai; codecs=\"wai1.video.av1\""}]},{"name":"ambience","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4","mimeType":"application/wai; codecs=\"wai1.audio.flac\""}]}]}},"scene":0,"scenes":[{"nodes":[0,1,2]}],"nodes":[{"name":"node-227786eee6f53904e1bbd79468fff7c2","translation":[0.5,-2.25,0]},{"name":"node-60f27ae393f098439aee225f345f4081","translation":[1,2,4]},{"name":"node-7b8a8e8142b739d33850b44c3cf75504","translation":[0.3333333432674408,10,-7.75]}],"buffers":[{"byteLength":6912,"extensions":{"MPEG_buffer_circular":{"media":0}}}],"bufferViews":[{"buffer":0,"byteLength":6912}],"accessors":[{"bufferView":0,"componentType":5121,"normalized":true,"count":2304,"type":"VEC3","extensions":{"MPEG_accessor_timed":{"immutable":true}}}],"textures":[{"extensions":{"MPEG_texture_video":{"accessor":0,"width":64,"height":36,"format":"RGB"}}}],"extras":{"wai":{"profile":"wai-scene/1","world":"57574c4404000100000000f90000000200010000e800000003e8010000630000000450020000000000007b22636865636b706f696e745f6576657279223a3630302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030227d2c2273696d223a227761692e776f726c642e73696d2e6b696e656d6174696330222c227469636b5f72617465223a36307d0000000000000003000000227786eee6f53904e1bbd79468fff7c2380000000000008000000000000000c0fdffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000060f27ae393f098439aee225f345f40813800000000000000010000000000000002000000000000000300000000000000000000000000000000000000000000000000000000000000000000007b8a8e8142b739d33850b44c3cf75504380000005555555500000000000000000a00000000000040f8ffffff000000000100000000000000000000000000000000000000070000000000000002000000b88d2383bd7689e0225c0448fbd91b58e13eead2f634d82aac4ce603c47e65330008000000637562652e676c6208340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4010d000000616d6269656e63652e666c61630000000000","media":"[{\"name\":\"screen\",\"asset\":\"739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e\",\"capability\":\"wai.video.av1\",\"width\":64,\"height\":36},{\"name\":\"ambience\",\"asset\":\"08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4\",\"capability\":\"wai.audio.flac\"}]"}}} diff --git a/wai/live-conformance/scene/docs/changed.usda b/wai/live-conformance/scene/docs/changed.usda new file mode 100644 index 000000000..64080575c --- /dev/null +++ b/wai/live-conformance/scene/docs/changed.usda @@ -0,0 +1,70 @@ +#usda 1.0 +( + customLayerData = { + string "wai:media" = "[{\"name\":\"screen\",\"asset\":\"739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e\",\"capability\":\"wai.video.av1\",\"width\":64,\"height\":36},{\"name\":\"ambience\",\"asset\":\"08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4\",\"capability\":\"wai.audio.flac\"}]" + string "wai:profile" = "wai-scene/1" + string "wai:world" = "57574c4404000100000000f90000000200010000e800000003e8010000630000000450020000000000007b22636865636b706f696e745f6576657279223a3630302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030227d2c2273696d223a227761692e776f726c642e73696d2e6b696e656d6174696330222c227469636b5f72617465223a36307d0000000000000003000000227786eee6f53904e1bbd79468fff7c2380000000000008000000000000000c0fdffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000060f27ae393f098439aee225f345f40813800000000000000010000000000000002000000000000000300000000000000000000000000000000000000000000000000000000000000000000007b8a8e8142b739d33850b44c3cf75504380000005555555500000000000000000a00000000000040f8ffffff000000000100000000000000000000000000000000000000070000000000000002000000b88d2383bd7689e0225c0448fbd91b58e13eead2f634d82aac4ce603c47e65330008000000637562652e676c6208340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4010d000000616d6269656e63652e666c61630000000000" + } + defaultPrim = "World" +) + +def Xform "World" +{ + def Xform "node_227786eee6f53904e1bbd79468fff7c2" + { + double3 xformOp:translate = (0.5, -2.25, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "node_60f27ae393f098439aee225f345f4081" + { + double3 xformOp:translate = (1, 2, 4) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "node_7b8a8e8142b739d33850b44c3cf75504" + { + double3 xformOp:translate = (0.3333333332557231, 10, -7.75) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Scope "Assets" + { + def "asset_0" ( + prepend references = @urn:wai:asset:b88d2383bd7689e0225c0448fbd91b58e13eead2f634d82aac4ce603c47e6533@ + ) + { + custom string wai:name = "cube.glb" + custom int wai:role = 0 + } + + def "asset_1" ( + prepend references = @urn:wai:asset:08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4@ + ) + { + custom string wai:name = "ambience.flac" + custom int wai:role = 1 + } + } + + def Scope "Media" + { + def "media_0" ( + prepend references = @urn:wai:asset:739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e@ + ) + { + custom string wai:capability = "wai.video.av1" + custom int wai:height = 36 + custom string wai:name = "screen" + custom int wai:width = 64 + } + + def "media_1" ( + prepend references = @urn:wai:asset:08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4@ + ) + { + custom string wai:capability = "wai.audio.flac" + custom string wai:name = "ambience" + } + } +} diff --git a/wai/live-conformance/scene/docs/foreign_asset_path.usda b/wai/live-conformance/scene/docs/foreign_asset_path.usda new file mode 100644 index 000000000..449c54e41 --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_asset_path.usda @@ -0,0 +1,40 @@ +#usda 1.0 +( + "A layer written outside WAI" + defaultPrim = "Set" + metersPerUnit = 1 + upAxis = "Y" +) + +def Xform "Set" ( + kind = "group" +) +{ + double3 xformOp:translate = (10, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Pillar" # a pillar + { + double3 xformOp:translate = (0.5, 3, -0.25) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Cap" + { + double3 xformOp:translate = (0, 1.5, 0) + } + } + + def Mesh "Floor" + { + int[] faceVertexCounts = [3] + int[] faceVertexIndices = [0, 1, 2] + point3f[] points = [(0, 0, 0), (1, 0, 0), (0, 0, 1)] + } + + /* an asset by its content hash */ + def "Statue" ( + prepend references = @./statue.usda@ + ) + { + } +} diff --git a/wai/live-conformance/scene/docs/foreign_audio.gltf b/wai/live-conformance/scene/docs/foreign_audio.gltf new file mode 100644 index 000000000..05d26934c --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_audio.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0"},"extensionsUsed":["MPEG_accessor_timed","MPEG_audio_spatial","MPEG_buffer_circular","MPEG_media","MPEG_texture_video"],"extensions":{"MPEG_media":{"media":[{"name":"voice","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:0404040404040404040404040404040404040404040404040404040404040404","mimeType":"application/wai; codecs=\"wai1.audio.opus\""}]},{"name":"screen","startTime":0,"alternatives":[{"uri":"https://example.invalid/screen.mp4","mimeType":"video/mp4"},{"uri":"urn:wai:asset:739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e","mimeType":"application/wai; codecs=\"wai1.video.av1\""}]}]}},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"name":"room","translation":[0,1,0],"children":[1]},{"name":"speaker","translation":[-2,0,-1],"extensions":{"MPEG_audio_spatial":{"sources":[{"id":0,"type":"Object","accessors":[2]}]}}}],"buffers":[{"byteLength":48,"uri":"data:application/octet-stream;base64,AAAAAAAAAD8AAIA/AAAAwAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAQAAAAAAAAIC/"},{"byteLength":4096,"extensions":{"MPEG_buffer_circular":{"media":0}}},{"byteLength":6912,"extensions":{"MPEG_buffer_circular":{"media":1}}}],"bufferViews":[{"buffer":0,"byteLength":12},{"buffer":0,"byteOffset":12,"byteLength":36},{"buffer":1,"byteLength":4096},{"buffer":2,"byteLength":6912}],"accessors":[{"bufferView":0,"componentType":5126,"count":3,"type":"SCALAR","min":[0],"max":[1]},{"bufferView":1,"componentType":5126,"count":3,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":1024,"type":"SCALAR","extensions":{"MPEG_accessor_timed":{"immutable":true}}},{"bufferView":3,"componentType":5121,"normalized":true,"count":2304,"type":"VEC3","extensions":{"MPEG_accessor_timed":{"immutable":true}}}],"animations":[{"channels":[{"sampler":0,"target":{"node":1,"path":"translation"}}],"samplers":[{"input":0,"interpolation":"LINEAR","output":1}]}],"textures":[{"extensions":{"MPEG_texture_video":{"accessor":3,"width":48,"height":16,"format":"RGB"}}}]} \ No newline at end of file diff --git a/wai/live-conformance/scene/docs/foreign_broken.usda b/wai/live-conformance/scene/docs/foreign_broken.usda new file mode 100644 index 000000000..58ed57a17 --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_broken.usda @@ -0,0 +1,39 @@ +#usda 1.0 +( + "A layer written outside WAI" + defaultPrim = "Set" + metersPerUnit = 1 + upAxis = "Y" +) + +def Xform "Set" ( + kind = "group" +) +{ + double3 xformOp:translate = (10, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Pillar" # a pillar + { + double3 xformOp:translate = (0.5, 3, -0.25) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Cap" + { + double3 xformOp:translate = (0, 1.5, 0) + } + } + + def Mesh "Floor" + { + int[] faceVertexCounts = [3] + int[] faceVertexIndices = [0, 1, 2] + point3f[] points = [(0, 0, 0), (1, 0, 0), (0, 0, 1)] + } + + /* an asset by its content hash */ + def "Statue" ( + prepend references = @urn:wai:asset:f3fdba94a88739184ad0f417656a62214dd1aa7138d8b21e3efabe4b38034461@ + ) + { + } \ No newline at end of file diff --git a/wai/live-conformance/scene/docs/foreign_hoa.gltf b/wai/live-conformance/scene/docs/foreign_hoa.gltf new file mode 100644 index 000000000..222baf52f --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_hoa.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0"},"extensionsUsed":["MPEG_accessor_timed","MPEG_audio_spatial","MPEG_buffer_circular","MPEG_media","MPEG_texture_video"],"extensions":{"MPEG_media":{"media":[{"name":"voice","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:0404040404040404040404040404040404040404040404040404040404040404","mimeType":"application/wai; codecs=\"wai1.audio.opus\""}]},{"name":"screen","startTime":0,"alternatives":[{"uri":"https://example.invalid/screen.mp4","mimeType":"video/mp4"},{"uri":"urn:wai:asset:739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e","mimeType":"application/wai; codecs=\"wai1.video.av1\""}]}]}},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"name":"room","translation":[0,1,0],"children":[1]},{"name":"speaker","translation":[-2,0,-1],"extensions":{"MPEG_audio_spatial":{"sources":[{"id":0,"type":"HOA","accessors":[2]}]}}}],"buffers":[{"byteLength":48,"uri":"data:application/octet-stream;base64,AAAAAAAAAD8AAIA/AAAAwAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAQAAAAAAAAIC/"},{"byteLength":4096,"extensions":{"MPEG_buffer_circular":{"media":0}}},{"byteLength":6912,"extensions":{"MPEG_buffer_circular":{"media":1}}}],"bufferViews":[{"buffer":0,"byteLength":12},{"buffer":0,"byteOffset":12,"byteLength":36},{"buffer":1,"byteLength":4096},{"buffer":2,"byteLength":6912}],"accessors":[{"bufferView":0,"componentType":5126,"count":3,"type":"SCALAR","min":[0],"max":[1]},{"bufferView":1,"componentType":5126,"count":3,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":1024,"type":"SCALAR","extensions":{"MPEG_accessor_timed":{"immutable":true}}},{"bufferView":3,"componentType":5121,"normalized":true,"count":2304,"type":"VEC3","extensions":{"MPEG_accessor_timed":{"immutable":true}}}],"animations":[{"channels":[{"sampler":0,"target":{"node":1,"path":"translation"}}],"samplers":[{"input":0,"interpolation":"LINEAR","output":1}]}],"textures":[{"extensions":{"MPEG_texture_video":{"accessor":3,"width":48,"height":16,"format":"RGB"}}}]} \ No newline at end of file diff --git a/wai/live-conformance/scene/docs/foreign_https.gltf b/wai/live-conformance/scene/docs/foreign_https.gltf new file mode 100644 index 000000000..9beef6df5 --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_https.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0"},"extensionsUsed":["MPEG_accessor_timed","MPEG_audio_spatial","MPEG_buffer_circular","MPEG_media","MPEG_texture_video"],"extensions":{"MPEG_media":{"media":[{"name":"voice","autoplay":true,"alternatives":[{"uri":"https://example.invalid/voice.ogg","mimeType":"audio/ogg"}]},{"name":"screen","startTime":0,"alternatives":[{"uri":"https://example.invalid/screen.mp4","mimeType":"video/mp4"},{"uri":"urn:wai:asset:739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e","mimeType":"application/wai; codecs=\"wai1.video.av1\""}]}]}},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"name":"room","translation":[0,1,0],"children":[1]},{"name":"speaker","translation":[-2,0,-1],"extensions":{"MPEG_audio_spatial":{"sources":[{"id":0,"type":"Object","accessors":[2]}]}}}],"buffers":[{"byteLength":48,"uri":"data:application/octet-stream;base64,AAAAAAAAAD8AAIA/AAAAwAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAQAAAAAAAAIC/"},{"byteLength":4096,"extensions":{"MPEG_buffer_circular":{"media":0}}},{"byteLength":6912,"extensions":{"MPEG_buffer_circular":{"media":1}}}],"bufferViews":[{"buffer":0,"byteLength":12},{"buffer":0,"byteOffset":12,"byteLength":36},{"buffer":1,"byteLength":4096},{"buffer":2,"byteLength":6912}],"accessors":[{"bufferView":0,"componentType":5126,"count":3,"type":"SCALAR","min":[0],"max":[1]},{"bufferView":1,"componentType":5126,"count":3,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":1024,"type":"SCALAR","extensions":{"MPEG_accessor_timed":{"immutable":true}}},{"bufferView":3,"componentType":5121,"normalized":true,"count":2304,"type":"VEC3","extensions":{"MPEG_accessor_timed":{"immutable":true}}}],"animations":[{"channels":[{"sampler":0,"target":{"node":1,"path":"translation"}}],"samplers":[{"input":0,"interpolation":"LINEAR","output":1}]}],"textures":[{"extensions":{"MPEG_texture_video":{"accessor":3,"width":48,"height":16,"format":"RGB"}}}]} \ No newline at end of file diff --git a/wai/live-conformance/scene/docs/foreign_inexact.gltf b/wai/live-conformance/scene/docs/foreign_inexact.gltf new file mode 100644 index 000000000..f184793b2 --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_inexact.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0,3]}],"nodes":[{"name":"base","translation":[1,0,-2],"children":[1,2]},{"name":"lamp","translation":[0.5,1e-10,0]},{"name":"crate","mesh":0,"translation":[-1.25,0,0.75]},{"name":"eye","camera":0,"translation":[0,1.5,4]}],"meshes":[{"primitives":[{"attributes":{"POSITION":0}}]}],"accessors":[{"componentType":5126,"count":3,"type":"VEC3","min":[0,0,0],"max":[1,1,0]}],"cameras":[{"type":"perspective","perspective":{"yfov":1,"znear":0.1}}]} \ No newline at end of file diff --git a/wai/live-conformance/scene/docs/foreign_inexact.usda b/wai/live-conformance/scene/docs/foreign_inexact.usda new file mode 100644 index 000000000..962b1be9f --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_inexact.usda @@ -0,0 +1,40 @@ +#usda 1.0 +( + "A layer written outside WAI" + defaultPrim = "Set" + metersPerUnit = 1 + upAxis = "Y" +) + +def Xform "Set" ( + kind = "group" +) +{ + double3 xformOp:translate = (10, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Pillar" # a pillar + { + double3 xformOp:translate = (0.5, 3, -0.25) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Cap" + { + double3 xformOp:translate = (0, 0.1, 0) + } + } + + def Mesh "Floor" + { + int[] faceVertexCounts = [3] + int[] faceVertexIndices = [0, 1, 2] + point3f[] points = [(0, 0, 0), (1, 0, 0), (0, 0, 1)] + } + + /* an asset by its content hash */ + def "Statue" ( + prepend references = @urn:wai:asset:f3fdba94a88739184ad0f417656a62214dd1aa7138d8b21e3efabe4b38034461@ + ) + { + } +} diff --git a/wai/live-conformance/scene/docs/foreign_layer.usda b/wai/live-conformance/scene/docs/foreign_layer.usda new file mode 100644 index 000000000..1727ebf3b --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_layer.usda @@ -0,0 +1,40 @@ +#usda 1.0 +( + "A layer written outside WAI" + defaultPrim = "Set" + metersPerUnit = 1 + upAxis = "Y" +) + +def Xform "Set" ( + kind = "group" +) +{ + double3 xformOp:translate = (10, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Pillar" # a pillar + { + double3 xformOp:translate = (0.5, 3, -0.25) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Cap" + { + double3 xformOp:translate = (0, 1.5, 0) + } + } + + def Mesh "Floor" + { + int[] faceVertexCounts = [3] + int[] faceVertexIndices = [0, 1, 2] + point3f[] points = [(0, 0, 0), (1, 0, 0), (0, 0, 1)] + } + + /* an asset by its content hash */ + def "Statue" ( + prepend references = @urn:wai:asset:f3fdba94a88739184ad0f417656a62214dd1aa7138d8b21e3efabe4b38034461@ + ) + { + } +} diff --git a/wai/live-conformance/scene/docs/foreign_layer_no_contract.usda b/wai/live-conformance/scene/docs/foreign_layer_no_contract.usda new file mode 100644 index 000000000..1727ebf3b --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_layer_no_contract.usda @@ -0,0 +1,40 @@ +#usda 1.0 +( + "A layer written outside WAI" + defaultPrim = "Set" + metersPerUnit = 1 + upAxis = "Y" +) + +def Xform "Set" ( + kind = "group" +) +{ + double3 xformOp:translate = (10, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Pillar" # a pillar + { + double3 xformOp:translate = (0.5, 3, -0.25) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Cap" + { + double3 xformOp:translate = (0, 1.5, 0) + } + } + + def Mesh "Floor" + { + int[] faceVertexCounts = [3] + int[] faceVertexIndices = [0, 1, 2] + point3f[] points = [(0, 0, 0), (1, 0, 0), (0, 0, 1)] + } + + /* an asset by its content hash */ + def "Statue" ( + prepend references = @urn:wai:asset:f3fdba94a88739184ad0f417656a62214dd1aa7138d8b21e3efabe4b38034461@ + ) + { + } +} diff --git a/wai/live-conformance/scene/docs/foreign_nodes.gltf b/wai/live-conformance/scene/docs/foreign_nodes.gltf new file mode 100644 index 000000000..0fd0ae040 --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_nodes.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0,3]}],"nodes":[{"name":"base","translation":[1,0,-2],"children":[1,2]},{"name":"lamp","translation":[0.5,2,0]},{"name":"crate","mesh":0,"translation":[-1.25,0,0.75]},{"name":"eye","camera":0,"translation":[0,1.5,4]}],"meshes":[{"primitives":[{"attributes":{"POSITION":0}}]}],"accessors":[{"componentType":5126,"count":3,"type":"VEC3","min":[0,0,0],"max":[1,1,0]}],"cameras":[{"type":"perspective","perspective":{"yfov":1,"znear":0.1}}]} \ No newline at end of file diff --git a/wai/live-conformance/scene/docs/foreign_nodes_ballistic.gltf b/wai/live-conformance/scene/docs/foreign_nodes_ballistic.gltf new file mode 100644 index 000000000..0fd0ae040 --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_nodes_ballistic.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0,3]}],"nodes":[{"name":"base","translation":[1,0,-2],"children":[1,2]},{"name":"lamp","translation":[0.5,2,0]},{"name":"crate","mesh":0,"translation":[-1.25,0,0.75]},{"name":"eye","camera":0,"translation":[0,1.5,4]}],"meshes":[{"primitives":[{"attributes":{"POSITION":0}}]}],"accessors":[{"componentType":5126,"count":3,"type":"VEC3","min":[0,0,0],"max":[1,1,0]}],"cameras":[{"type":"perspective","perspective":{"yfov":1,"znear":0.1}}]} \ No newline at end of file diff --git a/wai/live-conformance/scene/docs/foreign_nodes_no_contract.gltf b/wai/live-conformance/scene/docs/foreign_nodes_no_contract.gltf new file mode 100644 index 000000000..0fd0ae040 --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_nodes_no_contract.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0,3]}],"nodes":[{"name":"base","translation":[1,0,-2],"children":[1,2]},{"name":"lamp","translation":[0.5,2,0]},{"name":"crate","mesh":0,"translation":[-1.25,0,0.75]},{"name":"eye","camera":0,"translation":[0,1.5,4]}],"meshes":[{"primitives":[{"attributes":{"POSITION":0}}]}],"accessors":[{"componentType":5126,"count":3,"type":"VEC3","min":[0,0,0],"max":[1,1,0]}],"cameras":[{"type":"perspective","perspective":{"yfov":1,"znear":0.1}}]} \ No newline at end of file diff --git a/wai/live-conformance/scene/docs/foreign_not_gltf2.gltf b/wai/live-conformance/scene/docs/foreign_not_gltf2.gltf new file mode 100644 index 000000000..801efb50f --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_not_gltf2.gltf @@ -0,0 +1 @@ +{"asset":{"version":"1.0"},"scene":0,"scenes":[{"nodes":[0,3]}],"nodes":[{"name":"base","translation":[1,0,-2],"children":[1,2]},{"name":"lamp","translation":[0.5,2,0]},{"name":"crate","mesh":0,"translation":[-1.25,0,0.75]},{"name":"eye","camera":0,"translation":[0,1.5,4]}],"meshes":[{"primitives":[{"attributes":{"POSITION":0}}]}],"accessors":[{"componentType":5126,"count":3,"type":"VEC3","min":[0,0,0],"max":[1,1,0]}],"cameras":[{"type":"perspective","perspective":{"yfov":1,"znear":0.1}}]} \ No newline at end of file diff --git a/wai/live-conformance/scene/docs/foreign_not_json.gltf b/wai/live-conformance/scene/docs/foreign_not_json.gltf new file mode 100644 index 000000000..9904df764 --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_not_json.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0,3]}],"nodes":[{"name":"base","translation":[1,0,-2],"children":[1,2]},{"name":"lamp","translation":[0.5,2,0]},{"name":"crate","mesh":0,"translation":[-1.25,0,0.75]},{"name":"eye","camera": \ No newline at end of file diff --git a/wai/live-conformance/scene/docs/foreign_over.usda b/wai/live-conformance/scene/docs/foreign_over.usda new file mode 100644 index 000000000..66ff655ca --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_over.usda @@ -0,0 +1,40 @@ +#usda 1.0 +( + "A layer written outside WAI" + defaultPrim = "Set" + metersPerUnit = 1 + upAxis = "Y" +) + +def Xform "Set" ( + kind = "group" +) +{ + double3 xformOp:translate = (10, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Pillar" # a pillar + { + double3 xformOp:translate = (0.5, 3, -0.25) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Cap" + { + double3 xformOp:translate = (0, 1.5, 0) + } + } + + over "Floor" + { + int[] faceVertexCounts = [3] + int[] faceVertexIndices = [0, 1, 2] + point3f[] points = [(0, 0, 0), (1, 0, 0), (0, 0, 1)] + } + + /* an asset by its content hash */ + def "Statue" ( + prepend references = @urn:wai:asset:f3fdba94a88739184ad0f417656a62214dd1aa7138d8b21e3efabe4b38034461@ + ) + { + } +} diff --git a/wai/live-conformance/scene/docs/foreign_pregain.gltf b/wai/live-conformance/scene/docs/foreign_pregain.gltf new file mode 100644 index 000000000..f3b37af28 --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_pregain.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0"},"extensionsUsed":["MPEG_accessor_timed","MPEG_audio_spatial","MPEG_buffer_circular","MPEG_media","MPEG_texture_video"],"extensions":{"MPEG_media":{"media":[{"name":"voice","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:0404040404040404040404040404040404040404040404040404040404040404","mimeType":"application/wai; codecs=\"wai1.audio.opus\""}]},{"name":"screen","startTime":0,"alternatives":[{"uri":"https://example.invalid/screen.mp4","mimeType":"video/mp4"},{"uri":"urn:wai:asset:739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e","mimeType":"application/wai; codecs=\"wai1.video.av1\""}]}]}},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"name":"room","translation":[0,1,0],"children":[1]},{"name":"speaker","translation":[-2,0,-1],"extensions":{"MPEG_audio_spatial":{"sources":[{"id":0,"type":"Object","accessors":[2],"pregain":6}]}}}],"buffers":[{"byteLength":48,"uri":"data:application/octet-stream;base64,AAAAAAAAAD8AAIA/AAAAwAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAQAAAAAAAAIC/"},{"byteLength":4096,"extensions":{"MPEG_buffer_circular":{"media":0}}},{"byteLength":6912,"extensions":{"MPEG_buffer_circular":{"media":1}}}],"bufferViews":[{"buffer":0,"byteLength":12},{"buffer":0,"byteOffset":12,"byteLength":36},{"buffer":1,"byteLength":4096},{"buffer":2,"byteLength":6912}],"accessors":[{"bufferView":0,"componentType":5126,"count":3,"type":"SCALAR","min":[0],"max":[1]},{"bufferView":1,"componentType":5126,"count":3,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":1024,"type":"SCALAR","extensions":{"MPEG_accessor_timed":{"immutable":true}}},{"bufferView":3,"componentType":5121,"normalized":true,"count":2304,"type":"VEC3","extensions":{"MPEG_accessor_timed":{"immutable":true}}}],"animations":[{"channels":[{"sampler":0,"target":{"node":1,"path":"translation"}}],"samplers":[{"input":0,"interpolation":"LINEAR","output":1}]}],"textures":[{"extensions":{"MPEG_texture_video":{"accessor":3,"width":48,"height":16,"format":"RGB"}}}]} \ No newline at end of file diff --git a/wai/live-conformance/scene/docs/foreign_rotate.usda b/wai/live-conformance/scene/docs/foreign_rotate.usda new file mode 100644 index 000000000..31630a5fa --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_rotate.usda @@ -0,0 +1,41 @@ +#usda 1.0 +( + "A layer written outside WAI" + defaultPrim = "Set" + metersPerUnit = 1 + upAxis = "Y" +) + +def Xform "Set" ( + kind = "group" +) +{ + double3 xformOp:translate = (10, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Pillar" # a pillar + { + double3 xformOp:translate = (0.5, 3, -0.25) + float3 xformOp:rotateXYZ = (0, 90, 0) + uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:rotateXYZ"] + + def Xform "Cap" + { + double3 xformOp:translate = (0, 1.5, 0) + } + } + + def Mesh "Floor" + { + int[] faceVertexCounts = [3] + int[] faceVertexIndices = [0, 1, 2] + point3f[] points = [(0, 0, 0), (1, 0, 0), (0, 0, 1)] + } + + /* an asset by its content hash */ + def "Statue" ( + prepend references = @urn:wai:asset:f3fdba94a88739184ad0f417656a62214dd1aa7138d8b21e3efabe4b38034461@ + ) + { + } +} diff --git a/wai/live-conformance/scene/docs/foreign_rotation.gltf b/wai/live-conformance/scene/docs/foreign_rotation.gltf new file mode 100644 index 000000000..4dd88c406 --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_rotation.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0,3]}],"nodes":[{"name":"base","translation":[1,0,-2],"children":[1,2]},{"name":"lamp","translation":[0.5,2,0],"rotation":[0,0.7071068,0,0.7071068]},{"name":"crate","mesh":0,"translation":[-1.25,0,0.75]},{"name":"eye","camera":0,"translation":[0,1.5,4]}],"meshes":[{"primitives":[{"attributes":{"POSITION":0}}]}],"accessors":[{"componentType":5126,"count":3,"type":"VEC3","min":[0,0,0],"max":[1,1,0]}],"cameras":[{"type":"perspective","perspective":{"yfov":1,"znear":0.1}}]} \ No newline at end of file diff --git a/wai/live-conformance/scene/docs/foreign_step.gltf b/wai/live-conformance/scene/docs/foreign_step.gltf new file mode 100644 index 000000000..93174e18f --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_step.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0"},"extensionsUsed":["MPEG_accessor_timed","MPEG_audio_spatial","MPEG_buffer_circular","MPEG_media","MPEG_texture_video"],"extensions":{"MPEG_media":{"media":[{"name":"voice","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:0404040404040404040404040404040404040404040404040404040404040404","mimeType":"application/wai; codecs=\"wai1.audio.opus\""}]},{"name":"screen","startTime":0,"alternatives":[{"uri":"https://example.invalid/screen.mp4","mimeType":"video/mp4"},{"uri":"urn:wai:asset:739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e","mimeType":"application/wai; codecs=\"wai1.video.av1\""}]}]}},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"name":"room","translation":[0,1,0],"children":[1]},{"name":"speaker","translation":[-2,0,-1],"extensions":{"MPEG_audio_spatial":{"sources":[{"id":0,"type":"Object","accessors":[2]}]}}}],"buffers":[{"byteLength":48,"uri":"data:application/octet-stream;base64,AAAAAAAAAD8AAIA/AAAAwAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAQAAAAAAAAIC/"},{"byteLength":4096,"extensions":{"MPEG_buffer_circular":{"media":0}}},{"byteLength":6912,"extensions":{"MPEG_buffer_circular":{"media":1}}}],"bufferViews":[{"buffer":0,"byteLength":12},{"buffer":0,"byteOffset":12,"byteLength":36},{"buffer":1,"byteLength":4096},{"buffer":2,"byteLength":6912}],"accessors":[{"bufferView":0,"componentType":5126,"count":3,"type":"SCALAR","min":[0],"max":[1]},{"bufferView":1,"componentType":5126,"count":3,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":1024,"type":"SCALAR","extensions":{"MPEG_accessor_timed":{"immutable":true}}},{"bufferView":3,"componentType":5121,"normalized":true,"count":2304,"type":"VEC3","extensions":{"MPEG_accessor_timed":{"immutable":true}}}],"animations":[{"channels":[{"sampler":0,"target":{"node":1,"path":"translation"}}],"samplers":[{"input":0,"interpolation":"STEP","output":1}]}],"textures":[{"extensions":{"MPEG_texture_video":{"accessor":3,"width":48,"height":16,"format":"RGB"}}}]} \ No newline at end of file diff --git a/wai/live-conformance/scene/docs/foreign_time.gltf b/wai/live-conformance/scene/docs/foreign_time.gltf new file mode 100644 index 000000000..b3a4d377e --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_time.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0"},"extensionsUsed":["MPEG_accessor_timed","MPEG_audio_spatial","MPEG_buffer_circular","MPEG_media","MPEG_texture_video"],"extensions":{"MPEG_media":{"media":[{"name":"voice","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:0404040404040404040404040404040404040404040404040404040404040404","mimeType":"application/wai; codecs=\"wai1.audio.opus\""}]},{"name":"screen","startTime":0,"alternatives":[{"uri":"https://example.invalid/screen.mp4","mimeType":"video/mp4"},{"uri":"urn:wai:asset:739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e","mimeType":"application/wai; codecs=\"wai1.video.av1\""}]}]}},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"name":"room","translation":[0,1,0],"children":[1]},{"name":"speaker","translation":[-2,0,-1],"extensions":{"MPEG_audio_spatial":{"sources":[{"id":0,"type":"Object","accessors":[2]}]}}}],"buffers":[{"byteLength":48,"uri":"data:application/octet-stream;base64,AAAAAM3MzD0AAIA/AAAAwAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAQAAAAAAAAIC/"},{"byteLength":4096,"extensions":{"MPEG_buffer_circular":{"media":0}}},{"byteLength":6912,"extensions":{"MPEG_buffer_circular":{"media":1}}}],"bufferViews":[{"buffer":0,"byteLength":12},{"buffer":0,"byteOffset":12,"byteLength":36},{"buffer":1,"byteLength":4096},{"buffer":2,"byteLength":6912}],"accessors":[{"bufferView":0,"componentType":5126,"count":3,"type":"SCALAR","min":[0],"max":[1]},{"bufferView":1,"componentType":5126,"count":3,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":1024,"type":"SCALAR","extensions":{"MPEG_accessor_timed":{"immutable":true}}},{"bufferView":3,"componentType":5121,"normalized":true,"count":2304,"type":"VEC3","extensions":{"MPEG_accessor_timed":{"immutable":true}}}],"animations":[{"channels":[{"sampler":0,"target":{"node":1,"path":"translation"}}],"samplers":[{"input":0,"interpolation":"LINEAR","output":1}]}],"textures":[{"extensions":{"MPEG_texture_video":{"accessor":3,"width":48,"height":16,"format":"RGB"}}}]} \ No newline at end of file diff --git a/wai/live-conformance/scene/docs/foreign_variant.usda b/wai/live-conformance/scene/docs/foreign_variant.usda new file mode 100644 index 000000000..d9e3dc97a --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_variant.usda @@ -0,0 +1,45 @@ +#usda 1.0 +( + "A layer written outside WAI" + defaultPrim = "Set" + metersPerUnit = 1 + upAxis = "Y" +) + +def Xform "Set" ( + kind = "group" +) +{ + double3 xformOp:translate = (10, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Pillar" # a pillar + { + double3 xformOp:translate = (0.5, 3, -0.25) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Cap" + { + double3 xformOp:translate = (0, 1.5, 0) + } + } + + variantSet "look" = { + "plain" { + } + } + + def Mesh "Floor" + { + int[] faceVertexCounts = [3] + int[] faceVertexIndices = [0, 1, 2] + point3f[] points = [(0, 0, 0), (1, 0, 0), (0, 0, 1)] + } + + /* an asset by its content hash */ + def "Statue" ( + prepend references = @urn:wai:asset:f3fdba94a88739184ad0f417656a62214dd1aa7138d8b21e3efabe4b38034461@ + ) + { + } +} diff --git a/wai/live-conformance/scene/imported/foreign_audio_gltf.wscn b/wai/live-conformance/scene/imported/foreign_audio_gltf.wscn new file mode 100644 index 0000000000000000000000000000000000000000..105cd5fc2aa01eb78d79c396242b39044abf9967 GIT binary patch literal 401 zcmah_!D<3A5KUeCBRNahgGlit^xjJkp2`xE5dv#wn`B)}Dfk6`i+3;mvTk2iS@h(< zByZ-;%$sEW{`yvkCjmRbC;2$SixqMT;9FW3llF$>Qg+%Y-CD;gCzbO%3Xv*P>up0; z&ZKRFPL91?&R@#1oXJhi9I9G>ahot8CJMwT^zD{{GuN4xlG?aE*~J`OqdhvhJ3Zu7 z{0ylw>|<)Uqkzf?-I^+BV!J`{CqHHSz)&(gxyWF&zrOEtFnk7Xg#GWwNRQx_Kb*(3 Wm|Wp4f0_1RovC<6>yDivA^rg@fmTug literal 0 HcmV?d00001 diff --git a/wai/live-conformance/scene/imported/foreign_audio_gltf.wwld b/wai/live-conformance/scene/imported/foreign_audio_gltf.wwld new file mode 100644 index 0000000000000000000000000000000000000000..1231137a23cd8babe1f5e7a79fa14914bc510138 GIT binary patch literal 369 zcmbtQI|>3Z5M4hP@Ct*S5`yR=TUl6GC}?FVVKcg6vm41qR}t|vdIS$YmPtH0N(E2u=_u7^hpKa<{@g+9O0E=UvEm8Cn%X1f@_+ zqGM}yKpFO-3lDNF9i_a|+|;7XWF;%5^2AKNl++Tvw9Jas6f+Yg9i^hw;{2SlR3)owrIggP#L}D+ zsIv0>qMQ^xsI*>AdAU+8P<389NM&LUkW#A1FV4s;$~Q7K)7A!>RGgZc0u(i%5P+;H z&dfz|1(2_ootc-Kn^=;WYyh+iNM*+tC6)l)ZDvpl@je4HkhZAz*tSeXaH-NK9|yri z-#`nf92b-(g^JS)oOX$QDdSznFvAP%Kl-3%H9*a3V1XER;6D%$GWSXAC8LCCH*%V{ zPg|Sd$#WddEU?>wW)Ni-C@p;cyKBmdc1sDb3tz-jvyv2jFRm)IxUosj?$OHnCQxROM^vTCTFwr;A0xZJ- z2{tGjME(2!|34BJD>F_naM~sIrHpqO!wfI5|LB97bpWUpS?>X2%zDy#$tYpkjhyD~ X)7B<<@*Ibn)c}n;U~EDFF=hb(#l3a? literal 0 HcmV?d00001 diff --git a/wai/live-conformance/scene/inputs/ballistic.wwld b/wai/live-conformance/scene/inputs/ballistic.wwld new file mode 100644 index 0000000000000000000000000000000000000000..c292ff17ebcea65342d75c7592d6cbbd77e5d408 GIT binary patch literal 465 zcmWF!_iDNF9i_a|+|;7XWF;%5^2AKNl++Tvw9Jas6f+Yg9i^hw;{2SlR3)owrIggP#L}D+ zsIv0>qMQ^xsI*>AdAU+8P<389NM&LUkW#A1FV4s;$~Q7K)7A!>RGgZc0u(i%5P+;H z&dfz|1(2_ootc-Kn^=;WYyh+iNM*+tC6)l)ZDvpl@je4HkX9;hd-v?CCCkIz*QaFs z|9;2gq%s>hQ*%y6FdPAT3b-k}j{79=i!x}m`iwwI7$)m?p! zPTMUnTLf(JvH2d#0x~NU2w?iSAPk2eQ1^l38zu>**`YMZ-W|Ql&3nr_A1KAJc>KO8 z9r4ia)unGHH?-FHJYzmmmuk$w0aTP+nv|-So|D93!X;R}+i;cMZJYC{JVr}47MRRf lB`PEsEH-`8nqA3H79U5R66qkenq{sUp34_u|202+0-#ZBp~Fiug<7_j#$?hfq%rY_gfmW;1&_ z?hgr>5yTTCdEsI(?o??XgpojV+6c`=RNAtRWZGQ7dEl}XRSk+RsftbnZ4J#gtM!r= zrL~Ti;$~yOav#8fMVW~eI4#E)6$Do3E2wRTceOBB(fo8nj`_0-Tw3EpZ5wc?^w_lo qjveuD*)C1f2k9Nm{{U_Ex&(c8`GCbY8TWn`6Ykx%?c_MjihKY(O+;}3 literal 0 HcmV?d00001 diff --git a/wai/live-conformance/scene/inputs/neg/truncated.wscn b/wai/live-conformance/scene/inputs/neg/truncated.wscn new file mode 100644 index 0000000000000000000000000000000000000000..3343e240198b34036f9f0ebbebae948756b92a45 GIT binary patch literal 369 zcma)2L2AP=5Om|vcLbeX;RYuZUwiK*ha8Qt)*!I8jwII&Azuo8UoOr>l!Ts=g_TCL zvl^|oo6oN_c}Wl_wp76pZ!13XJU=FJl=*|eSW%Z=!7a|yG~EsW@l$4Z=JgIEiuLFG=4c3P G3EKrR$437E literal 0 HcmV?d00001 diff --git a/wai/live-conformance/scene/inputs/neg/unordered.wwld b/wai/live-conformance/scene/inputs/neg/unordered.wwld new file mode 100644 index 0000000000000000000000000000000000000000..7c341132c5ba0f085df86e7ec55b834aeaa22277 GIT binary patch literal 634 zcmWF!_iDNF9i_a|+|;7XWF;%5^2AKNl++Tvw9Jas6f+Yg9i^hw;{2SlR3)owrIggP#L}D+ zsIv0>qMQ^xsI*>AdAU+8P<389NM&LUkW#A1FV4s;$~Q7K)7A!>RGgZc0u(i%5P+;H z&dfz|1(2_ootc-Kn^=;WYyh+iNM*+tC6)l)ZDvpl@je4HkWTni^?34!8P2obDaD(_ zJ2YBA**`YMZ-W|Ql&3nr_A1KAJc>KO8 z9r4ia)unGHH?-FHJYzmmmuk$w0aTP+nv|-So|D93!X;R}+i;cMZJYC{JVr}47MRRf lB`PEsEH-`8nqA3H7mHJ}iHtSQdSMR5g? zuUAr3T3l=Zw5KF9IXk{6u>|O5GlN=S05Aiw{qe&(J9o`U%H&(OJ+4hC!4fEj0+>*E zxH+J(T=r?llG^^CuMd^HXAQKQ8Dt0)V??)MV=u#{D-Mp!i(Oa6l{Tz=3o;UFi2{g? UE)RDR%q1`yq!=H7#x4T`03D-u$p8QV literal 0 HcmV?d00001 diff --git a/wai/live-conformance/scene/out/ballistic.gltf b/wai/live-conformance/scene/out/ballistic.gltf new file mode 100644 index 000000000..11f3b91a4 --- /dev/null +++ b/wai/live-conformance/scene/out/ballistic.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0","generator":"wai-scene/1"},"scene":0,"scenes":[{"nodes":[0,1]}],"nodes":[{"name":"node-028e3bb51609166d2ab2078de7716441","translation":[0,5,0]},{"name":"node-9e46c67773c2687866e4bbeb8c7e3db8","translation":[-3,1.5,2]}],"extras":{"wai":{"profile":"wai-scene/1","world":"57574c4403000100000000f90000000200010000ac00000004b0010000000000007b22636865636b706f696e745f6576657279223a3630302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030227d2c2273696d223a227761692e776f726c642e73696d2e62616c6c697374696330222c227469636b5f72617465223a36307d0000000000000002000000028e3bb51609166d2ab2078de7716441400000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000c0000000009e46c67773c2687866e4bbeb8c7e3db84000000000000000fdffffff000000800100000000000000020000000000000001000000000000000000000000000000000000000000004000000000000000800000000000000000"}}} diff --git a/wai/live-conformance/scene/out/ballistic.usda b/wai/live-conformance/scene/out/ballistic.usda new file mode 100644 index 000000000..d18456fab --- /dev/null +++ b/wai/live-conformance/scene/out/ballistic.usda @@ -0,0 +1,23 @@ +#usda 1.0 +( + customLayerData = { + string "wai:profile" = "wai-scene/1" + string "wai:world" = "57574c4403000100000000f90000000200010000ac00000004b0010000000000007b22636865636b706f696e745f6576657279223a3630302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030227d2c2273696d223a227761692e776f726c642e73696d2e62616c6c697374696330222c227469636b5f72617465223a36307d0000000000000002000000028e3bb51609166d2ab2078de7716441400000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000c0000000009e46c67773c2687866e4bbeb8c7e3db84000000000000000fdffffff000000800100000000000000020000000000000001000000000000000000000000000000000000000000004000000000000000800000000000000000" + } + defaultPrim = "World" +) + +def Xform "World" +{ + def Xform "node_028e3bb51609166d2ab2078de7716441" + { + double3 xformOp:translate = (0, 5, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "node_9e46c67773c2687866e4bbeb8c7e3db8" + { + double3 xformOp:translate = (-3, 1.5, 2) + uniform token[] xformOpOrder = ["xformOp:translate"] + } +} diff --git a/wai/live-conformance/scene/out/distance.gltf b/wai/live-conformance/scene/out/distance.gltf new file mode 100644 index 000000000..127c6aa01 --- /dev/null +++ b/wai/live-conformance/scene/out/distance.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0","generator":"wai-scene/1"},"extensionsUsed":["MPEG_accessor_timed","MPEG_audio_spatial","MPEG_buffer_circular","MPEG_media"],"extensions":{"MPEG_media":{"media":[{"name":"source-0303030303030303","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:0303030303030303030303030303030303030303030303030303030303030303","mimeType":"application/wai"}]}]}},"scene":0,"scenes":[{"nodes":[0,1,2]}],"nodes":[{"name":"listener","camera":0,"extensions":{"MPEG_audio_spatial":{"listener":{"id":0}}}},{"name":"object-0","translation":[-2,0,1],"extensions":{"MPEG_audio_spatial":{"sources":[{"id":0,"type":"Object","attenuation":"custom","accessors":[0]}]}}},{"name":"object-1","translation":[6,0,8],"extensions":{"MPEG_audio_spatial":{"sources":[{"id":1,"type":"Object","attenuation":"custom","accessors":[0]}]}}}],"cameras":[{"type":"perspective","perspective":{"yfov":1,"znear":0.25}}],"buffers":[{"byteLength":4096,"extensions":{"MPEG_buffer_circular":{"media":0}}}],"bufferViews":[{"buffer":0,"byteLength":4096}],"accessors":[{"bufferView":0,"componentType":5126,"count":1024,"type":"SCALAR","extensions":{"MPEG_accessor_timed":{"immutable":true}}}],"extras":{"wai":{"profile":"wai-scene/1","audio":"5753434e03000100000000ae00000002b00000009800000003480100002d0000007b2264697374616e6365223a227761692e617564696f2e646973742e696e76657273655f636c616d706564222c226475726174696f6e223a3235362c22666c6f6f725f6c61796f7574223a2273746572656f222c226e756d65726963223a227761692e6465742e66697865643634222c2270616e5f6c6177223a227761692e617564696f2e70616e2e737172745f636f6e7374706f776572222c2273616d706c655f72617465223a34383030307d000002000000030303030303030303030303030303030303030303030303030303030303030300000000010000000100000000000000000000000000feffffff0000000000000000000000000100000003030303030303030303030303030303030303030303030303030303030303030000000001000000010000000000000000000000000006000000000000000000000000000000080000000100000003030303030303030303030303030303030303030303030303030303030303030004000000746f6e65000000"}}} diff --git a/wai/live-conformance/scene/out/distance.usda b/wai/live-conformance/scene/out/distance.usda new file mode 100644 index 000000000..d87d75f03 --- /dev/null +++ b/wai/live-conformance/scene/out/distance.usda @@ -0,0 +1,31 @@ +#usda 1.0 +( + customLayerData = { + string "wai:audio" = "5753434e03000100000000ae00000002b00000009800000003480100002d0000007b2264697374616e6365223a227761692e617564696f2e646973742e696e76657273655f636c616d706564222c226475726174696f6e223a3235362c22666c6f6f725f6c61796f7574223a2273746572656f222c226e756d65726963223a227761692e6465742e66697865643634222c2270616e5f6c6177223a227761692e617564696f2e70616e2e737172745f636f6e7374706f776572222c2273616d706c655f72617465223a34383030307d000002000000030303030303030303030303030303030303030303030303030303030303030300000000010000000100000000000000000000000000feffffff0000000000000000000000000100000003030303030303030303030303030303030303030303030303030303030303030000000001000000010000000000000000000000000006000000000000000000000000000000080000000100000003030303030303030303030303030303030303030303030303030303030303030004000000746f6e65000000" + string "wai:profile" = "wai-scene/1" + } + defaultPrim = "World" + endTimeCode = 256 + startTimeCode = 0 + timeCodesPerSecond = 48000 +) + +def Xform "World" +{ + def Scope "Audio" + { + def Xform "object_0" + { + custom string wai:source = "0303030303030303030303030303030303030303030303030303030303030303" + double3 xformOp:translate = (-2, 0, 1) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "object_1" + { + custom string wai:source = "0303030303030303030303030303030303030303030303030303030303030303" + double3 xformOp:translate = (6, 0, 8) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + } +} diff --git a/wai/live-conformance/scene/out/empty_scene.gltf b/wai/live-conformance/scene/out/empty_scene.gltf new file mode 100644 index 000000000..af4402fa8 --- /dev/null +++ b/wai/live-conformance/scene/out/empty_scene.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0","generator":"wai-scene/1"},"scene":0,"scenes":[{}],"extras":{"wai":{"profile":"wai-scene/1","world":"57574c4403000100000000f90000000200010000040000000408010000000000007b22636865636b706f696e745f6576657279223a3130302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432227d2c2273696d223a227761692e776f726c642e73696d2e6b696e656d6174696330222c227469636b5f72617465223a36307d000000000000000000000000000000"}}} diff --git a/wai/live-conformance/scene/out/empty_scene.usda b/wai/live-conformance/scene/out/empty_scene.usda new file mode 100644 index 000000000..ec73c0e1d --- /dev/null +++ b/wai/live-conformance/scene/out/empty_scene.usda @@ -0,0 +1,12 @@ +#usda 1.0 +( + customLayerData = { + string "wai:profile" = "wai-scene/1" + string "wai:world" = "57574c4403000100000000f90000000200010000040000000408010000000000007b22636865636b706f696e745f6576657279223a3130302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432227d2c2273696d223a227761692e776f726c642e73696d2e6b696e656d6174696330222c227469636b5f72617465223a36307d000000000000000000000000000000" + } + defaultPrim = "World" +) + +def Xform "World" +{ +} diff --git a/wai/live-conformance/scene/out/full.gltf b/wai/live-conformance/scene/out/full.gltf new file mode 100644 index 000000000..4390b7db1 --- /dev/null +++ b/wai/live-conformance/scene/out/full.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0","generator":"wai-scene/1"},"extensionsUsed":["MPEG_accessor_timed","MPEG_audio_spatial","MPEG_buffer_circular","MPEG_media","MPEG_texture_video"],"extensions":{"MPEG_media":{"media":[{"name":"tone","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:0404040404040404040404040404040404040404040404040404040404040404","mimeType":"application/wai; codecs=\"wai1.audio.flac\""}]},{"name":"screen","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e","mimeType":"application/wai; codecs=\"wai1.video.av1\""}]}]}},"scene":0,"scenes":[{"nodes":[0,1,2,3,4]}],"nodes":[{"name":"node-227786eee6f53904e1bbd79468fff7c2","translation":[0.5,-2.25,0]},{"name":"node-60f27ae393f098439aee225f345f4081","translation":[1,2,3]},{"name":"node-7b8a8e8142b739d33850b44c3cf75504","translation":[0.3333333432674408,10,-7.75]},{"name":"listener","camera":0,"extensions":{"MPEG_audio_spatial":{"listener":{"id":0}}}},{"name":"object-0","translation":[-6,0,1],"extensions":{"MPEG_audio_spatial":{"sources":[{"id":0,"type":"Object","attenuation":"custom","accessors":[2]}]}}}],"cameras":[{"type":"perspective","perspective":{"yfov":1,"znear":0.25}}],"buffers":[{"byteLength":32,"uri":"data:application/octet-stream;base64,AAAAAD7DLjwAAMDAAAAAAAAAgD8AAMBAAAAAAAAAgD8="},{"byteLength":4096,"extensions":{"MPEG_buffer_circular":{"media":0}}},{"byteLength":6912,"extensions":{"MPEG_buffer_circular":{"media":1}}}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":8},{"buffer":0,"byteOffset":8,"byteLength":24},{"buffer":1,"byteLength":4096},{"buffer":2,"byteLength":6912}],"accessors":[{"bufferView":0,"componentType":5126,"count":2,"type":"SCALAR","min":[0],"max":[0.01066666655242443]},{"bufferView":1,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":1024,"type":"SCALAR","extensions":{"MPEG_accessor_timed":{"immutable":true}}},{"bufferView":3,"componentType":5121,"normalized":true,"count":2304,"type":"VEC3","extensions":{"MPEG_accessor_timed":{"immutable":true}}}],"animations":[{"name":"audio","channels":[{"sampler":0,"target":{"node":4,"path":"translation"}}],"samplers":[{"input":0,"interpolation":"LINEAR","output":1}]}],"textures":[{"extensions":{"MPEG_texture_video":{"accessor":3,"width":64,"height":36,"format":"RGB"}}}],"extras":{"wai":{"profile":"wai-scene/1","world":"57574c4404000100000000f90000000200010000e800000003e8010000630000000450020000000000007b22636865636b706f696e745f6576657279223a3630302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030227d2c2273696d223a227761692e776f726c642e73696d2e6b696e656d6174696330222c227469636b5f72617465223a36307d0000000000000003000000227786eee6f53904e1bbd79468fff7c2380000000000008000000000000000c0fdffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000060f27ae393f098439aee225f345f40813800000000000000010000000000000002000000000000000300000000000000000000000000000000000000000000000000000000000000000000007b8a8e8142b739d33850b44c3cf75504380000005555555500000000000000000a00000000000040f8ffffff000000000100000000000000000000000000000000000000070000000000000002000000b88d2383bd7689e0225c0448fbd91b58e13eead2f634d82aac4ce603c47e65330008000000637562652e676c6208340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4010d000000616d6269656e63652e666c61630000000000","audio":"5753434e03000100000000ae00000002b00000006e00000003200100002d0000007b2264697374616e6365223a227761692e617564696f2e646973742e696e76657273655f636c616d706564222c226475726174696f6e223a3531322c22666c6f6f725f6c61796f7574223a2273746572656f222c226e756d65726963223a227761692e6465742e66697865643634222c2270616e5f6c6177223a227761692e617564696f2e70616e2e737172745f636f6e7374706f776572222c2273616d706c655f72617465223a34383030307d000001000000040404040404040404040404040404040404040404040404040404040404040400000000010000000200000000000000000000000000faffffff00000000000000000000000001000000000200000000000000000000060000000000000000000000000000000100000000000100000004040404040404040404040404040404040404040404040404040404040404040004000000746f6e65000000","media":"[{\"name\":\"screen\",\"asset\":\"739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e\",\"capability\":\"wai.video.av1\",\"width\":64,\"height\":36},{\"name\":\"tone\",\"asset\":\"0404040404040404040404040404040404040404040404040404040404040404\",\"capability\":\"wai.audio.flac\"}]"}}} diff --git a/wai/live-conformance/scene/out/full.usda b/wai/live-conformance/scene/out/full.usda new file mode 100644 index 000000000..6598861cd --- /dev/null +++ b/wai/live-conformance/scene/out/full.usda @@ -0,0 +1,88 @@ +#usda 1.0 +( + customLayerData = { + string "wai:audio" = "5753434e03000100000000ae00000002b00000006e00000003200100002d0000007b2264697374616e6365223a227761692e617564696f2e646973742e696e76657273655f636c616d706564222c226475726174696f6e223a3531322c22666c6f6f725f6c61796f7574223a2273746572656f222c226e756d65726963223a227761692e6465742e66697865643634222c2270616e5f6c6177223a227761692e617564696f2e70616e2e737172745f636f6e7374706f776572222c2273616d706c655f72617465223a34383030307d000001000000040404040404040404040404040404040404040404040404040404040404040400000000010000000200000000000000000000000000faffffff00000000000000000000000001000000000200000000000000000000060000000000000000000000000000000100000000000100000004040404040404040404040404040404040404040404040404040404040404040004000000746f6e65000000" + string "wai:media" = "[{\"name\":\"screen\",\"asset\":\"739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e\",\"capability\":\"wai.video.av1\",\"width\":64,\"height\":36},{\"name\":\"tone\",\"asset\":\"0404040404040404040404040404040404040404040404040404040404040404\",\"capability\":\"wai.audio.flac\"}]" + string "wai:profile" = "wai-scene/1" + string "wai:world" = "57574c4404000100000000f90000000200010000e800000003e8010000630000000450020000000000007b22636865636b706f696e745f6576657279223a3630302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030227d2c2273696d223a227761692e776f726c642e73696d2e6b696e656d6174696330222c227469636b5f72617465223a36307d0000000000000003000000227786eee6f53904e1bbd79468fff7c2380000000000008000000000000000c0fdffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000060f27ae393f098439aee225f345f40813800000000000000010000000000000002000000000000000300000000000000000000000000000000000000000000000000000000000000000000007b8a8e8142b739d33850b44c3cf75504380000005555555500000000000000000a00000000000040f8ffffff000000000100000000000000000000000000000000000000070000000000000002000000b88d2383bd7689e0225c0448fbd91b58e13eead2f634d82aac4ce603c47e65330008000000637562652e676c6208340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4010d000000616d6269656e63652e666c61630000000000" + } + defaultPrim = "World" + endTimeCode = 512 + startTimeCode = 0 + timeCodesPerSecond = 48000 +) + +def Xform "World" +{ + def Xform "node_227786eee6f53904e1bbd79468fff7c2" + { + double3 xformOp:translate = (0.5, -2.25, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "node_60f27ae393f098439aee225f345f4081" + { + double3 xformOp:translate = (1, 2, 3) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "node_7b8a8e8142b739d33850b44c3cf75504" + { + double3 xformOp:translate = (0.3333333332557231, 10, -7.75) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Scope "Assets" + { + def "asset_0" ( + prepend references = @urn:wai:asset:b88d2383bd7689e0225c0448fbd91b58e13eead2f634d82aac4ce603c47e6533@ + ) + { + custom string wai:name = "cube.glb" + custom int wai:role = 0 + } + + def "asset_1" ( + prepend references = @urn:wai:asset:08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4@ + ) + { + custom string wai:name = "ambience.flac" + custom int wai:role = 1 + } + } + + def Scope "Audio" + { + def Xform "object_0" + { + custom string wai:source = "0404040404040404040404040404040404040404040404040404040404040404" + double3 xformOp:translate = (-6, 0, 1) + double3 xformOp:translate.timeSamples = { + 0: (-6, 0, 1), + 512: (6, 0, 1), + } + uniform token[] xformOpOrder = ["xformOp:translate"] + } + } + + def Scope "Media" + { + def "media_0" ( + prepend references = @urn:wai:asset:739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e@ + ) + { + custom string wai:capability = "wai.video.av1" + custom int wai:height = 36 + custom string wai:name = "screen" + custom int wai:width = 64 + } + + def "media_1" ( + prepend references = @urn:wai:asset:0404040404040404040404040404040404040404040404040404040404040404@ + ) + { + custom string wai:capability = "wai.audio.flac" + custom string wai:name = "tone" + } + } +} diff --git a/wai/live-conformance/scene/out/kinematic.gltf b/wai/live-conformance/scene/out/kinematic.gltf new file mode 100644 index 000000000..8d4400f24 --- /dev/null +++ b/wai/live-conformance/scene/out/kinematic.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0","generator":"wai-scene/1"},"extensionsUsed":["MPEG_accessor_timed","MPEG_buffer_circular","MPEG_media","MPEG_texture_video"],"extensions":{"MPEG_media":{"media":[{"name":"screen","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e","mimeType":"application/wai; codecs=\"wai1.video.av1\""}]},{"name":"ambience","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4","mimeType":"application/wai; codecs=\"wai1.audio.flac\""}]}]}},"scene":0,"scenes":[{"nodes":[0,1,2]}],"nodes":[{"name":"node-227786eee6f53904e1bbd79468fff7c2","translation":[0.5,-2.25,0]},{"name":"node-60f27ae393f098439aee225f345f4081","translation":[1,2,3]},{"name":"node-7b8a8e8142b739d33850b44c3cf75504","translation":[0.3333333432674408,10,-7.75]}],"buffers":[{"byteLength":6912,"extensions":{"MPEG_buffer_circular":{"media":0}}}],"bufferViews":[{"buffer":0,"byteLength":6912}],"accessors":[{"bufferView":0,"componentType":5121,"normalized":true,"count":2304,"type":"VEC3","extensions":{"MPEG_accessor_timed":{"immutable":true}}}],"textures":[{"extensions":{"MPEG_texture_video":{"accessor":0,"width":64,"height":36,"format":"RGB"}}}],"extras":{"wai":{"profile":"wai-scene/1","world":"57574c4404000100000000f90000000200010000e800000003e8010000630000000450020000000000007b22636865636b706f696e745f6576657279223a3630302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030227d2c2273696d223a227761692e776f726c642e73696d2e6b696e656d6174696330222c227469636b5f72617465223a36307d0000000000000003000000227786eee6f53904e1bbd79468fff7c2380000000000008000000000000000c0fdffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000060f27ae393f098439aee225f345f40813800000000000000010000000000000002000000000000000300000000000000000000000000000000000000000000000000000000000000000000007b8a8e8142b739d33850b44c3cf75504380000005555555500000000000000000a00000000000040f8ffffff000000000100000000000000000000000000000000000000070000000000000002000000b88d2383bd7689e0225c0448fbd91b58e13eead2f634d82aac4ce603c47e65330008000000637562652e676c6208340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4010d000000616d6269656e63652e666c61630000000000","media":"[{\"name\":\"screen\",\"asset\":\"739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e\",\"capability\":\"wai.video.av1\",\"width\":64,\"height\":36},{\"name\":\"ambience\",\"asset\":\"08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4\",\"capability\":\"wai.audio.flac\"}]"}}} diff --git a/wai/live-conformance/scene/out/kinematic.usda b/wai/live-conformance/scene/out/kinematic.usda new file mode 100644 index 000000000..0021a80ec --- /dev/null +++ b/wai/live-conformance/scene/out/kinematic.usda @@ -0,0 +1,70 @@ +#usda 1.0 +( + customLayerData = { + string "wai:media" = "[{\"name\":\"screen\",\"asset\":\"739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e\",\"capability\":\"wai.video.av1\",\"width\":64,\"height\":36},{\"name\":\"ambience\",\"asset\":\"08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4\",\"capability\":\"wai.audio.flac\"}]" + string "wai:profile" = "wai-scene/1" + string "wai:world" = "57574c4404000100000000f90000000200010000e800000003e8010000630000000450020000000000007b22636865636b706f696e745f6576657279223a3630302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030227d2c2273696d223a227761692e776f726c642e73696d2e6b696e656d6174696330222c227469636b5f72617465223a36307d0000000000000003000000227786eee6f53904e1bbd79468fff7c2380000000000008000000000000000c0fdffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000060f27ae393f098439aee225f345f40813800000000000000010000000000000002000000000000000300000000000000000000000000000000000000000000000000000000000000000000007b8a8e8142b739d33850b44c3cf75504380000005555555500000000000000000a00000000000040f8ffffff000000000100000000000000000000000000000000000000070000000000000002000000b88d2383bd7689e0225c0448fbd91b58e13eead2f634d82aac4ce603c47e65330008000000637562652e676c6208340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4010d000000616d6269656e63652e666c61630000000000" + } + defaultPrim = "World" +) + +def Xform "World" +{ + def Xform "node_227786eee6f53904e1bbd79468fff7c2" + { + double3 xformOp:translate = (0.5, -2.25, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "node_60f27ae393f098439aee225f345f4081" + { + double3 xformOp:translate = (1, 2, 3) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "node_7b8a8e8142b739d33850b44c3cf75504" + { + double3 xformOp:translate = (0.3333333332557231, 10, -7.75) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Scope "Assets" + { + def "asset_0" ( + prepend references = @urn:wai:asset:b88d2383bd7689e0225c0448fbd91b58e13eead2f634d82aac4ce603c47e6533@ + ) + { + custom string wai:name = "cube.glb" + custom int wai:role = 0 + } + + def "asset_1" ( + prepend references = @urn:wai:asset:08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4@ + ) + { + custom string wai:name = "ambience.flac" + custom int wai:role = 1 + } + } + + def Scope "Media" + { + def "media_0" ( + prepend references = @urn:wai:asset:739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e@ + ) + { + custom string wai:capability = "wai.video.av1" + custom int wai:height = 36 + custom string wai:name = "screen" + custom int wai:width = 64 + } + + def "media_1" ( + prepend references = @urn:wai:asset:08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4@ + ) + { + custom string wai:capability = "wai.audio.flac" + custom string wai:name = "ambience" + } + } +} diff --git a/wai/live-conformance/scene/out/moving.gltf b/wai/live-conformance/scene/out/moving.gltf new file mode 100644 index 000000000..5f7cdb521 --- /dev/null +++ b/wai/live-conformance/scene/out/moving.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0","generator":"wai-scene/1"},"extensionsUsed":["MPEG_accessor_timed","MPEG_audio_spatial","MPEG_buffer_circular","MPEG_media"],"extensions":{"MPEG_media":{"media":[{"name":"tone","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:0404040404040404040404040404040404040404040404040404040404040404","mimeType":"application/wai; codecs=\"wai1.audio.flac\""}]}]}},"scene":0,"scenes":[{"nodes":[0,1]}],"nodes":[{"name":"listener","camera":0,"extensions":{"MPEG_audio_spatial":{"listener":{"id":0}}}},{"name":"object-0","translation":[-6,0,1],"extensions":{"MPEG_audio_spatial":{"sources":[{"id":0,"type":"Object","attenuation":"custom","accessors":[2]}]}}}],"cameras":[{"type":"perspective","perspective":{"yfov":1,"znear":0.25}}],"buffers":[{"byteLength":32,"uri":"data:application/octet-stream;base64,AAAAAD7DLjwAAMDAAAAAAAAAgD8AAMBAAAAAAAAAgD8="},{"byteLength":4096,"extensions":{"MPEG_buffer_circular":{"media":0}}}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":8},{"buffer":0,"byteOffset":8,"byteLength":24},{"buffer":1,"byteLength":4096}],"accessors":[{"bufferView":0,"componentType":5126,"count":2,"type":"SCALAR","min":[0],"max":[0.01066666655242443]},{"bufferView":1,"componentType":5126,"count":2,"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":1024,"type":"SCALAR","extensions":{"MPEG_accessor_timed":{"immutable":true}}}],"animations":[{"name":"audio","channels":[{"sampler":0,"target":{"node":1,"path":"translation"}}],"samplers":[{"input":0,"interpolation":"LINEAR","output":1}]}],"extras":{"wai":{"profile":"wai-scene/1","audio":"5753434e03000100000000ae00000002b00000006e00000003200100002d0000007b2264697374616e6365223a227761692e617564696f2e646973742e696e76657273655f636c616d706564222c226475726174696f6e223a3531322c22666c6f6f725f6c61796f7574223a2273746572656f222c226e756d65726963223a227761692e6465742e66697865643634222c2270616e5f6c6177223a227761692e617564696f2e70616e2e737172745f636f6e7374706f776572222c2273616d706c655f72617465223a34383030307d000001000000040404040404040404040404040404040404040404040404040404040404040400000000010000000200000000000000000000000000faffffff00000000000000000000000001000000000200000000000000000000060000000000000000000000000000000100000000000100000004040404040404040404040404040404040404040404040404040404040404040004000000746f6e65000000","media":"[{\"name\":\"tone\",\"asset\":\"0404040404040404040404040404040404040404040404040404040404040404\",\"capability\":\"wai.audio.flac\"}]"}}} diff --git a/wai/live-conformance/scene/out/moving.usda b/wai/live-conformance/scene/out/moving.usda new file mode 100644 index 000000000..2e5668bc9 --- /dev/null +++ b/wai/live-conformance/scene/out/moving.usda @@ -0,0 +1,40 @@ +#usda 1.0 +( + customLayerData = { + string "wai:audio" = "5753434e03000100000000ae00000002b00000006e00000003200100002d0000007b2264697374616e6365223a227761692e617564696f2e646973742e696e76657273655f636c616d706564222c226475726174696f6e223a3531322c22666c6f6f725f6c61796f7574223a2273746572656f222c226e756d65726963223a227761692e6465742e66697865643634222c2270616e5f6c6177223a227761692e617564696f2e70616e2e737172745f636f6e7374706f776572222c2273616d706c655f72617465223a34383030307d000001000000040404040404040404040404040404040404040404040404040404040404040400000000010000000200000000000000000000000000faffffff00000000000000000000000001000000000200000000000000000000060000000000000000000000000000000100000000000100000004040404040404040404040404040404040404040404040404040404040404040004000000746f6e65000000" + string "wai:media" = "[{\"name\":\"tone\",\"asset\":\"0404040404040404040404040404040404040404040404040404040404040404\",\"capability\":\"wai.audio.flac\"}]" + string "wai:profile" = "wai-scene/1" + } + defaultPrim = "World" + endTimeCode = 512 + startTimeCode = 0 + timeCodesPerSecond = 48000 +) + +def Xform "World" +{ + def Scope "Audio" + { + def Xform "object_0" + { + custom string wai:source = "0404040404040404040404040404040404040404040404040404040404040404" + double3 xformOp:translate = (-6, 0, 1) + double3 xformOp:translate.timeSamples = { + 0: (-6, 0, 1), + 512: (6, 0, 1), + } + uniform token[] xformOpOrder = ["xformOp:translate"] + } + } + + def Scope "Media" + { + def "media_0" ( + prepend references = @urn:wai:asset:0404040404040404040404040404040404040404040404040404040404040404@ + ) + { + custom string wai:capability = "wai.audio.flac" + custom string wai:name = "tone" + } + } +} diff --git a/wai/live-conformance/scene/out/replay.gltf b/wai/live-conformance/scene/out/replay.gltf new file mode 100644 index 000000000..2deb68490 --- /dev/null +++ b/wai/live-conformance/scene/out/replay.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0","generator":"wai-scene/1"},"scene":0,"scenes":[{}],"extras":{"wai":{"profile":"wai-scene/1","world":"57574c4404000100000000f90000000200010000040000000408010000d803000005e0040000680000007b22636865636b706f696e745f6576657279223a3130302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432227d2c2273696d223a227761692e776f726c642e73696d2e6b696e656d6174696330222c227469636b5f72617465223a36307d0000000000000000000000000000008139770ea87d175f56a35466c34c7ecccb8d8a91b4ee37a25df60f5b8fc9b39401000000000000000100000000000000000021061455b45613bca2d3462d186c9dce010038000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000008444710bb8df1521ed9339f5320ceb628b4e2366444d47ac9e49b127972365a181de9e1bb27bce3a565876df54938aff7c2ef9cd9c0da02b5b39cfb3d38010a8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c010000000000000001000000000000000000c2d2e7e191b1b81ee6b8b20763bca1f00100380000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000cd2bce5c12affdc3a781ee5edc8b8757a2bc3359116b1ae58d6123eebd431af2a27a481fb8ea33b0f164eab9c433d971183c03e3e1fceaebb559ac370779ee098139770ea87d175f56a35466c34c7ecccb8d8a91b4ee37a25df60f5b8fc9b394020000000000000032000000000000000000c2d2e7e191b1b81ee6b8b20763bca1f003000800000000000000070000007e34d15a2825803ca3837bc2dc32354b3954e5d4bf2b538ae9c2ae7a4ac0a3c96adfd0411cd866e6a6cd5c5b17735baf0c1692e35cb48cee40cb8b9e8243740c8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c02000000000000003200000000000000000021061455b45613bca2d3462d186c9dce0400180000000000000002000000000000000000000000000000000000009ce85f18da9206e14fa08e300386ccd4d5bf470167d056b239dc798492920458ba39c6dff98cb1ab465cfc4c468bdc5135565ce6042a101abd2e82365b532f068a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c030000000000000078000000000000000000c2d2e7e191b1b81ee6b8b20763bca1f00500180000000000000000000000000000000000000000000000050000001320485655072c876b99881bbfedef71d35e877d109eada14bc4c81dc30aedcc033f85af3f021f904330c67f8eb73d82ac041d3f0e1ac7ac2916a5cb1e54be0e8139770ea87d175f56a35466c34c7ecccb8d8a91b4ee37a25df60f5b8fc9b3940300000000000000b400000000000000000021061455b45613bca2d3462d186c9dce0200000000000928a2b636bb961f9ed4634eeb738947059430f5660a9848229f171a6eb1bc981c542dd71eae7ea05ed7bf667cff6d5f2df3907c78e6f86aed8d4f48a3e7e507c800000000000000604de1dce82bdb9781ac769fb157d2e3d2912161e64ed4657664d1b5d568e3cbc55c6221b28840ce4c196bdf52a4b9b841dcb3fd75abc97b94bd41b4a3a6db2b5ca62ae70cb9e494cf5fbb4bcdeb12cec5572e7aea8f7c01ec08439cc861a809"}}} diff --git a/wai/live-conformance/scene/out/replay.usda b/wai/live-conformance/scene/out/replay.usda new file mode 100644 index 000000000..f0fc7fd73 --- /dev/null +++ b/wai/live-conformance/scene/out/replay.usda @@ -0,0 +1,12 @@ +#usda 1.0 +( + customLayerData = { + string "wai:profile" = "wai-scene/1" + string "wai:world" = "57574c4404000100000000f90000000200010000040000000408010000d803000005e0040000680000007b22636865636b706f696e745f6576657279223a3130302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432343234323432227d2c2273696d223a227761692e776f726c642e73696d2e6b696e656d6174696330222c227469636b5f72617465223a36307d0000000000000000000000000000008139770ea87d175f56a35466c34c7ecccb8d8a91b4ee37a25df60f5b8fc9b39401000000000000000100000000000000000021061455b45613bca2d3462d186c9dce010038000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000008444710bb8df1521ed9339f5320ceb628b4e2366444d47ac9e49b127972365a181de9e1bb27bce3a565876df54938aff7c2ef9cd9c0da02b5b39cfb3d38010a8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c010000000000000001000000000000000000c2d2e7e191b1b81ee6b8b20763bca1f00100380000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000cd2bce5c12affdc3a781ee5edc8b8757a2bc3359116b1ae58d6123eebd431af2a27a481fb8ea33b0f164eab9c433d971183c03e3e1fceaebb559ac370779ee098139770ea87d175f56a35466c34c7ecccb8d8a91b4ee37a25df60f5b8fc9b394020000000000000032000000000000000000c2d2e7e191b1b81ee6b8b20763bca1f003000800000000000000070000007e34d15a2825803ca3837bc2dc32354b3954e5d4bf2b538ae9c2ae7a4ac0a3c96adfd0411cd866e6a6cd5c5b17735baf0c1692e35cb48cee40cb8b9e8243740c8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c02000000000000003200000000000000000021061455b45613bca2d3462d186c9dce0400180000000000000002000000000000000000000000000000000000009ce85f18da9206e14fa08e300386ccd4d5bf470167d056b239dc798492920458ba39c6dff98cb1ab465cfc4c468bdc5135565ce6042a101abd2e82365b532f068a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c030000000000000078000000000000000000c2d2e7e191b1b81ee6b8b20763bca1f00500180000000000000000000000000000000000000000000000050000001320485655072c876b99881bbfedef71d35e877d109eada14bc4c81dc30aedcc033f85af3f021f904330c67f8eb73d82ac041d3f0e1ac7ac2916a5cb1e54be0e8139770ea87d175f56a35466c34c7ecccb8d8a91b4ee37a25df60f5b8fc9b3940300000000000000b400000000000000000021061455b45613bca2d3462d186c9dce0200000000000928a2b636bb961f9ed4634eeb738947059430f5660a9848229f171a6eb1bc981c542dd71eae7ea05ed7bf667cff6d5f2df3907c78e6f86aed8d4f48a3e7e507c800000000000000604de1dce82bdb9781ac769fb157d2e3d2912161e64ed4657664d1b5d568e3cbc55c6221b28840ce4c196bdf52a4b9b841dcb3fd75abc97b94bd41b4a3a6db2b5ca62ae70cb9e494cf5fbb4bcdeb12cec5572e7aea8f7c01ec08439cc861a809" + } + defaultPrim = "World" +) + +def Xform "World" +{ +} diff --git a/wai/live-conformance/scene/out/stereo_pair.gltf b/wai/live-conformance/scene/out/stereo_pair.gltf new file mode 100644 index 000000000..d09f61b66 --- /dev/null +++ b/wai/live-conformance/scene/out/stereo_pair.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0","generator":"wai-scene/1"},"extensionsUsed":["MPEG_accessor_timed","MPEG_audio_spatial","MPEG_buffer_circular","MPEG_media"],"extensions":{"MPEG_media":{"media":[{"name":"source-0101010101010101","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:0101010101010101010101010101010101010101010101010101010101010101","mimeType":"application/wai"}]},{"name":"source-0202020202020202","autoplay":true,"alternatives":[{"uri":"urn:wai:asset:0202020202020202020202020202020202020202020202020202020202020202","mimeType":"application/wai"}]}]}},"scene":0,"scenes":[{"nodes":[0,1,2]}],"nodes":[{"name":"listener","camera":0,"extensions":{"MPEG_audio_spatial":{"listener":{"id":0}}}},{"name":"object-0","translation":[-5,0,0],"extensions":{"MPEG_audio_spatial":{"sources":[{"id":0,"type":"Object","attenuation":"custom","accessors":[0]}]}}},{"name":"object-1","translation":[5,0,0],"extensions":{"MPEG_audio_spatial":{"sources":[{"id":1,"type":"Object","attenuation":"custom","accessors":[1]}]}}}],"cameras":[{"type":"perspective","perspective":{"yfov":1,"znear":0.25}}],"buffers":[{"byteLength":4096,"extensions":{"MPEG_buffer_circular":{"media":0}}},{"byteLength":4096,"extensions":{"MPEG_buffer_circular":{"media":1}}}],"bufferViews":[{"buffer":0,"byteLength":4096},{"buffer":1,"byteLength":4096}],"accessors":[{"bufferView":0,"componentType":5126,"count":1024,"type":"SCALAR","extensions":{"MPEG_accessor_timed":{"immutable":true}}},{"bufferView":1,"componentType":5126,"count":1024,"type":"SCALAR","extensions":{"MPEG_accessor_timed":{"immutable":true}}}],"extras":{"wai":{"profile":"wai-scene/1","audio":"5753434e03000100000000ae00000002b0000000980000000348010000560000007b2264697374616e6365223a227761692e617564696f2e646973742e696e76657273655f636c616d706564222c226475726174696f6e223a3235362c22666c6f6f725f6c61796f7574223a2273746572656f222c226e756d65726963223a227761692e6465742e66697865643634222c2270616e5f6c6177223a227761692e617564696f2e70616e2e737172745f636f6e7374706f776572222c2273616d706c655f72617465223a34383030307d000002000000010101010101010101010101010101010101010101010101010101010101010100000000010000000100000000000000000000000000fbffffff0000000000000000000000000000000002020202020202020202020202020202020202020202020202020202020202020000000001000000010000000000000000000000000005000000000000000000000000000000000000000200000001010101010101010101010101010101010101010101010101010101010101010004000000746f6e6502020202020202020202020202020202020202020202020202020202020202020004000000746f6e650000"}}} diff --git a/wai/live-conformance/scene/out/stereo_pair.usda b/wai/live-conformance/scene/out/stereo_pair.usda new file mode 100644 index 000000000..79bcc47fd --- /dev/null +++ b/wai/live-conformance/scene/out/stereo_pair.usda @@ -0,0 +1,31 @@ +#usda 1.0 +( + customLayerData = { + string "wai:audio" = "5753434e03000100000000ae00000002b0000000980000000348010000560000007b2264697374616e6365223a227761692e617564696f2e646973742e696e76657273655f636c616d706564222c226475726174696f6e223a3235362c22666c6f6f725f6c61796f7574223a2273746572656f222c226e756d65726963223a227761692e6465742e66697865643634222c2270616e5f6c6177223a227761692e617564696f2e70616e2e737172745f636f6e7374706f776572222c2273616d706c655f72617465223a34383030307d000002000000010101010101010101010101010101010101010101010101010101010101010100000000010000000100000000000000000000000000fbffffff0000000000000000000000000000000002020202020202020202020202020202020202020202020202020202020202020000000001000000010000000000000000000000000005000000000000000000000000000000000000000200000001010101010101010101010101010101010101010101010101010101010101010004000000746f6e6502020202020202020202020202020202020202020202020202020202020202020004000000746f6e650000" + string "wai:profile" = "wai-scene/1" + } + defaultPrim = "World" + endTimeCode = 256 + startTimeCode = 0 + timeCodesPerSecond = 48000 +) + +def Xform "World" +{ + def Scope "Audio" + { + def Xform "object_0" + { + custom string wai:source = "0101010101010101010101010101010101010101010101010101010101010101" + double3 xformOp:translate = (-5, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "object_1" + { + custom string wai:source = "0202020202020202020202020202020202020202020202020202020202020202" + double3 xformOp:translate = (5, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + } +} diff --git a/wai/live-conformance/scene/out/truss.gltf b/wai/live-conformance/scene/out/truss.gltf new file mode 100644 index 000000000..82db2977f --- /dev/null +++ b/wai/live-conformance/scene/out/truss.gltf @@ -0,0 +1 @@ +{"asset":{"version":"2.0","generator":"wai-scene/1"},"scene":0,"scenes":[{"nodes":[0,1,2]}],"nodes":[{"name":"node-3fc7c32cb9ba9862690eaeb75e861260","translation":[0,2,0]},{"name":"node-a6f2b8a47d8ff9ebc274ef05513e9952"},{"name":"node-b18d00d2d44041a77345aa5e7580a9ed","translation":[2,2,0]}],"extras":{"wai":{"profile":"wai-scene/1","world":"57574c4403000100000000f500000002f8000000e300000004e0010000000000007b22636865636b706f696e745f6576657279223a3630302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030227d2c2273696d223a227761692e776f726c642e73696d2e747275737330222c227469636b5f72617465223a36307d000000030000003fc7c32cb9ba9862690eaeb75e86126039000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000001000000a6f2b8a47d8ff9ebc274ef05513e995231000000013fc7c32cb9ba9862690eaeb75e861260b18d00d2d44041a77345aa5e7580a9ed00000000020000000000002000000000b18d00d2d44041a77345aa5e7580a9ed390000000000000000020000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000"}}} diff --git a/wai/live-conformance/scene/out/truss.usda b/wai/live-conformance/scene/out/truss.usda new file mode 100644 index 000000000..8a1d63fc2 --- /dev/null +++ b/wai/live-conformance/scene/out/truss.usda @@ -0,0 +1,27 @@ +#usda 1.0 +( + customLayerData = { + string "wai:profile" = "wai-scene/1" + string "wai:world" = "57574c4403000100000000f500000002f8000000e300000004e0010000000000007b22636865636b706f696e745f6576657279223a3630302c226e756d65726963223a227761692e6465742e66697865643634222c227265736f6c7665223a7b2264656661756c74223a227761692e776f726c642e7265736f6c76652e6c7777227d2c22726e67223a7b22616c67223a22786f736869726f3235362b2b222c2273656564223a2230303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030227d2c2273696d223a227761692e776f726c642e73696d2e747275737330222c227469636b5f72617465223a36307d000000030000003fc7c32cb9ba9862690eaeb75e86126039000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000001000000a6f2b8a47d8ff9ebc274ef05513e995231000000013fc7c32cb9ba9862690eaeb75e861260b18d00d2d44041a77345aa5e7580a9ed00000000020000000000002000000000b18d00d2d44041a77345aa5e7580a9ed390000000000000000020000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000" + } + defaultPrim = "World" +) + +def Xform "World" +{ + def Xform "node_3fc7c32cb9ba9862690eaeb75e861260" + { + double3 xformOp:translate = (0, 2, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } + + def Xform "node_a6f2b8a47d8ff9ebc274ef05513e9952" + { + } + + def Xform "node_b18d00d2d44041a77345aa5e7580a9ed" + { + double3 xformOp:translate = (2, 2, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + } +} diff --git a/wai/live-conformance/scene/vectors.json b/wai/live-conformance/scene/vectors.json new file mode 100644 index 000000000..dca78fd79 --- /dev/null +++ b/wai/live-conformance/scene/vectors.json @@ -0,0 +1,1317 @@ +{ + "about": "the conformance corpus of extensions/scene-carriage.md §9, written by wai_scene_vectors gen", + "inputs": [ + { + "path": "../../world-conformance/vectors/scene.wwld", + "sha256": "41b89729f87c308653d4359522280d7255c36d5b2d5300eafa8b1b7f7b76b3f0" + }, + { + "path": "../../world-conformance/vectors/basic_kinematics.wwld", + "sha256": "c90b8a03ea11ab87428b2255f402c17eb26a89c625b59d8d753afad143707491" + }, + { + "path": "../../audio-conformance/vectors/moving.wscn", + "sha256": "f799afa2ade71684f0fb1bf5eeb6413950228906551ba75be477b24ace125010" + }, + { + "path": "../../audio-conformance/vectors/distance.wscn", + "sha256": "fcb946a54ef3b79d7f7ab14b98777800ff90e06309f7f2a5f9422bfc32f13d0b" + }, + { + "path": "../../audio-conformance/vectors/stereo_pair.wscn", + "sha256": "1ac272f61ce80b691de745bd9af94db2aaab20087cade54128c8886a53cd7185" + } + ], + "files": [ + { + "file": "docs/carried_bad_hex.gltf", + "sha256": "2f4abd35ac4215a2e93a32cc83614e6d136bac35110277b113ca7834df0a1b76" + }, + { + "file": "docs/carried_bad_profile.usda", + "sha256": "ff120a84eea3c88ad51ee09b6230b5ea038aadbb763013ad7b56847ce884da3c" + }, + { + "file": "docs/changed.gltf", + "sha256": "144cda9b7ffbd2535b8cb68c27cbcc1d70519e6a3c613dde37295c6bda8db91b" + }, + { + "file": "docs/changed.usda", + "sha256": "15cb0c1c1585310e2b16eb791a06fe2e780ffb2244ad29bc55972f3191b00631" + }, + { + "file": "docs/foreign_asset_path.usda", + "sha256": "32e4863ec510cec8396cab321fb01c71117d2147881759c33defae9cedfb5d26" + }, + { + "file": "docs/foreign_audio.gltf", + "sha256": "1963840abce0ea915f81f195599d4fe7957b916f92a4bc1acd2645a3e34223c4" + }, + { + "file": "docs/foreign_broken.usda", + "sha256": "20b470f8891db49e3e05c82be9928bad7effbe9819afc90a26dbd61c30ff3376" + }, + { + "file": "docs/foreign_hoa.gltf", + "sha256": "e1ddaac80b0f7feba6d90ed340721489f67fbbfebb1171364b36f2f50e5861a4" + }, + { + "file": "docs/foreign_https.gltf", + "sha256": "8db64f64b541ae731780d36174344a5aea6a7ffec04affcc947743576bf0b968" + }, + { + "file": "docs/foreign_inexact.gltf", + "sha256": "64db1d6129595c8524b7f0e4c6aeddbfc1d057a0f48df89b2fbcbfc888afdb33" + }, + { + "file": "docs/foreign_inexact.usda", + "sha256": "3b04a779adce6b179c8e33538e17314d192995ea49d1a5a204e4fb3c8697af23" + }, + { + "file": "docs/foreign_layer.usda", + "sha256": "4aff83081049cdaea857ac47a1269e567a7e95e95556be52064bcbba4db3cf90" + }, + { + "file": "docs/foreign_layer_no_contract.usda", + "sha256": "4aff83081049cdaea857ac47a1269e567a7e95e95556be52064bcbba4db3cf90" + }, + { + "file": "docs/foreign_nodes.gltf", + "sha256": "e0b519a9549eb8d52660b735e7a0851ddeb9ff45bbe8b5cc943f5c70c132b558" + }, + { + "file": "docs/foreign_nodes_ballistic.gltf", + "sha256": "e0b519a9549eb8d52660b735e7a0851ddeb9ff45bbe8b5cc943f5c70c132b558" + }, + { + "file": "docs/foreign_nodes_no_contract.gltf", + "sha256": "e0b519a9549eb8d52660b735e7a0851ddeb9ff45bbe8b5cc943f5c70c132b558" + }, + { + "file": "docs/foreign_not_gltf2.gltf", + "sha256": "4183c373404ad5c2a135d7083e4b71c102bd8c9bc2115fc439188cc4bdd3d4a8" + }, + { + "file": "docs/foreign_not_json.gltf", + "sha256": "8fe862cb4aa6669af26c23cc0e8d1aa1c81924bf9c189b68a401c41c8c22b25a" + }, + { + "file": "docs/foreign_over.usda", + "sha256": "cb3c9ccb84f1998d97e21eabd427ad76b3763f7f812bbb13ab70756390d91e6a" + }, + { + "file": "docs/foreign_pregain.gltf", + "sha256": "b2772dfecf6c0c2875dc5fb2731fa14973458f2d12ab2cb8d5d5481dd2809e79" + }, + { + "file": "docs/foreign_rotate.usda", + "sha256": "0d50a87198eebd914a4aa552b0ba1e408484fe057655436c07f11d6a14bcae8c" + }, + { + "file": "docs/foreign_rotation.gltf", + "sha256": "878d1c2525d67dba471e657d7a1e266dc386cfdc50d4e4e97c25b69eec108f9c" + }, + { + "file": "docs/foreign_step.gltf", + "sha256": "3ae8d66b5cc818ee5d3111fb82dc1f8240478b77e32be4262ac68842e6e6c1a9" + }, + { + "file": "docs/foreign_time.gltf", + "sha256": "cd5b0c759ce7fbc3a4795d69030ebc8658822eb15325922e1210ad63048f39d8" + }, + { + "file": "docs/foreign_variant.usda", + "sha256": "eaec6ad01da7f1dcddd04036a84c582aef84bb6b1ff69f06c6cc504f201a856b" + }, + { + "file": "imported/foreign_audio_gltf.wscn", + "sha256": "250a03a6b92e772104e8710ae2160773ce967b6536c639c7caa70d3831806f53" + }, + { + "file": "imported/foreign_audio_gltf.wwld", + "sha256": "ecfb1695fe8742c53afc5518ead8f63c584886d575ed20bb123053d8ff6532ad" + }, + { + "file": "imported/foreign_layer_usda.wwld", + "sha256": "29b5612b4ee20d21fc07fc0bab39b96a13c63e9a6b8bcade79334f20b87f8bcd" + }, + { + "file": "imported/foreign_nodes_gltf.wwld", + "sha256": "c1902ed36c6881c53bb2ccf293c250765ed88b5b7bfd850fd24561b742ddb140" + }, + { + "file": "inputs/ballistic.wwld", + "sha256": "5e14be4b6f259800249519ca0a124a055987dcf4c778fbe8c3c4c96c11a9bb1f" + }, + { + "file": "inputs/kinematic.wwld", + "sha256": "1d90da6343869cf80d3522c8f7886b342cdcfc070931db04d9d5c50c6bf9f1bc" + }, + { + "file": "inputs/neg/descending.wscn", + "sha256": "010af6b9fab6a44ae00c84aceb9badc1770e463f31331c34ca7df8ecb2267c52" + }, + { + "file": "inputs/neg/truncated.wscn", + "sha256": "7d6cf9298390ead9696d2b7998dc3cda422ca7708098d6e7c089a0c631ce77b0" + }, + { + "file": "inputs/neg/unordered.wwld", + "sha256": "abf86863d140bc05e73e614789568b70db2fa2088399da5331f551e018ac5881" + }, + { + "file": "inputs/truss.wwld", + "sha256": "187b3ab67d36d9ce8c7dd0bf374c9e765b14876c24d819a4420577f6a1f78e65" + }, + { + "file": "out/ballistic.gltf", + "sha256": "3396ead363dec199c49621503f8d230922d81842574d5e863197df032b0ad6e7" + }, + { + "file": "out/ballistic.usda", + "sha256": "660411b9d989c467a424736ebc1cbc4e37d6c774593107c51deb6bce246b8403" + }, + { + "file": "out/distance.gltf", + "sha256": "69b69d12756bb92c2215b228cff2d6665ed8a8f5c92906f1732473f65b8d2c70" + }, + { + "file": "out/distance.usda", + "sha256": "02b555bef72e1f9b1363480a249fa387ea363c73daf2f5279afe031f0a84af52" + }, + { + "file": "out/empty_scene.gltf", + "sha256": "70bf60e26dd51bbf3eec59600abcee83f5aa72d4c3d3ae04fd2c8f0d1725ba2f" + }, + { + "file": "out/empty_scene.usda", + "sha256": "16b1939509e529e7aac3fb73fc047deb43de1e2dfa3b056e90ee839a47b2e70a" + }, + { + "file": "out/full.gltf", + "sha256": "b91f82c2297c6972466176780d6805b3378cb3170bc4d6942dc0055cfda1f4b9" + }, + { + "file": "out/full.usda", + "sha256": "8b42f605ab3d5eda6cf5664da4a638635633610959c37edaa94462ac7e0b9c3c" + }, + { + "file": "out/kinematic.gltf", + "sha256": "cd5ec474fc3fcb2627ce34b2d6e31c9b7a6f8afb9eadc9e729472ba40a8b9fd6" + }, + { + "file": "out/kinematic.usda", + "sha256": "d52dccdb3acc419cd1c5675f618373d448505db6ec1cf974af6bfa4712a1797e" + }, + { + "file": "out/moving.gltf", + "sha256": "c816e49e53d5aa6ceaf3e24c5e58cd14381f0e1a59bc342b813ccd78dfb91e25" + }, + { + "file": "out/moving.usda", + "sha256": "ad92704041bf4630525c4fed8b7bdb4d5269a3fe8122651a49069e3e702c41ee" + }, + { + "file": "out/replay.gltf", + "sha256": "09161f7a24a8dc108566b1dc75ae830ec7479dbf56c84171165d8cc180b9b939" + }, + { + "file": "out/replay.usda", + "sha256": "73ba18243aedf6210679c37c39590c9fa34f06007058732abdf2459dbfe62fd0" + }, + { + "file": "out/stereo_pair.gltf", + "sha256": "6c6d12b6fdd2f42ef5fa2f85152106da8d394501a5332b0a0da7080ca0a30e15" + }, + { + "file": "out/stereo_pair.usda", + "sha256": "173f0a79ebb278e66435d1a83281854d26253bc2c17959e1553e13793801c92c" + }, + { + "file": "out/truss.gltf", + "sha256": "20ca4fb14b937f8e4167f40a3a1411c8cb43cae44ced4e44814d6fd1df8ad0b2" + }, + { + "file": "out/truss.usda", + "sha256": "013b9822d78753b8190ab87dbe5ac35034f7aab3b733787a05bf12a311c7d0b6" + } + ], + "vectors": [ + { + "name": "kinematic_gltf_export", + "op": "export", + "format": "gltf", + "bundle": { + "world": "inputs/kinematic.wwld", + "audio": null, + "media": [ + { + "name": "screen", + "asset": "739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e", + "capability": "wai.video.av1", + "width": 64, + "height": 36 + }, + { + "name": "ambience", + "asset": "08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4", + "capability": "wai.audio.flac" + } + ] + }, + "outcome": "accept", + "file": "out/kinematic.gltf" + }, + { + "name": "kinematic_gltf_import", + "op": "import", + "format": "gltf", + "file": "out/kinematic.gltf", + "params": null, + "outcome": "accept", + "bundle": { + "world": "inputs/kinematic.wwld", + "audio": null, + "media": [ + { + "name": "screen", + "asset": "739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e", + "capability": "wai.video.av1", + "width": 64, + "height": 36 + }, + { + "name": "ambience", + "asset": "08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4", + "capability": "wai.audio.flac" + } + ] + }, + "dropped": [] + }, + { + "name": "kinematic_usda_export", + "op": "export", + "format": "usda", + "bundle": { + "world": "inputs/kinematic.wwld", + "audio": null, + "media": [ + { + "name": "screen", + "asset": "739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e", + "capability": "wai.video.av1", + "width": 64, + "height": 36 + }, + { + "name": "ambience", + "asset": "08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4", + "capability": "wai.audio.flac" + } + ] + }, + "outcome": "accept", + "file": "out/kinematic.usda" + }, + { + "name": "kinematic_usda_import", + "op": "import", + "format": "usda", + "file": "out/kinematic.usda", + "params": null, + "outcome": "accept", + "bundle": { + "world": "inputs/kinematic.wwld", + "audio": null, + "media": [ + { + "name": "screen", + "asset": "739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e", + "capability": "wai.video.av1", + "width": 64, + "height": 36 + }, + { + "name": "ambience", + "asset": "08340a117bbb31aa2edb3ccf650c32a4b1703498aa15121153169792acba63e4", + "capability": "wai.audio.flac" + } + ] + }, + "dropped": [] + }, + { + "name": "ballistic_gltf_export", + "op": "export", + "format": "gltf", + "bundle": { + "world": "inputs/ballistic.wwld", + "audio": null, + "media": [] + }, + "outcome": "accept", + "file": "out/ballistic.gltf" + }, + { + "name": "ballistic_gltf_import", + "op": "import", + "format": "gltf", + "file": "out/ballistic.gltf", + "params": null, + "outcome": "accept", + "bundle": { + "world": "inputs/ballistic.wwld", + "audio": null, + "media": [] + }, + "dropped": [] + }, + { + "name": "ballistic_usda_export", + "op": "export", + "format": "usda", + "bundle": { + "world": "inputs/ballistic.wwld", + "audio": null, + "media": [] + }, + "outcome": "accept", + "file": "out/ballistic.usda" + }, + { + "name": "ballistic_usda_import", + "op": "import", + "format": "usda", + "file": "out/ballistic.usda", + "params": null, + "outcome": "accept", + "bundle": { + "world": "inputs/ballistic.wwld", + "audio": null, + "media": [] + }, + "dropped": [] + }, + { + "name": "truss_gltf_export", + "op": "export", + "format": "gltf", + "bundle": { + "world": "inputs/truss.wwld", + "audio": null, + "media": [] + }, + "outcome": "accept", + "file": "out/truss.gltf" + }, + { + "name": "truss_gltf_import", + "op": "import", + "format": "gltf", + "file": "out/truss.gltf", + "params": null, + "outcome": "accept", + "bundle": { + "world": "inputs/truss.wwld", + "audio": null, + "media": [] + }, + "dropped": [] + }, + { + "name": "truss_usda_export", + "op": "export", + "format": "usda", + "bundle": { + "world": "inputs/truss.wwld", + "audio": null, + "media": [] + }, + "outcome": "accept", + "file": "out/truss.usda" + }, + { + "name": "truss_usda_import", + "op": "import", + "format": "usda", + "file": "out/truss.usda", + "params": null, + "outcome": "accept", + "bundle": { + "world": "inputs/truss.wwld", + "audio": null, + "media": [] + }, + "dropped": [] + }, + { + "name": "empty_scene_gltf_export", + "op": "export", + "format": "gltf", + "bundle": { + "world": "../../world-conformance/vectors/scene.wwld", + "audio": null, + "media": [] + }, + "outcome": "accept", + "file": "out/empty_scene.gltf" + }, + { + "name": "empty_scene_gltf_import", + "op": "import", + "format": "gltf", + "file": "out/empty_scene.gltf", + "params": null, + "outcome": "accept", + "bundle": { + "world": "../../world-conformance/vectors/scene.wwld", + "audio": null, + "media": [] + }, + "dropped": [] + }, + { + "name": "empty_scene_usda_export", + "op": "export", + "format": "usda", + "bundle": { + "world": "../../world-conformance/vectors/scene.wwld", + "audio": null, + "media": [] + }, + "outcome": "accept", + "file": "out/empty_scene.usda" + }, + { + "name": "empty_scene_usda_import", + "op": "import", + "format": "usda", + "file": "out/empty_scene.usda", + "params": null, + "outcome": "accept", + "bundle": { + "world": "../../world-conformance/vectors/scene.wwld", + "audio": null, + "media": [] + }, + "dropped": [] + }, + { + "name": "replay_gltf_export", + "op": "export", + "format": "gltf", + "bundle": { + "world": "../../world-conformance/vectors/basic_kinematics.wwld", + "audio": null, + "media": [] + }, + "outcome": "accept", + "file": "out/replay.gltf" + }, + { + "name": "replay_gltf_import", + "op": "import", + "format": "gltf", + "file": "out/replay.gltf", + "params": null, + "outcome": "accept", + "bundle": { + "world": "../../world-conformance/vectors/basic_kinematics.wwld", + "audio": null, + "media": [] + }, + "dropped": [] + }, + { + "name": "replay_usda_export", + "op": "export", + "format": "usda", + "bundle": { + "world": "../../world-conformance/vectors/basic_kinematics.wwld", + "audio": null, + "media": [] + }, + "outcome": "accept", + "file": "out/replay.usda" + }, + { + "name": "replay_usda_import", + "op": "import", + "format": "usda", + "file": "out/replay.usda", + "params": null, + "outcome": "accept", + "bundle": { + "world": "../../world-conformance/vectors/basic_kinematics.wwld", + "audio": null, + "media": [] + }, + "dropped": [] + }, + { + "name": "moving_gltf_export", + "op": "export", + "format": "gltf", + "bundle": { + "world": null, + "audio": "../../audio-conformance/vectors/moving.wscn", + "media": [ + { + "name": "tone", + "asset": "0404040404040404040404040404040404040404040404040404040404040404", + "capability": "wai.audio.flac" + } + ] + }, + "outcome": "accept", + "file": "out/moving.gltf" + }, + { + "name": "moving_gltf_import", + "op": "import", + "format": "gltf", + "file": "out/moving.gltf", + "params": null, + "outcome": "accept", + "bundle": { + "world": null, + "audio": "../../audio-conformance/vectors/moving.wscn", + "media": [ + { + "name": "tone", + "asset": "0404040404040404040404040404040404040404040404040404040404040404", + "capability": "wai.audio.flac" + } + ] + }, + "dropped": [] + }, + { + "name": "moving_usda_export", + "op": "export", + "format": "usda", + "bundle": { + "world": null, + "audio": "../../audio-conformance/vectors/moving.wscn", + "media": [ + { + "name": "tone", + "asset": "0404040404040404040404040404040404040404040404040404040404040404", + "capability": "wai.audio.flac" + } + ] + }, + "outcome": "accept", + "file": "out/moving.usda" + }, + { + "name": "moving_usda_import", + "op": "import", + "format": "usda", + "file": "out/moving.usda", + "params": null, + "outcome": "accept", + "bundle": { + "world": null, + "audio": "../../audio-conformance/vectors/moving.wscn", + "media": [ + { + "name": "tone", + "asset": "0404040404040404040404040404040404040404040404040404040404040404", + "capability": "wai.audio.flac" + } + ] + }, + "dropped": [] + }, + { + "name": "distance_gltf_export", + "op": "export", + "format": "gltf", + "bundle": { + "world": null, + "audio": "../../audio-conformance/vectors/distance.wscn", + "media": [] + }, + "outcome": "accept", + "file": "out/distance.gltf" + }, + { + "name": "distance_gltf_import", + "op": "import", + "format": "gltf", + "file": "out/distance.gltf", + "params": null, + "outcome": "accept", + "bundle": { + "world": null, + "audio": "../../audio-conformance/vectors/distance.wscn", + "media": [] + }, + "dropped": [] + }, + { + "name": "distance_usda_export", + "op": "export", + "format": "usda", + "bundle": { + "world": null, + "audio": "../../audio-conformance/vectors/distance.wscn", + "media": [] + }, + "outcome": "accept", + "file": "out/distance.usda" + }, + { + "name": "distance_usda_import", + "op": "import", + "format": "usda", + "file": "out/distance.usda", + "params": null, + "outcome": "accept", + "bundle": { + "world": null, + "audio": "../../audio-conformance/vectors/distance.wscn", + "media": [] + }, + "dropped": [] + }, + { + "name": "stereo_pair_gltf_export", + "op": "export", + "format": "gltf", + "bundle": { + "world": null, + "audio": "../../audio-conformance/vectors/stereo_pair.wscn", + "media": [] + }, + "outcome": "accept", + "file": "out/stereo_pair.gltf" + }, + { + "name": "stereo_pair_gltf_import", + "op": "import", + "format": "gltf", + "file": "out/stereo_pair.gltf", + "params": null, + "outcome": "accept", + "bundle": { + "world": null, + "audio": "../../audio-conformance/vectors/stereo_pair.wscn", + "media": [] + }, + "dropped": [] + }, + { + "name": "stereo_pair_usda_export", + "op": "export", + "format": "usda", + "bundle": { + "world": null, + "audio": "../../audio-conformance/vectors/stereo_pair.wscn", + "media": [] + }, + "outcome": "accept", + "file": "out/stereo_pair.usda" + }, + { + "name": "stereo_pair_usda_import", + "op": "import", + "format": "usda", + "file": "out/stereo_pair.usda", + "params": null, + "outcome": "accept", + "bundle": { + "world": null, + "audio": "../../audio-conformance/vectors/stereo_pair.wscn", + "media": [] + }, + "dropped": [] + }, + { + "name": "full_gltf_export", + "op": "export", + "format": "gltf", + "bundle": { + "world": "inputs/kinematic.wwld", + "audio": "../../audio-conformance/vectors/moving.wscn", + "media": [ + { + "name": "screen", + "asset": "739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e", + "capability": "wai.video.av1", + "width": 64, + "height": 36 + }, + { + "name": "tone", + "asset": "0404040404040404040404040404040404040404040404040404040404040404", + "capability": "wai.audio.flac" + } + ] + }, + "outcome": "accept", + "file": "out/full.gltf" + }, + { + "name": "full_gltf_import", + "op": "import", + "format": "gltf", + "file": "out/full.gltf", + "params": null, + "outcome": "accept", + "bundle": { + "world": "inputs/kinematic.wwld", + "audio": "../../audio-conformance/vectors/moving.wscn", + "media": [ + { + "name": "screen", + "asset": "739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e", + "capability": "wai.video.av1", + "width": 64, + "height": 36 + }, + { + "name": "tone", + "asset": "0404040404040404040404040404040404040404040404040404040404040404", + "capability": "wai.audio.flac" + } + ] + }, + "dropped": [] + }, + { + "name": "full_usda_export", + "op": "export", + "format": "usda", + "bundle": { + "world": "inputs/kinematic.wwld", + "audio": "../../audio-conformance/vectors/moving.wscn", + "media": [ + { + "name": "screen", + "asset": "739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e", + "capability": "wai.video.av1", + "width": 64, + "height": 36 + }, + { + "name": "tone", + "asset": "0404040404040404040404040404040404040404040404040404040404040404", + "capability": "wai.audio.flac" + } + ] + }, + "outcome": "accept", + "file": "out/full.usda" + }, + { + "name": "full_usda_import", + "op": "import", + "format": "usda", + "file": "out/full.usda", + "params": null, + "outcome": "accept", + "bundle": { + "world": "inputs/kinematic.wwld", + "audio": "../../audio-conformance/vectors/moving.wscn", + "media": [ + { + "name": "screen", + "asset": "739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e", + "capability": "wai.video.av1", + "width": 64, + "height": 36 + }, + { + "name": "tone", + "asset": "0404040404040404040404040404040404040404040404040404040404040404", + "capability": "wai.audio.flac" + } + ] + }, + "dropped": [] + }, + { + "name": "unordered_world_gltf", + "op": "export", + "format": "gltf", + "bundle": { + "world": "inputs/neg/unordered.wwld", + "audio": null, + "media": [] + }, + "outcome": "refuse", + "code": "world_malformed" + }, + { + "name": "unordered_world_usda", + "op": "export", + "format": "usda", + "bundle": { + "world": "inputs/neg/unordered.wwld", + "audio": null, + "media": [] + }, + "outcome": "refuse", + "code": "world_malformed" + }, + { + "name": "truncated_audio_gltf", + "op": "export", + "format": "gltf", + "bundle": { + "world": null, + "audio": "inputs/neg/truncated.wscn", + "media": [] + }, + "outcome": "refuse", + "code": "audio_malformed" + }, + { + "name": "truncated_audio_usda", + "op": "export", + "format": "usda", + "bundle": { + "world": null, + "audio": "inputs/neg/truncated.wscn", + "media": [] + }, + "outcome": "refuse", + "code": "audio_malformed" + }, + { + "name": "descending_keys_gltf", + "op": "export", + "format": "gltf", + "bundle": { + "world": null, + "audio": "inputs/neg/descending.wscn", + "media": [] + }, + "outcome": "refuse", + "code": "not_presentable" + }, + { + "name": "descending_keys_usda", + "op": "export", + "format": "usda", + "bundle": { + "world": null, + "audio": "inputs/neg/descending.wscn", + "media": [] + }, + "outcome": "refuse", + "code": "not_presentable" + }, + { + "name": "world_capability_gltf", + "op": "export", + "format": "gltf", + "bundle": { + "world": null, + "audio": null, + "media": [ + { + "name": "thing", + "asset": "739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e", + "capability": "wai.world.scene" + } + ] + }, + "outcome": "refuse", + "code": "media_malformed" + }, + { + "name": "world_capability_usda", + "op": "export", + "format": "usda", + "bundle": { + "world": null, + "audio": null, + "media": [ + { + "name": "thing", + "asset": "739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e", + "capability": "wai.world.scene" + } + ] + }, + "outcome": "refuse", + "code": "media_malformed" + }, + { + "name": "video_without_size_gltf", + "op": "export", + "format": "gltf", + "bundle": { + "world": null, + "audio": null, + "media": [ + { + "name": "screen", + "asset": "739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e", + "capability": "wai.video.av1" + } + ] + }, + "outcome": "refuse", + "code": "media_malformed" + }, + { + "name": "video_without_size_usda", + "op": "export", + "format": "usda", + "bundle": { + "world": null, + "audio": null, + "media": [ + { + "name": "screen", + "asset": "739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e", + "capability": "wai.video.av1" + } + ] + }, + "outcome": "refuse", + "code": "media_malformed" + }, + { + "name": "changed_gltf", + "op": "import", + "format": "gltf", + "file": "docs/changed.gltf", + "params": null, + "outcome": "refuse", + "code": "presentation_mismatch" + }, + { + "name": "carried_bad_hex_gltf", + "op": "import", + "format": "gltf", + "file": "docs/carried_bad_hex.gltf", + "params": null, + "outcome": "refuse", + "code": "carried_malformed" + }, + { + "name": "changed_usda", + "op": "import", + "format": "usda", + "file": "docs/changed.usda", + "params": null, + "outcome": "refuse", + "code": "presentation_mismatch" + }, + { + "name": "carried_bad_profile_usda", + "op": "import", + "format": "usda", + "file": "docs/carried_bad_profile.usda", + "params": null, + "outcome": "refuse", + "code": "carried_malformed" + }, + { + "name": "foreign_nodes_gltf", + "op": "import", + "format": "gltf", + "file": "docs/foreign_nodes.gltf", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": null + }, + "outcome": "accept", + "bundle": { + "world": "imported/foreign_nodes_gltf.wwld", + "audio": null, + "media": [] + }, + "dropped": [ + "node 2: mesh" + ] + }, + { + "name": "foreign_audio_gltf", + "op": "import", + "format": "gltf", + "file": "docs/foreign_audio.gltf", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": "{\"distance\":\"wai.audio.dist.inverse_clamped\",\"duration\":48000,\"floor_layout\":\"stereo\",\"numeric\":\"wai.det.fixed64\",\"pan_law\":\"wai.audio.pan.sqrt_constpower\",\"sample_rate\":48000}" + }, + "outcome": "accept", + "bundle": { + "world": "imported/foreign_audio_gltf.wwld", + "audio": "imported/foreign_audio_gltf.wscn", + "media": [ + { + "name": "screen", + "asset": "739363f10b53ad3a7f954385605fe0fcd12330dd5c4b4f2f17127c10542a132e", + "capability": "wai.video.av1", + "width": 48, + "height": 16 + } + ] + }, + "dropped": [] + }, + { + "name": "foreign_rotation_gltf", + "op": "import", + "format": "gltf", + "file": "docs/foreign_rotation.gltf", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": null + }, + "outcome": "refuse", + "code": "transform_not_carried" + }, + { + "name": "foreign_inexact_gltf", + "op": "import", + "format": "gltf", + "file": "docs/foreign_inexact.gltf", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": null + }, + "outcome": "refuse", + "code": "position_not_exact" + }, + { + "name": "foreign_step_gltf", + "op": "import", + "format": "gltf", + "file": "docs/foreign_step.gltf", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": "{\"distance\":\"wai.audio.dist.inverse_clamped\",\"duration\":48000,\"floor_layout\":\"stereo\",\"numeric\":\"wai.det.fixed64\",\"pan_law\":\"wai.audio.pan.sqrt_constpower\",\"sample_rate\":48000}" + }, + "outcome": "refuse", + "code": "animation_not_carried" + }, + { + "name": "foreign_time_gltf", + "op": "import", + "format": "gltf", + "file": "docs/foreign_time.gltf", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": "{\"distance\":\"wai.audio.dist.inverse_clamped\",\"duration\":48000,\"floor_layout\":\"stereo\",\"numeric\":\"wai.det.fixed64\",\"pan_law\":\"wai.audio.pan.sqrt_constpower\",\"sample_rate\":48000}" + }, + "outcome": "refuse", + "code": "time_not_exact" + }, + { + "name": "foreign_pregain_gltf", + "op": "import", + "format": "gltf", + "file": "docs/foreign_pregain.gltf", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": "{\"distance\":\"wai.audio.dist.inverse_clamped\",\"duration\":48000,\"floor_layout\":\"stereo\",\"numeric\":\"wai.det.fixed64\",\"pan_law\":\"wai.audio.pan.sqrt_constpower\",\"sample_rate\":48000}" + }, + "outcome": "refuse", + "code": "gain_not_exact" + }, + { + "name": "foreign_hoa_gltf", + "op": "import", + "format": "gltf", + "file": "docs/foreign_hoa.gltf", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": "{\"distance\":\"wai.audio.dist.inverse_clamped\",\"duration\":48000,\"floor_layout\":\"stereo\",\"numeric\":\"wai.det.fixed64\",\"pan_law\":\"wai.audio.pan.sqrt_constpower\",\"sample_rate\":48000}" + }, + "outcome": "refuse", + "code": "audio_not_object" + }, + { + "name": "foreign_https_gltf", + "op": "import", + "format": "gltf", + "file": "docs/foreign_https.gltf", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": "{\"distance\":\"wai.audio.dist.inverse_clamped\",\"duration\":48000,\"floor_layout\":\"stereo\",\"numeric\":\"wai.det.fixed64\",\"pan_law\":\"wai.audio.pan.sqrt_constpower\",\"sample_rate\":48000}" + }, + "outcome": "refuse", + "code": "media_not_wai" + }, + { + "name": "foreign_not_json_gltf", + "op": "import", + "format": "gltf", + "file": "docs/foreign_not_json.gltf", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": "{\"distance\":\"wai.audio.dist.inverse_clamped\",\"duration\":48000,\"floor_layout\":\"stereo\",\"numeric\":\"wai.det.fixed64\",\"pan_law\":\"wai.audio.pan.sqrt_constpower\",\"sample_rate\":48000}" + }, + "outcome": "refuse", + "code": "document_malformed" + }, + { + "name": "foreign_not_gltf2_gltf", + "op": "import", + "format": "gltf", + "file": "docs/foreign_not_gltf2.gltf", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": "{\"distance\":\"wai.audio.dist.inverse_clamped\",\"duration\":48000,\"floor_layout\":\"stereo\",\"numeric\":\"wai.det.fixed64\",\"pan_law\":\"wai.audio.pan.sqrt_constpower\",\"sample_rate\":48000}" + }, + "outcome": "refuse", + "code": "document_malformed" + }, + { + "name": "foreign_nodes_no_contract_gltf", + "op": "import", + "format": "gltf", + "file": "docs/foreign_nodes_no_contract.gltf", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": null, + "audio_contract": null + }, + "outcome": "refuse", + "code": "contract_absent" + }, + { + "name": "foreign_nodes_ballistic_gltf", + "op": "import", + "format": "gltf", + "file": "docs/foreign_nodes_ballistic.gltf", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.ballistic0\",\"tick_rate\":60}", + "audio_contract": null + }, + "outcome": "refuse", + "code": "sim_not_importable" + }, + { + "name": "foreign_layer_usda", + "op": "import", + "format": "usda", + "file": "docs/foreign_layer.usda", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": null + }, + "outcome": "accept", + "bundle": { + "world": "imported/foreign_layer_usda.wwld", + "audio": null, + "media": [] + }, + "dropped": [ + "/Set/Floor: Mesh" + ] + }, + { + "name": "foreign_rotate_usda", + "op": "import", + "format": "usda", + "file": "docs/foreign_rotate.usda", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": null + }, + "outcome": "refuse", + "code": "transform_not_carried" + }, + { + "name": "foreign_inexact_usda", + "op": "import", + "format": "usda", + "file": "docs/foreign_inexact.usda", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": null + }, + "outcome": "refuse", + "code": "position_not_exact" + }, + { + "name": "foreign_asset_path_usda", + "op": "import", + "format": "usda", + "file": "docs/foreign_asset_path.usda", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": null + }, + "outcome": "refuse", + "code": "asset_not_wai" + }, + { + "name": "foreign_variant_usda", + "op": "import", + "format": "usda", + "file": "docs/foreign_variant.usda", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": null + }, + "outcome": "refuse", + "code": "usd_not_read" + }, + { + "name": "foreign_over_usda", + "op": "import", + "format": "usda", + "file": "docs/foreign_over.usda", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": null + }, + "outcome": "refuse", + "code": "usd_not_read" + }, + { + "name": "foreign_broken_usda", + "op": "import", + "format": "usda", + "file": "docs/foreign_broken.usda", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": null + }, + "outcome": "refuse", + "code": "document_malformed" + }, + { + "name": "foreign_layer_no_contract_usda", + "op": "import", + "format": "usda", + "file": "docs/foreign_layer_no_contract.usda", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": null, + "audio_contract": null + }, + "outcome": "refuse", + "code": "contract_absent" + } + ] +} diff --git a/wai/wai-rs/src/bin/wai_prior_forms_vectors.rs b/wai/wai-rs/src/bin/wai_prior_forms_vectors.rs index a23651ef9..b6b55ff79 100644 --- a/wai/wai-rs/src/bin/wai_prior_forms_vectors.rs +++ b/wai/wai-rs/src/bin/wai_prior_forms_vectors.rs @@ -25,7 +25,7 @@ fn walk(root: &Path, rel: &str, out: &mut BTreeMap>) -> Result<( let key = if rel.is_empty() { name.clone() } else { format!("{rel}/{name}") }; if e.path().is_dir() { walk(root, &key, out)?; - } else if key != "README.md" { + } else if key != "README.md" && key != ".gitattributes" { out.insert(key, std::fs::read(e.path()).map_err(|err| format!("read {}: {err}", e.path().display()))?); } } diff --git a/wai/wai-rs/src/bin/wai_scene_vectors.rs b/wai/wai-rs/src/bin/wai_scene_vectors.rs new file mode 100644 index 000000000..e8d462d91 --- /dev/null +++ b/wai/wai-rs/src/bin/wai_scene_vectors.rs @@ -0,0 +1,78 @@ +//! Conformance corpus for scene carriage (`live-conformance/scene/`, +//! extensions/scene-carriage.md §9). +//! +//! wai_scene_vectors gen write vectors.json and the corpus's files from the +//! worlds and documents built in code and the inputs +//! other corpora commit, which must match their pins +//! wai_scene_vectors verify the inputs match their pins, every file regenerates +//! byte for byte, no other file but README.md and +//! .gitattributes is there, and every vector gets the +//! outcome it states +//! +//! The generator and this verifier are one implementation. The corpus's second implementation +//! is `live-conformance/scene_verify.py`, written from the extension's text and sharing no code +//! with this binary. + +use std::collections::BTreeMap; +use std::path::Path; +use wai::scene_map::corpus; + +fn walk(root: &Path, rel: &str, out: &mut BTreeMap>) -> Result<(), String> { + let d = root.join(rel); + let entries = std::fs::read_dir(&d).map_err(|e| format!("read {}: {e}", d.display()))?; + for e in entries.flatten() { + let name = e.file_name().to_string_lossy().into_owned(); + let key = if rel.is_empty() { name.clone() } else { format!("{rel}/{name}") }; + if e.path().is_dir() { + walk(root, &key, out)?; + } else if key != "README.md" && key != ".gitattributes" { + out.insert(key, std::fs::read(e.path()).map_err(|err| format!("read {}: {err}", e.path().display()))?); + } + } + Ok(()) +} + +fn inputs(dir: &Path) -> BTreeMap> { + corpus::INPUT_FILES.iter().filter_map(|(p, _)| std::fs::read(dir.join(p)).ok().map(|b| (p.to_string(), b))).collect() +} + +fn die(e: impl std::fmt::Display) -> ! { + eprintln!("{e}"); + std::process::exit(1) +} + +fn main() { + let a: Vec = std::env::args().collect(); + match (a.get(1).map(String::as_str), a.get(2)) { + (Some("gen"), Some(d)) => { + let dir = Path::new(d); + let inp = inputs(dir); + let bad = corpus::check_inputs(&inp); + if !bad.is_empty() { + die(format!("the inputs {bad:?} are missing or do not match their pins in corpus::INPUT_FILES")); + } + for (name, body) in corpus::generate(&inp) { + let p = dir.join(&name); + if let Some(parent) = p.parent() { + std::fs::create_dir_all(parent).unwrap_or_else(|e| die(format!("mkdir {}: {e}", parent.display()))); + } + std::fs::write(&p, body).unwrap_or_else(|e| die(format!("write {}: {e}", p.display()))); + } + println!("wrote the scene corpus to {d}"); + } + (Some("verify"), Some(d)) => { + let dir = Path::new(d); + let mut files = BTreeMap::new(); + walk(dir, "", &mut files).unwrap_or_else(|e| die(e)); + let bad = corpus::verify(&files, &inputs(dir)); + if !bad.is_empty() { + for b in &bad { + eprintln!("FAIL {b}"); + } + std::process::exit(1); + } + println!("scene corpus: every file regenerates byte for byte and every vector reaches its outcome"); + } + _ => die("usage: wai_scene_vectors gen|verify "), + } +} diff --git a/wai/wai-rs/src/scene_map.rs b/wai/wai-rs/src/scene_map.rs new file mode 100644 index 000000000..d61b0d4a9 --- /dev/null +++ b/wai/wai-rs/src/scene_map.rs @@ -0,0 +1,529 @@ +//! Scene carriage (`extensions/scene-carriage.md`): WAI worlds, audio scenes, their assets and +//! timed media mapped onto ISO/IEC 23090-14 scene description — a glTF 2.0 document with the +//! MPEG extensions — and onto a USD layer in the text format of the USD Core Specification +//! v1.0.1, in both directions. +//! +//! - A [`Bundle`] is the WAI side (§2): at most one world (`WWLD`), at most one audio scene +//! (`WSCN`), and a list of timed media items, each a WAI object named by its content hash. +//! - [`gltf::export`] and [`usda::export`] write a bundle as a document, byte for byte (§4, §6): +//! what a player of that format presents, and the bundle's own bytes carried exactly. +//! - [`gltf::import`] and [`usda::import`] read a document back: one WAI wrote, to the same +//! bundle, refused unless re-exporting it gives the document's bytes; one WAI did not +//! write, to a new bundle under the rules of §5 and §7, refused where a value would not +//! carry exactly. +//! +//! The corpus is `live-conformance/scene/` ([`corpus`]); its second implementation is +//! `live-conformance/scene_verify.py`. + +pub mod corpus; +pub mod gltf; +pub mod num; +pub mod usda; + +use crate::world::fixed::Fx; + +/// The mapping's profile identifier, carried in every document it writes (§2.3). +pub const PROFILE: &str = "wai-scene/1"; +/// The URI scheme prefix that names a WAI object by its content hash (§2.2). +pub const ASSET_URN: &str = "urn:wai:asset:"; +/// The media type of a WAI envelope (http-delivery §3). +pub const MEDIA_TYPE: &str = "application/wai"; + +/// Every refusal code of §8, in its table's order. +pub const CODES: &[&str] = &[ + "world_malformed", + "audio_malformed", + "media_malformed", + "not_presentable", + "document_malformed", + "carried_malformed", + "presentation_mismatch", + "usd_not_read", + "transform_not_carried", + "position_not_exact", + "animation_not_carried", + "time_not_exact", + "gain_not_exact", + "audio_not_object", + "media_not_wai", + "asset_not_wai", + "contract_absent", + "sim_not_importable", +]; + +/// Why a bundle or a document was refused: a code of §8 and the particulars. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Refusal { + pub code: &'static str, + pub why: String, +} + +impl Refusal { + pub fn new(code: &'static str, why: impl Into) -> Self { + debug_assert!(CODES.contains(&code), "{code} is not a §8 code"); + Refusal { code, why: why.into() } + } +} + +impl std::fmt::Display for Refusal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}: {}", self.code, self.why) + } +} + +impl std::error::Error for Refusal {} + +pub(crate) fn refuse(code: &'static str, why: impl Into) -> Result { + Err(Refusal::new(code, why)) +} + +/// A timed media item (§2.2): a WAI object by its content hash, its capability, a name, and, +/// for a picture or a video, its width and height. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MediaItem { + pub name: String, + pub asset: [u8; 32], + pub capability: String, + pub size: Option<(u32, u32)>, +} + +/// The WAI side of the mapping (§2.1). +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Bundle { + /// A `WWLD` payload. + pub world: Option>, + /// A `WSCN` payload. + pub audio: Option>, + pub media: Vec, +} + +/// What an import of a document WAI did not write reports beside the bundle: the +/// presentation it did not carry (§5.3, §7.2). +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Imported { + pub bundle: Bundle, + pub dropped: Vec, +} + +/// What a document WAI did not write needs from its reader to become WAI bytes (§5.3): the +/// key that mints its node ids and the contracts of the world and the audio scene it yields. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ImportParams { + pub actor: [u8; 32], + pub world_contract: Option>, + pub audio_contract: Option>, +} + +pub(crate) fn hex(b: &[u8]) -> String { + b.iter().map(|x| format!("{x:02x}")).collect() +} + +pub(crate) fn unhex(s: &str) -> Option> { + if s.len() % 2 != 0 || !s.bytes().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) { + return None; + } + (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok()).collect() +} + +/// `urn:wai:asset:<64 lowercase hexadecimal digits>` as a content hash. +pub(crate) fn parse_urn(s: &str) -> Option<[u8; 32]> { + let h = s.strip_prefix(ASSET_URN)?; + if h.len() != 64 { + return None; + } + unhex(h)?.try_into().ok() +} + +/// A capability a media item may name (§2.2): `wai.video.`, `wai.audio.` or `wai.image.`, then +/// the capability grammar of http-delivery §4.1. +pub(crate) fn media_capability_ok(c: &str) -> bool { + let Some(rest) = c.strip_prefix("wai.") else { return false }; + let kind_ok = rest.starts_with("video.") || rest.starts_with("audio.") || rest.starts_with("image."); + let mut segs = rest.split('.'); + let first = segs.next().unwrap_or(""); + kind_ok + && !first.is_empty() + && first.bytes().all(|b| b.is_ascii_lowercase() || b.is_ascii_digit()) + && rest.contains('.') + && segs.all(|s| !s.is_empty() && s.bytes().all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_')) +} + +/// Whether a capability names pictures (a width and a height are due). +pub(crate) fn is_picture(c: &str) -> bool { + c.starts_with("wai.video.") || c.starts_with("wai.image.") +} + +/// §2.2: a media item's rules. +pub(crate) fn check_media(m: &MediaItem) -> Result<(), Refusal> { + if !media_capability_ok(&m.capability) { + return refuse("media_malformed", format!("{:?}: capability {:?}", m.name, m.capability)); + } + match (is_picture(&m.capability), m.size) { + (true, Some((w, h))) if (1..=65535).contains(&w) && (1..=65535).contains(&h) => Ok(()), + (false, None) => Ok(()), + _ => refuse("media_malformed", format!("{:?}: a width and height of 1 to 65535 for pictures and video, none for audio", m.name)), + } +} + +// --------------------------------------------------------------------------------------- +// The parts of WWLD and WSCN the mapping reads (§2.1) +// --------------------------------------------------------------------------------------- + +/// A section-table container (`WWLD`, `WSCN`): its sections by kind. Unknown kinds are kept +/// and ignored; a section out of bounds or a kind twice is refused. +pub(crate) fn sections(b: &[u8], magic: &[u8; 4], code: &'static str) -> Result>, Refusal> { + if b.len() < 6 || &b[..4] != magic { + return refuse(code, "no magic"); + } + let n = u16::from_le_bytes([b[4], b[5]]) as usize; + let base = 6 + 9 * n; + if base > b.len() { + return refuse(code, "the section table runs past the end"); + } + let mut out = std::collections::BTreeMap::new(); + for i in 0..n { + let e = &b[6 + 9 * i..6 + 9 * (i + 1)]; + let off = u32::from_le_bytes(e[1..5].try_into().unwrap()) as usize; + let len = u32::from_le_bytes(e[5..9].try_into().unwrap()) as usize; + let end = base.checked_add(off).and_then(|s| s.checked_add(len)).filter(|&x| x <= b.len()); + let Some(end) = end else { return refuse(code, format!("section {} runs past the end", e[0])) }; + if out.insert(e[0], b[base + off..end].to_vec()).is_some() { + return refuse(code, format!("section {} twice", e[0])); + } + } + Ok(out) +} + +/// One node of a world's tick-0 graph: its id and its state bytes. +pub(crate) struct GraphNode { + pub id: [u8; 16], + pub state: Vec, +} + +/// The world as the mapping reads it (§2.1). +pub(crate) struct WorldView { + pub sim: String, + pub nodes: Vec, + pub assets: Vec<(Vec, u8, String)>, +} + +pub(crate) fn read_world(b: &[u8]) -> Result { + let s = sections(b, b"WWLD", "world_malformed")?; + let (Some(contract), Some(graph)) = (s.get(&1), s.get(&2)) else { + return refuse("world_malformed", "no contract or graph section"); + }; + let c = crate::strict_json::read(contract).map_err(|e| Refusal::new("world_malformed", format!("contract: {e}")))?; + let Some(sim) = c.get("sim").and_then(|v| v.as_str()).map(str::to_string) else { + return refuse("world_malformed", "the contract names no sim"); + }; + if graph.len() < 4 { + return refuse("world_malformed", "the graph is truncated"); + } + let n = u32::from_le_bytes(graph[..4].try_into().unwrap()) as usize; + let mut at = 4usize; + let mut nodes: Vec = Vec::new(); + for _ in 0..n { + if at + 20 > graph.len() { + return refuse("world_malformed", "the graph is truncated"); + } + let id: [u8; 16] = graph[at..at + 16].try_into().unwrap(); + let len = u32::from_le_bytes(graph[at + 16..at + 20].try_into().unwrap()) as usize; + at += 20; + if at + len > graph.len() { + return refuse("world_malformed", "the graph is truncated"); + } + if nodes.last().is_some_and(|p| p.id >= id) { + return refuse("world_malformed", "the graph's nodes are not in ascending id order"); + } + nodes.push(GraphNode { id, state: graph[at..at + len].to_vec() }); + at += len; + } + if at != graph.len() { + return refuse("world_malformed", "bytes follow the graph's last node"); + } + let assets = match s.get(&3) { + Some(a) => read_assets(a, "world_malformed")?, + None => Vec::new(), + }; + for nd in &nodes { + position(&sim, &nd.state)?; + } + Ok(WorldView { sim, nodes, assets }) +} + +/// The asset table of interactive-worlds §4.1. +pub(crate) fn read_assets(a: &[u8], code: &'static str) -> Result, u8, String)>, Refusal> { + if a.len() < 4 { + return refuse(code, "the asset table is truncated"); + } + let n = u32::from_le_bytes(a[..4].try_into().unwrap()) as usize; + let mut at = 4usize; + let mut out = Vec::new(); + for _ in 0..n { + if at + 37 > a.len() { + return refuse(code, "the asset table is truncated"); + } + let hash = a[at..at + 32].to_vec(); + let role = a[at + 32]; + let nl = u32::from_le_bytes(a[at + 33..at + 37].try_into().unwrap()) as usize; + at += 37; + if at + nl > a.len() { + return refuse(code, "the asset table is truncated"); + } + let name = String::from_utf8(a[at..at + nl].to_vec()).map_err(|_| Refusal::new(code, "an asset name that is not UTF-8"))?; + at += nl; + out.push((hash, role, name)); + } + if at != a.len() { + return refuse(code, "bytes follow the asset table"); + } + Ok(out) +} + +/// A node's position (§3.4), for the sims whose state begins with one; `None` for the others. +pub(crate) fn position(sim: &str, state: &[u8]) -> Result, Refusal> { + let take = |b: &[u8]| -> Result<[i64; 3], Refusal> { + if b.len() < 24 { + return refuse("world_malformed", "a node state shorter than its position"); + } + Ok([0, 8, 16].map(|o| i64::from_le_bytes(b[o..o + 8].try_into().unwrap()))) + }; + match sim { + "wai.world.sim.kinematic0" | "wai.world.sim.ballistic0" => take(state).map(Some), + "wai.world.sim.truss0" => match state.first() { + Some(0) => take(&state[1..]).map(Some), + Some(_) => Ok(None), + None => refuse("world_malformed", "an empty truss node"), + }, + _ => Ok(None), + } +} + +/// One key of an audio object: its sample and position. +pub(crate) struct Key { + pub sample: u64, + pub pos: [i64; 3], +} + +/// An audio object as the mapping reads it. +pub(crate) struct ObjectView { + pub source: [u8; 32], + pub keys: Vec, +} + +/// The audio scene as the mapping reads it (§2.1). +pub(crate) struct AudioView { + pub sample_rate: u64, + pub duration: u64, + pub objects: Vec, +} + +pub(crate) fn read_audio(b: &[u8]) -> Result { + let s = sections(b, b"WSCN", "audio_malformed")?; + let (Some(contract), Some(objs)) = (s.get(&1), s.get(&2)) else { + return refuse("audio_malformed", "no contract or object table"); + }; + let c = crate::strict_json::read(contract).map_err(|e| Refusal::new("audio_malformed", format!("contract: {e}")))?; + let rate = c.get("sample_rate").and_then(|v| v.as_u64()).filter(|&r| (1..=u64::from(u32::MAX)).contains(&r)); + let dur = c.get("duration").and_then(|v| v.as_u64()); + let (Some(sample_rate), Some(duration)) = (rate, dur) else { + return refuse("audio_malformed", "the contract lacks a sample_rate of 1 to 2^32 − 1 or a duration"); + }; + let rd = |at: usize, n: usize| -> Result<&[u8], Refusal> { + objs.get(at..at + n).ok_or_else(|| Refusal::new("audio_malformed", "the object table is truncated")) + }; + let n = u32::from_le_bytes(rd(0, 4)?.try_into().unwrap()) as usize; + let mut at = 4usize; + let mut objects = Vec::new(); + for _ in 0..n { + let source: [u8; 32] = rd(at, 32)?.try_into().unwrap(); + let nk = u16::from_le_bytes(rd(at + 40, 2)?.try_into().unwrap()) as usize; + at += 42; + let mut keys = Vec::with_capacity(nk); + for _ in 0..nk { + let k = rd(at, 32)?; + let sample = u64::from_le_bytes(k[..8].try_into().unwrap()); + let pos = [8, 16, 24].map(|o| i64::from_le_bytes(k[o..o + 8].try_into().unwrap())); + keys.push(Key { sample, pos }); + at += 32; + } + objects.push(ObjectView { source, keys }); + } + if at != objs.len() { + return refuse("audio_malformed", "bytes follow the object table"); + } + if let Some(a) = s.get(&3) { + read_assets(a, "audio_malformed")?; + } + if let Some(seal) = s.get(&4) + && seal.len() != 104 + { + return refuse("audio_malformed", "a seal that is not 104 bytes"); + } + Ok(AudioView { sample_rate, duration, objects }) +} + +/// The canonical JSON text of a bundle's media list (§2.3): each item an object of `name`, +/// `asset`, `capability`, and for pictures `width` and `height`, in that order. +pub(crate) fn media_json(media: &[MediaItem]) -> String { + let items: Vec = media + .iter() + .map(|m| { + let mut s = format!( + "{{\"name\":{},\"asset\":\"{}\",\"capability\":{}", + crate::strict_json::quote(&m.name), + hex(&m.asset), + crate::strict_json::quote(&m.capability) + ); + if let Some((w, h)) = m.size { + s += &format!(",\"width\":{w},\"height\":{h}"); + } + s + "}" + }) + .collect(); + format!("[{}]", items.join(",")) +} + +/// Read a media list written as [`media_json`] writes it, in the JSON value `v`. +pub(crate) fn media_from_json(v: &crate::strict_json::Json) -> Option> { + let mut out = Vec::new(); + for it in v.as_arr()? { + let m = it.as_obj()?; + let keys: Vec<&str> = m.iter().map(|(k, _)| k.as_str()).collect(); + let pic = keys.len() == 5; + if keys != ["name", "asset", "capability"] && keys != ["name", "asset", "capability", "width", "height"] { + return None; + } + let name = it.get("name")?.as_str()?.to_string(); + let asset: [u8; 32] = unhex(it.get("asset")?.as_str()?)?.try_into().ok()?; + let capability = it.get("capability")?.as_str()?.to_string(); + let size = if pic { + Some((u32::try_from(it.get("width")?.as_u64()?).ok()?, u32::try_from(it.get("height")?.as_u64()?).ok()?)) + } else { + None + }; + out.push(MediaItem { name, asset, capability, size }); + } + Some(out) +} + +/// §2.1 over a whole bundle, before anything is written. +pub(crate) fn check_bundle(b: &Bundle) -> Result<(Option, Option), Refusal> { + let w = b.world.as_deref().map(read_world).transpose()?; + let a = b.audio.as_deref().map(read_audio).transpose()?; + for m in &b.media { + check_media(m)?; + } + Ok((w, a)) +} + +/// The Q32.32 sum of two positions, or `None` past the range. +pub(crate) fn add(a: [i64; 3], b: [i64; 3]) -> Option<[i64; 3]> { + Some([a[0].checked_add(b[0])?, a[1].checked_add(b[1])?, a[2].checked_add(b[2])?]) +} + +/// The bytes of a kinematic0 node at `pos`, at rest, scalar 0 (§5.3). +pub(crate) fn kinematic_state(pos: [i64; 3]) -> Vec { + let mut v = Vec::with_capacity(56); + for x in pos.into_iter().chain([0, 0, 0, 0]) { + v.extend_from_slice(&x.to_le_bytes()); + } + v +} + +/// A `WWLD` payload of a contract and tick-0 nodes, no assets, an empty op log (§5.3). +pub(crate) fn world_bytes(contract: &[u8], nodes: &mut [([u8; 16], Vec)], assets: &[(Vec, u8, String)]) -> Vec { + nodes.sort_by(|a, b| a.0.cmp(&b.0)); + let mut graph = (nodes.len() as u32).to_le_bytes().to_vec(); + for (id, st) in nodes.iter() { + graph.extend_from_slice(id); + graph.extend_from_slice(&(st.len() as u32).to_le_bytes()); + graph.extend_from_slice(st); + } + let mut secs: Vec<(u8, Vec)> = vec![(1, contract.to_vec()), (2, graph)]; + if !assets.is_empty() { + secs.push((3, asset_table(assets))); + } + secs.push((4, Vec::new())); + container(b"WWLD", &secs) +} + +pub(crate) fn asset_table(assets: &[(Vec, u8, String)]) -> Vec { + let mut a = (assets.len() as u32).to_le_bytes().to_vec(); + for (h, r, n) in assets { + a.extend_from_slice(h); + a.push(*r); + a.extend_from_slice(&(n.len() as u32).to_le_bytes()); + a.extend_from_slice(n.as_bytes()); + } + a +} + +/// A section-table container as interactive-worlds §4 lays it out. +pub(crate) fn container(magic: &[u8; 4], secs: &[(u8, Vec)]) -> Vec { + let mut out = magic.to_vec(); + out.extend_from_slice(&(secs.len() as u16).to_le_bytes()); + let mut off = 0u32; + for (k, body) in secs { + out.push(*k); + out.extend_from_slice(&off.to_le_bytes()); + out.extend_from_slice(&(body.len() as u32).to_le_bytes()); + off += body.len().div_ceil(8) as u32 * 8; + } + for (_, body) in secs { + out.extend_from_slice(body); + out.extend(std::iter::repeat_n(0u8, body.len().div_ceil(8) * 8 - body.len())); + } + out +} + +/// A `WSCN` payload of a contract, objects of gain 1 and assets (§5.3). +pub(crate) fn audio_bytes(contract: &[u8], objects: &[([u8; 32], Vec<(u64, [i64; 3])>)], assets: &[(Vec, u8, String)]) -> Vec { + let mut t = (objects.len() as u32).to_le_bytes().to_vec(); + for (src, keys) in objects { + t.extend_from_slice(src); + t.extend_from_slice(&Fx::ONE.to_bits().to_le_bytes()); + t.extend_from_slice(&(keys.len() as u16).to_le_bytes()); + for (s, p) in keys { + t.extend_from_slice(&s.to_le_bytes()); + for x in p { + t.extend_from_slice(&x.to_le_bytes()); + } + } + } + let mut secs: Vec<(u8, Vec)> = vec![(1, contract.to_vec()), (2, t)]; + if !assets.is_empty() { + secs.push((3, asset_table(assets))); + } + container(b"WSCN", &secs) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn media_capabilities_follow_the_grammar() { + for ok in ["wai.video.av1", "wai.audio.flac", "wai.image.png", "wai.video.int_motion"] { + assert!(media_capability_ok(ok), "{ok}"); + } + for bad in ["wai.world.scene", "video.av1", "wai.video.", "wai.video.AV1", "wai.video..x", "wai.audio"] { + assert!(!media_capability_ok(bad), "{bad}"); + } + assert_eq!(parse_urn(&format!("{ASSET_URN}{}", "ab".repeat(32))), Some([0xab; 32])); + assert_eq!(parse_urn(&format!("{ASSET_URN}{}", "AB".repeat(32))), None); + } + + #[test] + fn the_containers_this_module_writes_are_the_ones_wai_rs_reads() { + let contract = br#"{"checkpoint_every":600,"numeric":"wai.det.fixed64","resolve":{"default":"wai.world.resolve.lww"},"rng":{"alg":"xoshiro256++","seed":"0000000000000000000000000000000000000000000000000000000000000000"},"sim":"wai.world.sim.kinematic0","tick_rate":60}"#; + let mut nodes = vec![([2u8; 16], kinematic_state([1, 2, 3])), ([1u8; 16], kinematic_state([4, 5, 6]))]; + let w = world_bytes(contract, &mut nodes, &[(vec![7; 32], 0, "cube.glb".into())]); + let c = crate::world::WorldContainer::from_bytes(&w).unwrap(); + assert_eq!(c.to_bytes(), w, "the world container wai-rs writes from it is the same bytes"); + let ac = br#"{"distance":"wai.audio.dist.inverse_clamped","duration":256,"floor_layout":"stereo","numeric":"wai.det.fixed64","pan_law":"wai.audio.pan.sqrt_constpower","sample_rate":48000}"#; + let a = audio_bytes(ac, &[([9; 32], vec![(0, [1, 2, 3]), (100, [4, 5, 6])])], &[(vec![9; 32], 0, "tone".into())]); + let s = crate::audio_scene::SpatialScene::from_bytes(&a).unwrap(); + assert_eq!(s.to_bytes(), a); + } +} diff --git a/wai/wai-rs/src/scene_map/corpus.rs b/wai/wai-rs/src/scene_map/corpus.rs new file mode 100644 index 000000000..ba0a7c7b3 --- /dev/null +++ b/wai/wai-rs/src/scene_map/corpus.rs @@ -0,0 +1,415 @@ +//! The conformance corpus of `extensions/scene-carriage.md` §9: `live-conformance/scene/`. +//! +//! [`generate`] writes it from worlds and documents it builds in code and from the audio scenes +//! and worlds other corpora commit, each pinned here by its SHA-256 ([`INPUT_FILES`]). Every +//! value is a constant of this module or a SHA-256 output of a label, so the corpus is the same +//! bytes on every machine. Each vector states its outcome; the generator asserts that this +//! module reaches it before writing it, and that the corpus gives every code of §8. +//! +//! [`verify`] checks a corpus directory against this module. +//! `live-conformance/scene_verify.py` checks the same files from the extension's text, sharing +//! no code with this module. + +use std::collections::BTreeMap; + +use serde_json::{json, Value}; + +use super::{gltf, hex, kinematic_state, usda, world_bytes, Bundle, ImportParams, Imported, MediaItem, CODES}; +use crate::world::fixed::Fx; + +/// The files of other corpora the corpus reads: `(path from the corpus directory, SHA-256)`. +pub const INPUT_FILES: &[(&str, &str)] = &[ + ("../../world-conformance/vectors/scene.wwld", "41b89729f87c308653d4359522280d7255c36d5b2d5300eafa8b1b7f7b76b3f0"), + ("../../world-conformance/vectors/basic_kinematics.wwld", "c90b8a03ea11ab87428b2255f402c17eb26a89c625b59d8d753afad143707491"), + ("../../audio-conformance/vectors/moving.wscn", "f799afa2ade71684f0fb1bf5eeb6413950228906551ba75be477b24ace125010"), + ("../../audio-conformance/vectors/distance.wscn", "fcb946a54ef3b79d7f7ab14b98777800ff90e06309f7f2a5f9422bfc32f13d0b"), + ("../../audio-conformance/vectors/stereo_pair.wscn", "1ac272f61ce80b691de745bd9af94db2aaab20087cade54128c8886a53cd7185"), +]; + +/// The world contract every world the corpus builds carries (interactive-worlds §6). +pub fn contract(sim: &str) -> Vec { + format!( + r#"{{"checkpoint_every":600,"numeric":"wai.det.fixed64","resolve":{{"default":"wai.world.resolve.lww"}},"rng":{{"alg":"xoshiro256++","seed":"{}"}},"sim":"{sim}","tick_rate":60}}"#, + "00".repeat(32) + ) + .into_bytes() +} + +/// The audio contract the corpus's imports carry (spatial-audio §2.1). +pub const AUDIO_CONTRACT: &[u8] = br#"{"distance":"wai.audio.dist.inverse_clamped","duration":48000,"floor_layout":"stereo","numeric":"wai.det.fixed64","pan_law":"wai.audio.pan.sqrt_constpower","sample_rate":48000}"#; + +fn label(s: &str) -> [u8; 32] { + crate::pin::sha256(format!("wai:scene/corpus\x00{s}").as_bytes()) +} + +fn fx(v: f64) -> i64 { + Fx::from_bits((v * 4294967296.0) as i64).to_bits() +} + +fn node_id(i: u64) -> [u8; 16] { + crate::world::mint_node_id(&label("publisher"), i) +} + +fn state(vals: &[i64]) -> Vec { + vals.iter().flat_map(|v| v.to_le_bytes()).collect() +} + +/// The worlds the corpus builds: kinematic0, ballistic0 and truss0 nodes at tick 0. +fn built_worlds() -> Vec<(&'static str, Vec)> { + let mesh = label("asset/cube.glb"); + let amb = label("asset/ambience.flac"); + let assets = vec![(mesh.to_vec(), 0u8, "cube.glb".to_string()), (amb.to_vec(), 1u8, "ambience.flac".to_string())]; + let mut k = vec![ + (node_id(1), kinematic_state([fx(1.0), fx(2.0), fx(3.0)])), + (node_id(2), kinematic_state([fx(0.5), fx(-2.25), 0])), + (node_id(3), state(&[1431655765, fx(10.0), fx(-7.75), fx(1.0), 0, 0, 7])), + ]; + let mut b = vec![ + (node_id(4), state(&[fx(0.0), fx(5.0), fx(0.0), 0, 0, 0, fx(0.5), fx(0.75)])), + (node_id(5), state(&[fx(-3.0), fx(1.5), fx(2.0), fx(1.0), 0, 0, fx(0.25), fx(0.5)])), + ]; + let p1 = node_id(6); + let p2 = node_id(7); + let mut member = vec![1u8]; + member.extend_from_slice(&p1); + member.extend_from_slice(&p2); + member.extend(state(&[fx(2.0), fx(0.125)])); + let particle = |p: [i64; 3]| { + let mut v = vec![0u8]; + v.extend(state(&[p[0], p[1], p[2], 0, 0, 0, fx(1.0)])); + v + }; + let mut t = vec![(p1, particle([0, fx(2.0), 0])), (p2, particle([fx(2.0), fx(2.0), 0])), (node_id(8), member)]; + vec![ + ("kinematic", world_bytes(&contract("wai.world.sim.kinematic0"), &mut k, &assets)), + ("ballistic", world_bytes(&contract("wai.world.sim.ballistic0"), &mut b, &[])), + ("truss", world_bytes(&contract("wai.world.sim.truss0"), &mut t, &[])), + ] +} + +fn media(name: &str, asset: [u8; 32], cap: &str, size: Option<(u32, u32)>) -> MediaItem { + MediaItem { name: name.into(), asset, capability: cap.into(), size } +} + +fn media_v(m: &[MediaItem]) -> Value { + Value::Array( + m.iter() + .map(|x| { + let mut o = json!({"name": x.name, "asset": hex(&x.asset), "capability": x.capability}); + if let Some((w, h)) = x.size { + o["width"] = json!(w); + o["height"] = json!(h); + } + o + }) + .collect(), + ) +} + +/// The hash of the source of the committed `moving.wscn`. +fn first_source(wscn: &[u8]) -> [u8; 32] { + let v = super::read_audio(wscn).expect("a committed audio scene"); + v.objects[0].source +} + +fn params_v(p: &Option) -> Value { + match p { + None => Value::Null, + Some(p) => json!({ + "actor": hex(&p.actor), + "world_contract": p.world_contract.as_ref().map(|c| String::from_utf8(c.clone()).unwrap()), + "audio_contract": p.audio_contract.as_ref().map(|c| String::from_utf8(c.clone()).unwrap()), + }), + } +} + +/// The foreign glTF documents: `(name, text)`. +fn foreign_gltf(src: [u8; 32], video: [u8; 32]) -> Vec<(&'static str, String)> { + let urn = |h: &[u8; 32]| format!("urn:wai:asset:{}", hex(h)); + let nodes = r#"{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0,3]}],"nodes":[{"name":"base","translation":[1,0,-2],"children":[1,2]},{"name":"lamp","translation":[0.5,2,0]},{"name":"crate","mesh":0,"translation":[-1.25,0,0.75]},{"name":"eye","camera":0,"translation":[0,1.5,4]}],"meshes":[{"primitives":[{"attributes":{"POSITION":0}}]}],"accessors":[{"componentType":5126,"count":3,"type":"VEC3","min":[0,0,0],"max":[1,1,0]}],"cameras":[{"type":"perspective","perspective":{"yfov":1,"znear":0.1}}]}"#.to_string(); + // Times 0, 0.5, 1 s at 48 kHz: samples 0, 24000, 48000. Positions -2, 0 and 2 on x. + let times: Vec = vec![0.0, 0.5, 1.0]; + let poss: Vec<[f32; 3]> = vec![[-2.0, 0.0, -1.0], [0.0, 0.0, -1.0], [2.0, 0.0, -1.0]]; + let mut data: Vec = times.iter().flat_map(|t| t.to_le_bytes()).collect(); + for p in &poss { + for x in p { + data.extend_from_slice(&x.to_le_bytes()); + } + } + let b64 = gltf::base64(&data); + let audio = |src_uri: &str, mime: &str, interp: &str, t1: &str, pregain: &str, ty: &str| { + let times_b64 = if t1 == "0.5" { + b64.clone() + } else { + let mut d = data.clone(); + d[4..8].copy_from_slice(&0.1f32.to_le_bytes()); + gltf::base64(&d) + }; + format!( + r#"{{"asset":{{"version":"2.0"}},"extensionsUsed":["MPEG_accessor_timed","MPEG_audio_spatial","MPEG_buffer_circular","MPEG_media","MPEG_texture_video"],"extensions":{{"MPEG_media":{{"media":[{{"name":"voice","autoplay":true,"alternatives":[{{"uri":"{src_uri}","mimeType":"{mime}"}}]}},{{"name":"screen","startTime":0,"alternatives":[{{"uri":"https://example.invalid/screen.mp4","mimeType":"video/mp4"}},{{"uri":"{v}","mimeType":"application/wai; codecs=\"wai1.video.av1\""}}]}}]}}}},"scene":0,"scenes":[{{"nodes":[0]}}],"nodes":[{{"name":"room","translation":[0,1,0],"children":[1]}},{{"name":"speaker","translation":[-2,0,-1],"extensions":{{"MPEG_audio_spatial":{{"sources":[{{"id":0,"type":"{ty}","accessors":[2]{pregain}}}]}}}}}}],"buffers":[{{"byteLength":{len},"uri":"data:application/octet-stream;base64,{times_b64}"}},{{"byteLength":4096,"extensions":{{"MPEG_buffer_circular":{{"media":0}}}}}},{{"byteLength":6912,"extensions":{{"MPEG_buffer_circular":{{"media":1}}}}}}],"bufferViews":[{{"buffer":0,"byteLength":12}},{{"buffer":0,"byteOffset":12,"byteLength":36}},{{"buffer":1,"byteLength":4096}},{{"buffer":2,"byteLength":6912}}],"accessors":[{{"bufferView":0,"componentType":5126,"count":3,"type":"SCALAR","min":[0],"max":[1]}},{{"bufferView":1,"componentType":5126,"count":3,"type":"VEC3"}},{{"bufferView":2,"componentType":5126,"count":1024,"type":"SCALAR","extensions":{{"MPEG_accessor_timed":{{"immutable":true}}}}}},{{"bufferView":3,"componentType":5121,"normalized":true,"count":2304,"type":"VEC3","extensions":{{"MPEG_accessor_timed":{{"immutable":true}}}}}}],"animations":[{{"channels":[{{"sampler":0,"target":{{"node":1,"path":"translation"}}}}],"samplers":[{{"input":0,"interpolation":"{interp}","output":1}}]}}],"textures":[{{"extensions":{{"MPEG_texture_video":{{"accessor":3,"width":48,"height":16,"format":"RGB"}}}}}}]}}"#, + v = urn(&video), + len = data.len(), + ) + }; + let wai_mime = r#"application/wai; codecs=\"wai1.audio.opus\""#; + vec![ + ("nodes", nodes.clone()), + ("audio", audio(&urn(&src), wai_mime, "LINEAR", "0.5", "", "Object")), + ("rotation", nodes.replacen(r#""name":"lamp","translation":[0.5,2,0]"#, r#""name":"lamp","translation":[0.5,2,0],"rotation":[0,0.7071068,0,0.7071068]"#, 1)), + ("inexact", nodes.replacen("[0.5,2,0]", "[0.5,1e-10,0]", 1)), + ("step", audio(&urn(&src), wai_mime, "STEP", "0.5", "", "Object")), + ("time", audio(&urn(&src), wai_mime, "LINEAR", "0.1", "", "Object")), + ("pregain", audio(&urn(&src), wai_mime, "LINEAR", "0.5", r#","pregain":6"#, "Object")), + ("hoa", audio(&urn(&src), wai_mime, "LINEAR", "0.5", "", "HOA")), + ("https", audio("https://example.invalid/voice.ogg", "audio/ogg", "LINEAR", "0.5", "", "Object")), + ("not_json", nodes[..nodes.len() / 2].to_string()), + ("not_gltf2", nodes.replacen(r#""version":"2.0""#, r#""version":"1.0""#, 1)), + ] +} + +/// The foreign USDA layers: `(name, text)`. +fn foreign_usda(mesh: [u8; 32]) -> Vec<(&'static str, String)> { + let layer = format!( + r#"#usda 1.0 +( + "A layer written outside WAI" + defaultPrim = "Set" + metersPerUnit = 1 + upAxis = "Y" +) + +def Xform "Set" ( + kind = "group" +) +{{ + double3 xformOp:translate = (10, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Pillar" # a pillar + {{ + double3 xformOp:translate = (0.5, 3, -0.25) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Cap" + {{ + double3 xformOp:translate = (0, 1.5, 0) + }} + }} + + def Mesh "Floor" + {{ + int[] faceVertexCounts = [3] + int[] faceVertexIndices = [0, 1, 2] + point3f[] points = [(0, 0, 0), (1, 0, 0), (0, 0, 1)] + }} + + /* an asset by its content hash */ + def "Statue" ( + prepend references = @urn:wai:asset:{}@ + ) + {{ + }} +}} +"#, + hex(&mesh) + ); + vec![ + ("layer", layer.clone()), + ("rotate", layer.replacen(" uniform token[] xformOpOrder = [\"xformOp:translate\"]\n\n def Xform \"Cap\"", " float3 xformOp:rotateXYZ = (0, 90, 0)\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateXYZ\"]\n\n def Xform \"Cap\"", 1)), + ("inexact", layer.replacen("(0, 1.5, 0)", "(0, 0.1, 0)", 1)), + ("asset_path", layer.replacen(&format!("@urn:wai:asset:{}@", hex(&mesh)), "@./statue.usda@", 1)), + ("variant", layer.replacen(" def Mesh \"Floor\"", " variantSet \"look\" = {\n \"plain\" {\n }\n }\n\n def Mesh \"Floor\"", 1)), + ("over", layer.replacen(" def Mesh \"Floor\"", " over \"Floor\"", 1)), + ("broken", layer[..layer.len() - 3].to_string()), + ] +} + +/// The inputs whose bytes are missing or do not match their pins. +pub fn check_inputs(inputs: &BTreeMap>) -> Vec { + INPUT_FILES.iter().filter(|(p, d)| inputs.get(*p).map(|b| crate::pin::sha256_hex(b)).as_deref() != Some(*d)).map(|(p, _)| p.to_string()).collect() +} + +/// Generate the corpus from `inputs` (by the paths of [`INPUT_FILES`]): file name → bytes. +pub fn generate(inputs: &BTreeMap>) -> BTreeMap> { + assert!(check_inputs(inputs).is_empty(), "the inputs must match their pins"); + let mut files: BTreeMap> = BTreeMap::new(); + let mut vectors: Vec = Vec::new(); + let mut seen: std::collections::BTreeSet = Default::default(); + let path_of = |name: &str| -> String { INPUT_FILES.iter().find(|(p, _)| p.ends_with(name)).expect("an input").0.to_string() }; + for (name, bytes) in built_worlds() { + files.insert(format!("inputs/{name}.wwld"), bytes); + } + let get = |files: &BTreeMap>, p: &str| -> Vec { + files.get(p).cloned().or_else(|| inputs.get(p).cloned()).unwrap_or_else(|| panic!("{p}")) + }; + let src = first_source(&inputs[&path_of("moving.wscn")]); + let video = label("asset/screen.av1"); + let amb = label("asset/ambience.flac"); + let screen = media("screen", video, "wai.video.av1", Some((64, 36))); + let bundles: Vec<(&str, Option, Option, Vec)> = vec![ + ("kinematic", Some("inputs/kinematic.wwld".into()), None, vec![screen.clone(), media("ambience", amb, "wai.audio.flac", None)]), + ("ballistic", Some("inputs/ballistic.wwld".into()), None, vec![]), + ("truss", Some("inputs/truss.wwld".into()), None, vec![]), + ("empty_scene", Some(path_of("scene.wwld")), None, vec![]), + ("replay", Some(path_of("basic_kinematics.wwld")), None, vec![]), + ("moving", None, Some(path_of("moving.wscn")), vec![media("tone", src, "wai.audio.flac", None)]), + ("distance", None, Some(path_of("distance.wscn")), vec![]), + ("stereo_pair", None, Some(path_of("stereo_pair.wscn")), vec![]), + ("full", Some("inputs/kinematic.wwld".into()), Some(path_of("moving.wscn")), vec![screen.clone(), media("tone", src, "wai.audio.flac", None)]), + ]; + let bundle_v = |w: &Option, a: &Option, m: &[MediaItem]| json!({"world": w, "audio": a, "media": media_v(m)}); + let mut push = |v: Value, seen: &mut std::collections::BTreeSet| { + if let Some(c) = v.get("code").and_then(Value::as_str) { + seen.insert(c.to_string()); + } + vectors.push(v); + }; + // Exports and their round trips. + for (name, w, a, m) in &bundles { + let b = Bundle { world: w.as_ref().map(|p| get(&files, p)), audio: a.as_ref().map(|p| get(&files, p)), media: m.clone() }; + for fmt in ["gltf", "usda"] { + let out = if fmt == "gltf" { gltf::export(&b) } else { usda::export(&b) }.unwrap_or_else(|e| panic!("{name}: {e}")); + let back = if fmt == "gltf" { gltf::import(&out, &ImportParams::default()) } else { usda::import(&out, &ImportParams::default()) }.unwrap(); + assert_eq!(back, Imported { bundle: b.clone(), dropped: vec![] }, "{name} {fmt}"); + let p = format!("out/{name}.{fmt}"); + files.insert(p.clone(), out); + push(json!({"name": format!("{name}_{fmt}_export"), "op": "export", "format": fmt, "bundle": bundle_v(w, a, m), "outcome": "accept", "file": p}), &mut seen); + push(json!({"name": format!("{name}_{fmt}_import"), "op": "import", "format": fmt, "file": p, "params": Value::Null, "outcome": "accept", "bundle": bundle_v(w, a, m), "dropped": []}), &mut seen); + } + } + // Bundles refused on export. + let mut bad_world = get(&files, "inputs/kinematic.wwld"); + { + // Swap the first two node ids of the graph: ascending order broken. + let secs = super::sections(&bad_world, b"WWLD", "world_malformed").unwrap(); + let g = &secs[&2]; + let at = bad_world.windows(g.len()).position(|w| w == g.as_slice()).unwrap(); + let first = at + 4; + let second = first + 16 + 4 + 56; + let (a, b): (Vec, Vec) = (bad_world[first..first + 16].to_vec(), bad_world[second..second + 16].to_vec()); + bad_world[first..first + 16].copy_from_slice(&b); + bad_world[second..second + 16].copy_from_slice(&a); + } + files.insert("inputs/neg/unordered.wwld".into(), bad_world); + let moving = get(&files, &path_of("moving.wscn")); + let mut truncated = moving.clone(); + { + let secs = super::sections(&moving, b"WSCN", "audio_malformed").unwrap(); + let t = &secs[&2]; + let at = moving.windows(t.len()).position(|w| w == t.as_slice()).unwrap(); + // Claim one more key for the first object than the table holds. + let nk = u16::from_le_bytes([moving[at + 44], moving[at + 45]]); + truncated[at + 44..at + 46].copy_from_slice(&(nk + 1).to_le_bytes()); + } + files.insert("inputs/neg/truncated.wscn".into(), truncated); + let descending = { + let v = super::read_audio(&moving).unwrap(); + let keys: Vec<(u64, [i64; 3])> = v.objects[0].keys.iter().rev().map(|k| (k.sample, k.pos)).collect(); + super::audio_bytes(&super::sections(&moving, b"WSCN", "audio_malformed").unwrap()[&1], &[(v.objects[0].source, keys)], &[]) + }; + files.insert("inputs/neg/descending.wscn".into(), descending); + let neg_bundles: Vec<(&str, Option<&str>, Option<&str>, Vec)> = vec![ + ("unordered_world", Some("inputs/neg/unordered.wwld"), None, vec![]), + ("truncated_audio", None, Some("inputs/neg/truncated.wscn"), vec![]), + ("descending_keys", None, Some("inputs/neg/descending.wscn"), vec![]), + ("world_capability", None, None, vec![media("thing", video, "wai.world.scene", None)]), + ("video_without_size", None, None, vec![media("screen", video, "wai.video.av1", None)]), + ]; + for (name, w, a, m) in &neg_bundles { + let b = Bundle { world: w.map(|p| get(&files, p)), audio: a.map(|p| get(&files, p)), media: m.clone() }; + for fmt in ["gltf", "usda"] { + let r = if fmt == "gltf" { gltf::export(&b) } else { usda::export(&b) }; + let code = r.expect_err(name).code; + push(json!({"name": format!("{name}_{fmt}"), "op": "export", "format": fmt, "bundle": bundle_v(&w.map(str::to_string), &a.map(str::to_string), m), "outcome": "refuse", "code": code}), &mut seen); + } + } + // Documents: WAI-written ones altered, and foreign ones. + let actor = label("actor"); + let full = |wc: Option>, ac: Option>| Some(ImportParams { actor, world_contract: wc, audio_contract: ac }); + let kin = contract("wai.world.sim.kinematic0"); + let both = full(Some(kin.clone()), Some(AUDIO_CONTRACT.to_vec())); + let mut docs: Vec<(String, &str, Vec, Option)> = Vec::new(); + let kg = files["out/kinematic.gltf"].clone(); + let ku = files["out/kinematic.usda"].clone(); + let s = String::from_utf8(kg.clone()).unwrap(); + docs.push(("changed".into(), "gltf", s.replacen("\"translation\":[1,2,3]", "\"translation\":[1,2,4]", 1).into_bytes(), None)); + let wpos = s.find("\"world\":\"").unwrap() + 9; + let mut bh = kg.clone(); + bh[wpos] = b'z'; + docs.push(("carried_bad_hex".into(), "gltf", bh, None)); + let su = String::from_utf8(ku.clone()).unwrap(); + docs.push(("changed".into(), "usda", su.replacen("(1, 2, 3)", "(1, 2, 4)", 1).into_bytes(), None)); + docs.push(("carried_bad_profile".into(), "usda", su.replacen("\"wai-scene/1\"", "\"wai-scene/9\"", 1).into_bytes(), None)); + for (n, t) in foreign_gltf(src, video) { + let p = if matches!(n, "nodes" | "rotation" | "inexact") { full(Some(kin.clone()), None) } else { both.clone() }; + docs.push((format!("foreign_{n}"), "gltf", t.into_bytes(), p)); + } + let nodes_doc = docs.iter().find(|d| d.0 == "foreign_nodes").unwrap().2.clone(); + docs.push(("foreign_nodes_no_contract".into(), "gltf", nodes_doc.clone(), full(None, None))); + docs.push(("foreign_nodes_ballistic".into(), "gltf", nodes_doc, full(Some(contract("wai.world.sim.ballistic0")), None))); + for (n, t) in foreign_usda(label("asset/statue.usd")) { + docs.push((format!("foreign_{n}"), "usda", t.into_bytes(), full(Some(kin.clone()), None))); + } + let layer_doc = docs.iter().find(|d| d.0 == "foreign_layer").unwrap().2.clone(); + docs.push(("foreign_layer_no_contract".into(), "usda", layer_doc, full(None, None))); + for (name, fmt, bytes, params) in docs { + let p = format!("docs/{name}.{fmt}"); + let r = if fmt == "gltf" { gltf::import(&bytes, ¶ms.clone().unwrap_or_default()) } else { usda::import(&bytes, ¶ms.clone().unwrap_or_default()) }; + files.insert(p.clone(), bytes); + let v = match r { + Ok(imp) => { + let w = imp.bundle.world.map(|b| { + let q = format!("imported/{name}_{fmt}.wwld"); + files.insert(q.clone(), b); + q + }); + let a = imp.bundle.audio.map(|b| { + let q = format!("imported/{name}_{fmt}.wscn"); + files.insert(q.clone(), b); + q + }); + json!({"name": format!("{name}_{fmt}"), "op": "import", "format": fmt, "file": p, "params": params_v(¶ms), "outcome": "accept", "bundle": bundle_v(&w, &a, &imp.bundle.media), "dropped": imp.dropped}) + } + Err(e) => json!({"name": format!("{name}_{fmt}"), "op": "import", "format": fmt, "file": p, "params": params_v(¶ms), "outcome": "refuse", "code": e.code}), + }; + push(v, &mut seen); + } + let missing: Vec<&&str> = CODES.iter().filter(|c| !seen.contains(**c)).collect(); + assert!(missing.is_empty(), "the corpus gives no vector the codes {missing:?}"); + let inputs_v: Vec = INPUT_FILES.iter().map(|(p, d)| json!({"path": p, "sha256": d})).collect(); + let files_v: Vec = files.iter().map(|(p, b)| json!({"file": p, "sha256": crate::pin::sha256_hex(b)})).collect(); + let doc = json!({ + "about": "the conformance corpus of extensions/scene-carriage.md §9, written by wai_scene_vectors gen", + "inputs": inputs_v, + "files": files_v, + "vectors": vectors, + }); + let mut text = serde_json::to_string_pretty(&doc).expect("json"); + text.push('\n'); + files.insert("vectors.json".into(), text.into_bytes()); + files +} + +/// Check a corpus directory's files against this module: every file regenerates byte for +/// byte and nothing else is there. Returns the failures. +pub fn verify(corpus: &BTreeMap>, inputs: &BTreeMap>) -> Vec { + let missing = check_inputs(inputs); + if !missing.is_empty() { + return vec![format!("inputs missing or not matching their pins: {missing:?}")]; + } + let want = generate(inputs); + let mut bad = Vec::new(); + for (p, b) in &want { + match corpus.get(p) { + Some(x) if x == b => {} + Some(_) => bad.push(format!("{p} does not regenerate byte for byte")), + None => bad.push(format!("{p} is missing")), + } + } + for p in corpus.keys() { + if !want.contains_key(p) { + bad.push(format!("{p} is not a file of the corpus")); + } + } + bad +} diff --git a/wai/wai-rs/src/scene_map/gltf.rs b/wai/wai-rs/src/scene_map/gltf.rs new file mode 100644 index 000000000..cb72919c4 --- /dev/null +++ b/wai/wai-rs/src/scene_map/gltf.rs @@ -0,0 +1,743 @@ +//! A bundle as an ISO/IEC 23090-14 scene description — a glTF 2.0 JSON document with the +//! MPEG extensions — and back (`extensions/scene-carriage.md` §4 and §5). + +use std::collections::BTreeMap; + +use super::num::{fx_to_f32, json_number, ratio_to_f32}; +use super::{ + add, audio_bytes, check_bundle, hex, kinematic_state, media_from_json, media_json, parse_urn, position, refuse, unhex, + world_bytes, Bundle, ImportParams, Imported, MediaItem, Refusal, ASSET_URN, MEDIA_TYPE, PROFILE, +}; +use crate::strict_json::{self, quote, Json}; + +/// The samples one frame of an audio source's timed accessor holds (§4.4). +pub const AUDIO_FRAME: u64 = 1024; + +/// A JSON value to write, its members in the order given. +enum J { + O(Vec<(&'static str, J)>), + A(Vec), + S(String), + I(u64), + F(f64), + B(bool), +} + +fn write(v: &J, o: &mut String) { + match v { + J::O(m) => { + o.push('{'); + for (i, (k, x)) in m.iter().enumerate() { + if i > 0 { + o.push(','); + } + o.push_str("e(k)); + o.push(':'); + write(x, o); + } + o.push('}'); + } + J::A(a) => { + o.push('['); + for (i, x) in a.iter().enumerate() { + if i > 0 { + o.push(','); + } + write(x, o); + } + o.push(']'); + } + J::S(s) => o.push_str("e(s)), + J::I(n) => o.push_str(&n.to_string()), + J::F(f) => o.push_str(&json_number(*f)), + J::B(b) => o.push_str(if *b { "true" } else { "false" }), + } +} + +fn vec3(p: [i64; 3]) -> J { + J::A(p.iter().map(|&x| J::F(f64::from(fx_to_f32(x)))).collect()) +} + +/// The `mimeType` of a media item's alternative (§4.4): `application/wai`, with the +/// `codecs` parameter of http-delivery §4 when the capability is known. +pub(crate) fn mime(cap: Option<&str>) -> String { + match cap { + Some(c) => format!("{MEDIA_TYPE}; codecs=\"wai1.{}\"", c.strip_prefix("wai.").unwrap_or(c)), + None => MEDIA_TYPE.to_string(), + } +} + +/// The capability a `mimeType` names, when it is `application/wai` with a `codecs` parameter +/// of one WAI element of one capability (§5.3 step 5). +pub(crate) fn mime_capability(m: &str) -> Option { + let rest = m.strip_prefix("application/wai; codecs=\"wai1.")?.strip_suffix('"')?; + let c = format!("wai.{rest}"); + super::media_capability_ok(&c).then_some(c) +} + +/// Write `bundle` as a glTF document (§4), byte for byte. +pub fn export(bundle: &Bundle) -> Result, Refusal> { + let (world, audio) = check_bundle(bundle)?; + let mut nodes: Vec = Vec::new(); + let mut used: Vec<&'static str> = Vec::new(); + // §4.2: the world's nodes at tick 0. + if let Some(w) = &world { + for n in &w.nodes { + let mut m: Vec<(&'static str, J)> = vec![("name", J::S(format!("node-{}", hex(&n.id))))]; + if let Some(p) = position(&w.sim, &n.state)? { + m.push(("translation", vec3(p))); + } + nodes.push(J::O(m)); + } + } + // §4.4: media items — the audio scene's sources first, then the bundle's other items. + let mut items: Vec<(String, [u8; 32], Option, Option<(u32, u32)>)> = Vec::new(); + let mut by_hash: BTreeMap<[u8; 32], &MediaItem> = BTreeMap::new(); + for m in &bundle.media { + by_hash.entry(m.asset).or_insert(m); + } + if let Some(a) = &audio { + for o in &a.objects { + if items.iter().all(|i| i.1 != o.source) { + let m = by_hash.get(&o.source); + let name = m.map(|m| m.name.clone()).unwrap_or_else(|| format!("source-{}", &hex(&o.source)[..16])); + items.push((name, o.source, m.map(|m| m.capability.clone()), None)); + } + } + } + let n_sources = items.len(); + for m in &bundle.media { + if items.iter().all(|i| i.1 != m.asset) { + items.push((m.name.clone(), m.asset, Some(m.capability.clone()), m.size)); + } + } + // Buffers: 0 the animation data, when there is any; then one circular buffer per source + // item and per picture item. + let mut anim_bytes: Vec = Vec::new(); + let mut views: Vec = Vec::new(); + let mut accessors: Vec = Vec::new(); + let mut samplers: Vec = Vec::new(); + let mut channels: Vec = Vec::new(); + let mut anim_plan: Vec<(usize, Vec, Vec<[i64; 3]>)> = Vec::new(); + let mut cameras: Vec = Vec::new(); + // §4.3: the listener, then one node per audio object. + let mut object_nodes: Vec = Vec::new(); + if let Some(a) = &audio { + cameras.push(J::O(vec![("type", J::S("perspective".into())), ("perspective", J::O(vec![("yfov", J::F(1.0)), ("znear", J::F(0.25))]))])); + nodes.push(J::O(vec![ + ("name", J::S("listener".into())), + ("camera", J::I(0)), + ("extensions", J::O(vec![("MPEG_audio_spatial", J::O(vec![("listener", J::O(vec![("id", J::I(0))]))]))])), + ])); + used.push("MPEG_audio_spatial"); + for (i, o) in a.objects.iter().enumerate() { + for w in o.keys.windows(2) { + if w[1].sample <= w[0].sample { + return refuse("not_presentable", format!("object {i}: its keys are not in ascending sample order")); + } + } + let times: Vec = o.keys.iter().map(|k| ratio_to_f32(k.sample, a.sample_rate)).collect(); + if times.windows(2).any(|w| w[1] <= w[0]) { + return refuse("not_presentable", format!("object {i}: two keys at one binary32 time")); + } + object_nodes.push(nodes.len()); + if o.keys.len() >= 2 { + anim_plan.push((nodes.len(), times, o.keys.iter().map(|k| k.pos).collect())); + } + nodes.push(J::A(Vec::new())); // filled in once its source's accessor is known + } + } + let mut buffers: Vec = Vec::new(); + if !anim_plan.is_empty() { + buffers.push(J::A(Vec::new())); // filled in once the data is laid out + } + for (node, times, poss) in &anim_plan { + let (ti, to) = (anim_bytes.len(), anim_bytes.len() + 4 * times.len()); + for t in times { + anim_bytes.extend_from_slice(&t.to_le_bytes()); + } + for p in poss { + for x in p { + anim_bytes.extend_from_slice(&fx_to_f32(*x).to_le_bytes()); + } + } + let vi = views.len(); + views.push(J::O(vec![("buffer", J::I(0)), ("byteOffset", J::I(ti as u64)), ("byteLength", J::I(4 * times.len() as u64))])); + views.push(J::O(vec![("buffer", J::I(0)), ("byteOffset", J::I(to as u64)), ("byteLength", J::I(12 * times.len() as u64))])); + let ai = accessors.len(); + accessors.push(J::O(vec![ + ("bufferView", J::I(vi as u64)), + ("componentType", J::I(5126)), + ("count", J::I(times.len() as u64)), + ("type", J::S("SCALAR".into())), + ("min", J::A(vec![J::F(f64::from(times[0]))])), + ("max", J::A(vec![J::F(f64::from(*times.last().unwrap()))])), + ])); + accessors.push(J::O(vec![ + ("bufferView", J::I(vi as u64 + 1)), + ("componentType", J::I(5126)), + ("count", J::I(times.len() as u64)), + ("type", J::S("VEC3".into())), + ])); + let si = samplers.len(); + samplers.push(J::O(vec![("input", J::I(ai as u64)), ("interpolation", J::S("LINEAR".into())), ("output", J::I(ai as u64 + 1))])); + channels.push(J::O(vec![("sampler", J::I(si as u64)), ("target", J::O(vec![("node", J::I(*node as u64)), ("path", J::S("translation".into()))]))])); + } + if !anim_plan.is_empty() { + buffers[0] = J::O(vec![ + ("byteLength", J::I(anim_bytes.len() as u64)), + ("uri", J::S(format!("data:application/octet-stream;base64,{}", base64(&anim_bytes)))), + ]); + } + // One circular buffer, view and timed accessor per source item and per picture item. + let mut item_accessor: Vec> = vec![None; items.len()]; + let mut textures: Vec = Vec::new(); + for (k, (_, _, _, size)) in items.iter().enumerate() { + let picture = size.is_some(); + if k >= n_sources && !picture { + continue; + } + let (bytes, acc): (u64, Vec<(&'static str, J)>) = if let Some((w, h)) = size { + let count = u64::from(*w) * u64::from(*h); + (3 * count, vec![("componentType", J::I(5121)), ("normalized", J::B(true)), ("count", J::I(count)), ("type", J::S("VEC3".into()))]) + } else { + (4 * AUDIO_FRAME, vec![("componentType", J::I(5126)), ("count", J::I(AUDIO_FRAME)), ("type", J::S("SCALAR".into()))]) + }; + let b = buffers.len(); + buffers.push(J::O(vec![ + ("byteLength", J::I(bytes)), + ("extensions", J::O(vec![("MPEG_buffer_circular", J::O(vec![("media", J::I(k as u64))]))])), + ])); + let v = views.len(); + views.push(J::O(vec![("buffer", J::I(b as u64)), ("byteLength", J::I(bytes))])); + let mut a = vec![("bufferView", J::I(v as u64))]; + a.extend(acc); + a.push(("extensions", J::O(vec![("MPEG_accessor_timed", J::O(vec![("immutable", J::B(true))]))]))); + item_accessor[k] = Some(accessors.len()); + accessors.push(J::O(a)); + if let Some((w, h)) = size { + textures.push(J::O(vec![( + "extensions", + J::O(vec![( + "MPEG_texture_video", + J::O(vec![ + ("accessor", J::I(item_accessor[k].unwrap() as u64)), + ("width", J::I(u64::from(*w))), + ("height", J::I(u64::from(*h))), + ("format", J::S("RGB".into())), + ]), + )]), + )])); + } + } + // The audio objects' nodes, now that their accessors are known. + if let Some(a) = &audio { + for (i, o) in a.objects.iter().enumerate() { + let k = items.iter().position(|it| it.1 == o.source).expect("a source item"); + let mut m: Vec<(&'static str, J)> = vec![("name", J::S(format!("object-{i}")))]; + if let Some(first) = o.keys.first() { + m.push(("translation", vec3(first.pos))); + } + m.push(( + "extensions", + J::O(vec![( + "MPEG_audio_spatial", + J::O(vec![( + "sources", + J::A(vec![J::O(vec![ + ("id", J::I(i as u64)), + ("type", J::S("Object".into())), + ("attenuation", J::S("custom".into())), + ("accessors", J::A(vec![J::I(item_accessor[k].expect("a source accessor") as u64)])), + ])]), + )]), + )]), + )); + nodes[object_nodes[i]] = J::O(m); + } + } + if !items.is_empty() { + used.push("MPEG_media"); + } + if buffers.iter().any(|b| matches!(b, J::O(m) if m.iter().any(|(k, _)| *k == "extensions"))) { + used.push("MPEG_buffer_circular"); + used.push("MPEG_accessor_timed"); + } + if !textures.is_empty() { + used.push("MPEG_texture_video"); + } + used.sort_unstable(); + let media: Vec = items + .iter() + .map(|(name, h, cap, _)| { + J::O(vec![ + ("name", J::S(name.clone())), + ("autoplay", J::B(true)), + ("alternatives", J::A(vec![J::O(vec![("uri", J::S(format!("{ASSET_URN}{}", hex(h)))), ("mimeType", J::S(mime(cap.as_deref())))])])), + ]) + }) + .collect(); + let mut doc: Vec<(&'static str, J)> = vec![("asset", J::O(vec![("version", J::S("2.0".into())), ("generator", J::S(PROFILE.into()))]))]; + if !used.is_empty() { + doc.push(("extensionsUsed", J::A(used.iter().map(|s| J::S(s.to_string())).collect()))); + } + if !media.is_empty() { + doc.push(("extensions", J::O(vec![("MPEG_media", J::O(vec![("media", J::A(media))]))]))); + } + doc.push(("scene", J::I(0))); + let roots: Vec = (0..nodes.len()).map(|i| J::I(i as u64)).collect(); + doc.push(("scenes", J::A(vec![if roots.is_empty() { J::O(vec![]) } else { J::O(vec![("nodes", J::A(roots))]) }]))); + for (k, v) in [("nodes", nodes), ("cameras", cameras), ("buffers", buffers), ("bufferViews", views), ("accessors", accessors)] { + if !v.is_empty() { + doc.push((k, J::A(v))); + } + } + if !channels.is_empty() { + doc.push(("animations", J::A(vec![J::O(vec![("name", J::S("audio".into())), ("channels", J::A(channels)), ("samplers", J::A(samplers))])]))); + } + if !textures.is_empty() { + doc.push(("textures", J::A(textures))); + } + let mut wai: Vec<(&'static str, J)> = vec![("profile", J::S(PROFILE.into()))]; + if let Some(w) = &bundle.world { + wai.push(("world", J::S(hex(w)))); + } + if let Some(a) = &bundle.audio { + wai.push(("audio", J::S(hex(a)))); + } + if !bundle.media.is_empty() { + wai.push(("media", J::S(media_json(&bundle.media)))); + } + doc.push(("extras", J::O(vec![("wai", J::O(wai))]))); + let mut out = String::new(); + write(&J::O(doc), &mut out); + out.push('\n'); + Ok(out.into_bytes()) +} + +/// RFC 4648 base64 with padding. +pub(crate) fn base64(b: &[u8]) -> String { + const A: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + let mut o = String::with_capacity(b.len().div_ceil(3) * 4); + for c in b.chunks(3) { + let n = (u32::from(c[0]) << 16) | (u32::from(*c.get(1).unwrap_or(&0)) << 8) | u32::from(*c.get(2).unwrap_or(&0)); + for i in 0..4 { + if i <= c.len() { + o.push(A[((n >> (18 - 6 * i)) & 63) as usize] as char); + } else { + o.push('='); + } + } + } + o +} + +pub(crate) fn unbase64(s: &str) -> Option> { + let val = |c: u8| -> Option { + Some(match c { + b'A'..=b'Z' => c - b'A', + b'a'..=b'z' => c - b'a' + 26, + b'0'..=b'9' => c - b'0' + 52, + b'+' => 62, + b'/' => 63, + _ => return None, + } as u32) + }; + let b = s.as_bytes(); + if b.len() % 4 != 0 { + return None; + } + let mut o = Vec::with_capacity(b.len() / 4 * 3); + for (q, c) in b.chunks(4).enumerate() { + let last = q == b.len() / 4 - 1; + let pad = c.iter().rev().take_while(|&&x| x == b'=').count(); + if pad > 2 || (pad > 0 && !last) { + return None; + } + let mut n = 0u32; + for &x in &c[..4 - pad] { + n = (n << 6) | val(x)?; + } + n <<= 6 * pad as u32; + let bytes = [(n >> 16) as u8, (n >> 8) as u8, n as u8]; + o.extend_from_slice(&bytes[..3 - pad]); + } + if base64(&o) != s { + return None; // non-canonical padding bits + } + Some(o) +} + +// --------------------------------------------------------------------------------------- +// Import (§5) +// --------------------------------------------------------------------------------------- + +fn bad(why: impl Into) -> Refusal { + Refusal::new("document_malformed", why) +} + +fn idx(v: Option<&Json>, len: usize, what: &str) -> Result { + let i = v.and_then(Json::as_u64).ok_or_else(|| bad(format!("{what} is not an index")))? as usize; + if i >= len { + return Err(bad(format!("{what} {i} past the end"))); + } + Ok(i) +} + +fn arr<'a>(doc: &'a Json, k: &str) -> &'a [Json] { + doc.get(k).and_then(Json::as_arr).unwrap_or(&[]) +} + +/// A JSON number as glTF reads a float: the binary32 nearest its decimal value. +fn f32_of(v: &Json) -> Option { + match v { + Json::Num(s) => s.parse::().ok().filter(|f| f.is_finite()), + _ => None, + } +} + +/// Read `bytes` as a glTF document (§5): one WAI wrote, back to its bundle; any other, to a +/// new bundle under §5.3 with `params`. +pub fn import(bytes: &[u8], params: &ImportParams) -> Result { + let doc = strict_json::read(bytes).map_err(|e| bad(e))?; + if doc.as_obj().is_none() || doc.get("asset").and_then(|a| a.get("version")).and_then(Json::as_str) != Some("2.0") { + return Err(bad("not a glTF 2.0 document")); + } + if let Some(wai) = doc.get("extras").and_then(|e| e.get("wai")) { + let bundle = carried(wai)?; + let again = export(&bundle).map_err(|r| Refusal::new("carried_malformed", r.to_string()))?; + if again != bytes { + return refuse("presentation_mismatch", "the document is not the export of the bundle it carries"); + } + return Ok(Imported { bundle, dropped: Vec::new() }); + } + foreign(&doc, params) +} + +/// §5.2: the bundle a WAI-written document carries. +fn carried(wai: &Json) -> Result { + let cm = |why: &str| Refusal::new("carried_malformed", why.to_string()); + let m = wai.as_obj().ok_or_else(|| cm("extras.wai is not an object"))?; + let mut b = Bundle::default(); + for (k, v) in m { + match k.as_str() { + "profile" if v.as_str() == Some(PROFILE) => {} + "world" => b.world = Some(v.as_str().and_then(unhex).ok_or_else(|| cm("world is not hexadecimal"))?), + "audio" => b.audio = Some(v.as_str().and_then(unhex).ok_or_else(|| cm("audio is not hexadecimal"))?), + "media" => { + let text = v.as_str().ok_or_else(|| cm("media is not a string"))?; + let j = strict_json::read(text.as_bytes()).map_err(|_| cm("media is not JSON"))?; + b.media = media_from_json(&j).ok_or_else(|| cm("media is not a media list"))?; + } + _ => return Err(cm(&format!("member {k:?}"))), + } + } + if wai.get("profile").and_then(Json::as_str) != Some(PROFILE) { + return Err(cm("no profile wai-scene/1")); + } + Ok(b) +} + +struct Walked { + pos: [i64; 3], +} + +fn foreign(doc: &Json, p: &ImportParams) -> Result { + let nodes = arr(doc, "nodes"); + let scenes = arr(doc, "scenes"); + // Step 1: the scene's nodes, depth first, each once. + let mut order: Vec = Vec::new(); + let mut seen = vec![false; nodes.len()]; + let roots: Vec = if scenes.is_empty() { + Vec::new() + } else { + let s = if doc.get("scene").is_some() { idx(doc.get("scene"), scenes.len(), "scene")? } else { 0 }; + arr(&scenes[s], "nodes").iter().map(|n| idx(Some(n), nodes.len(), "a scene's node")).collect::>()? + }; + let mut stack: Vec<(usize, Option)> = roots.iter().rev().map(|&r| (r, None)).collect(); + let mut parent: Vec> = vec![None; nodes.len()]; + while let Some((n, par)) = stack.pop() { + if seen[n] { + return Err(bad(format!("node {n} is reached twice"))); + } + seen[n] = true; + parent[n] = par; + order.push(n); + let ch: Vec = arr(&nodes[n], "children").iter().map(|c| idx(Some(c), nodes.len(), "a child")).collect::>()?; + for &c in ch.iter().rev() { + stack.push((c, Some(n))); + } + } + // Step 2: transforms and positions, in walk order. + let mut pos: BTreeMap = BTreeMap::new(); + for &n in &order { + let nd = &nodes[n]; + if nd.get("matrix").is_some() { + return refuse("transform_not_carried", format!("node {n}: a matrix")); + } + let floats = |k: &str, len: usize| -> Result>, Refusal> { + match nd.get(k) { + None => Ok(None), + Some(v) => { + let a = v.as_arr().filter(|a| a.len() == len).ok_or_else(|| bad(format!("node {n}: {k}")))?; + a.iter().map(|x| f32_of(x).ok_or_else(|| bad(format!("node {n}: {k}")))).collect::, _>>().map(Some) + } + } + }; + if floats("rotation", 4)?.is_some_and(|r| r != [0.0, 0.0, 0.0, 1.0]) { + return refuse("transform_not_carried", format!("node {n}: a rotation")); + } + if floats("scale", 3)?.is_some_and(|s| s != [1.0, 1.0, 1.0]) { + return refuse("transform_not_carried", format!("node {n}: a scale")); + } + let t = floats("translation", 3)?.unwrap_or(vec![0.0; 3]); + let mut tf = [0i64; 3]; + for (i, x) in t.iter().enumerate() { + tf[i] = super::num::f64_to_fx(f64::from(*x)).ok_or_else(|| Refusal::new("position_not_exact", format!("node {n}: translation {x}")))?; + } + let base = parent[n].map(|q| pos[&q]).unwrap_or([0; 3]); + let at = add(base, tf).ok_or_else(|| Refusal::new("position_not_exact", format!("node {n}: past the Q32.32 range")))?; + pos.insert(n, at); + } + // Classification. + let spatial = |n: usize| nodes[n].get("extensions").and_then(|e| e.get("MPEG_audio_spatial")); + let mut audio_nodes: Vec = Vec::new(); + let mut world_nodes: Vec = Vec::new(); + let mut dropped: Vec = Vec::new(); + for &n in &order { + if let Some(s) = spatial(n) + && s.get("sources").is_some() + { + audio_nodes.push(n); + } else if nodes[n].get("camera").is_some() || spatial(n).is_some() { + continue; + } else { + if nodes[n].get("mesh").is_some() { + dropped.push(format!("node {n}: mesh")); + } + world_nodes.push(Walked { pos: pos[&n] }); + } + } + // Step 3: animations target audio nodes' translations, linearly. + let animations = arr(doc, "animations"); + let mut anim_of: BTreeMap = BTreeMap::new(); // node → (animation, sampler) + for (ai, a) in animations.iter().enumerate() { + let samplers = arr(a, "samplers"); + for c in arr(a, "channels") { + let s = idx(c.get("sampler"), samplers.len(), "a channel's sampler")?; + let tgt = c.get("target").ok_or_else(|| bad("a channel without a target"))?; + let Some(node) = tgt.get("node") else { continue }; + let node = idx(Some(node), nodes.len(), "a channel's node")?; + let path = tgt.get("path").and_then(Json::as_str).unwrap_or(""); + let interp = samplers[s].get("interpolation").and_then(Json::as_str).unwrap_or("LINEAR"); + if !audio_nodes.contains(&node) || path != "translation" || interp != "LINEAR" || anim_of.contains_key(&node) { + return refuse("animation_not_carried", format!("a channel on node {node}, path {path:?}, {interp}")); + } + anim_of.insert(node, (ai, s)); + } + } + // Step 4: audio sources. + let media_items = doc.get("extensions").and_then(|e| e.get("MPEG_media")).and_then(|m| m.get("media")).and_then(Json::as_arr).unwrap_or(&[]); + let accessors = arr(doc, "accessors"); + let views = arr(doc, "bufferViews"); + let buffers = arr(doc, "buffers"); + let media_of_accessor = |a: usize| -> Result { + let v = idx(accessors[a].get("bufferView"), views.len(), "an accessor's bufferView")?; + let b = idx(views[v].get("buffer"), buffers.len(), "a bufferView's buffer")?; + let m = buffers[b].get("extensions").and_then(|e| e.get("MPEG_buffer_circular")).and_then(|c| c.get("media")); + idx(m, media_items.len(), "a circular buffer's media") + }; + let wai_alt = |k: usize| -> Option<([u8; 32], Option)> { + arr(&media_items[k], "alternatives").iter().find_map(|alt| { + let h = parse_urn(alt.get("uri")?.as_str()?)?; + Some((h, alt.get("mimeType").and_then(Json::as_str).and_then(mime_capability))) + }) + }; + let audio_rate = || -> Result { + let c = p.audio_contract.as_ref().ok_or_else(|| Refusal::new("contract_absent", "audio objects and no audio contract"))?; + let j = strict_json::read(c).map_err(|_| Refusal::new("contract_absent", "the audio contract is not JSON"))?; + j.get("sample_rate").and_then(Json::as_u64).filter(|&r| (1..=u64::from(u32::MAX)).contains(&r)).ok_or_else(|| Refusal::new("contract_absent", "the audio contract has no sample_rate")) + }; + let mut objects: Vec<([u8; 32], Vec<(u64, [i64; 3])>)> = Vec::new(); + let mut source_items: Vec = Vec::new(); + for &n in &audio_nodes { + for s in arr(spatial(n).unwrap(), "sources") { + if s.get("type").and_then(Json::as_str) != Some("Object") { + return refuse("audio_not_object", format!("node {n}: a source of type {:?}", s.get("type").and_then(Json::as_str))); + } + if let Some(g) = s.get("pregain") + && f32_of(g) != Some(0.0) + { + return refuse("gain_not_exact", format!("node {n}: a pregain in decibels")); + } + let a0 = idx(arr(s, "accessors").first(), accessors.len(), "a source's accessor")?; + let k = media_of_accessor(a0)?; + let (hash, _) = wai_alt(k).ok_or_else(|| Refusal::new("media_not_wai", format!("node {n}: its media is no WAI object")))?; + if !source_items.contains(&k) { + source_items.push(k); + } + let keys = match anim_of.get(&n) { + None => vec![(0u64, pos[&n])], + Some(&(ai, si)) => { + let rate = audio_rate()?; + let base = parent[n].map(|q| pos[&q]).unwrap_or([0; 3]); + let smp = &arr(&animations[ai], "samplers")[si]; + let ti = idx(smp.get("input"), accessors.len(), "a sampler's input")?; + let to = idx(smp.get("output"), accessors.len(), "a sampler's output")?; + let times = read_floats(doc, ti, "SCALAR")?; + let vals = read_floats(doc, to, "VEC3")?; + if vals.len() != 3 * times.len() { + return Err(bad(format!("node {n}: input and output counts differ"))); + } + let mut keys = Vec::new(); + for (j, t) in times.iter().enumerate() { + let smp = time_to_sample(*t, rate).ok_or_else(|| Refusal::new("time_not_exact", format!("node {n}: time {t}")))?; + let mut q = [0i64; 3]; + for c in 0..3 { + q[c] = super::num::f64_to_fx(f64::from(vals[3 * j + c])).ok_or_else(|| Refusal::new("position_not_exact", format!("node {n}: key {j}")))?; + } + keys.push((smp, add(base, q).ok_or_else(|| Refusal::new("position_not_exact", "past the range"))?)); + } + keys + } + }; + objects.push((hash, keys)); + } + } + // Step 5: the other media items. + let textures = arr(doc, "textures"); + let mut media: Vec = Vec::new(); + for k in 0..media_items.len() { + if source_items.contains(&k) { + continue; + } + let (hash, cap) = wai_alt(k).ok_or_else(|| Refusal::new("media_not_wai", format!("media {k}: no WAI object")))?; + let cap = cap.ok_or_else(|| Refusal::new("media_not_wai", format!("media {k}: no capability in its mimeType")))?; + let name = media_items[k].get("name").and_then(Json::as_str).unwrap_or("").to_string(); + let mut size = None; + if super::is_picture(&cap) { + for t in textures { + let Some(tv) = t.get("extensions").and_then(|e| e.get("MPEG_texture_video")) else { continue }; + let a = idx(tv.get("accessor"), accessors.len(), "a video texture's accessor")?; + if media_of_accessor(a)? == k { + let wh = (tv.get("width").and_then(Json::as_u64), tv.get("height").and_then(Json::as_u64)); + let (Some(w), Some(h)) = wh else { return Err(bad("a video texture's width or height is not an integer")) }; + size = Some((w as u32, h as u32)); + break; + } + } + } + let m = MediaItem { name, asset: hash, capability: cap, size }; + super::check_media(&m).map_err(|_| Refusal::new("media_not_wai", format!("media {k}: no width and height of 1 to 65535")))?; + media.push(m); + } + // Step 6: contracts and the bytes. + let mut bundle = Bundle { media, ..Bundle::default() }; + if !world_nodes.is_empty() { + let c = p.world_contract.as_ref().ok_or_else(|| Refusal::new("contract_absent", "world nodes and no world contract"))?; + let j = strict_json::read(c).map_err(|_| Refusal::new("contract_absent", "the world contract is not JSON"))?; + if j.get("sim").and_then(Json::as_str) != Some("wai.world.sim.kinematic0") { + return refuse("sim_not_importable", "an imported world's sim is wai.world.sim.kinematic0"); + } + let mut ns: Vec<([u8; 16], Vec)> = world_nodes + .iter() + .enumerate() + .map(|(i, w)| (crate::world::mint_node_id(&p.actor, i as u64 + 1), kinematic_state(w.pos))) + .collect(); + bundle.world = Some(world_bytes(c, &mut ns, &[])); + } + if !objects.is_empty() { + let c = p.audio_contract.as_ref().ok_or_else(|| Refusal::new("contract_absent", "audio objects and no audio contract"))?; + audio_rate()?; + let mut assets: Vec<(Vec, u8, String)> = Vec::new(); + for (h, _) in &objects { + if assets.iter().all(|a| a.0 != h.as_slice()) { + let k = source_items[assets.len()]; + let name = media_items[k].get("name").and_then(Json::as_str).unwrap_or("").to_string(); + assets.push((h.to_vec(), 0, name)); + } + } + bundle.audio = Some(audio_bytes(c, &objects, &assets)); + } + Ok(Imported { bundle, dropped }) +} + +/// A sample index from a binary32 time, when `t · rate` is a whole number (§5.3 step 4). +pub(crate) fn time_to_sample(t: f32, rate: u64) -> Option { + if !(t >= 0.0) || !t.is_finite() { + return None; + } + if t == 0.0 { + return Some(0); + } + let bits = t.to_bits(); + let exp = ((bits >> 23) & 0xff) as i32; + let mant = u128::from(if exp == 0 { bits & 0x7f_ffff } else { (bits & 0x7f_ffff) | 0x80_0000 }); + let e = if exp == 0 { -149 } else { exp - 150 }; + let num = mant * u128::from(rate); + let v = if e >= 0 { num.checked_shl(e as u32)? } else { + let sh = (-e) as u32; + if sh >= 128 || num & ((1u128 << sh) - 1) != 0 { + return None; + } + num >> sh + }; + u64::try_from(v).ok() +} + +/// The binary32 values of an accessor of `FLOAT` components and type `ty`, from a buffer +/// carried in the document as a `data:` URI. +fn read_floats(doc: &Json, a: usize, ty: &str) -> Result, Refusal> { + let acc = &arr(doc, "accessors")[a]; + if acc.get("componentType").and_then(Json::as_u64) != Some(5126) || acc.get("type").and_then(Json::as_str) != Some(ty) || acc.get("sparse").is_some() { + return Err(bad(format!("accessor {a} is not FLOAT {ty}"))); + } + let count = acc.get("count").and_then(Json::as_u64).ok_or_else(|| bad("an accessor's count"))? as usize; + let comps = if ty == "VEC3" { 3 } else { 1 }; + let views = arr(doc, "bufferViews"); + let v = idx(acc.get("bufferView"), views.len(), "an accessor's bufferView")?; + let view = &views[v]; + if view.get("byteStride").is_some() { + return Err(bad("a strided bufferView")); + } + let buffers = arr(doc, "buffers"); + let b = idx(view.get("buffer"), buffers.len(), "a bufferView's buffer")?; + let uri = buffers[b].get("uri").and_then(Json::as_str).ok_or_else(|| bad("a buffer not carried in the document"))?; + let data = uri.strip_prefix("data:application/octet-stream;base64,").and_then(unbase64).ok_or_else(|| bad("a buffer not carried in the document as a data: URI"))?; + let off = view.get("byteOffset").map(|x| x.as_u64().ok_or_else(|| bad("byteOffset"))).transpose()?.unwrap_or(0) as usize + + acc.get("byteOffset").map(|x| x.as_u64().ok_or_else(|| bad("byteOffset"))).transpose()?.unwrap_or(0) as usize; + let len = view.get("byteLength").and_then(Json::as_u64).ok_or_else(|| bad("byteLength"))? as usize; + let need = count.checked_mul(4 * comps).ok_or_else(|| bad("an accessor's size"))?; + let vstart = view.get("byteOffset").and_then(Json::as_u64).unwrap_or(0) as usize; + if off.checked_add(need).is_none_or(|e| e > data.len() || e > vstart + len) { + return Err(bad(format!("accessor {a} runs past its view or buffer"))); + } + Ok(data[off..off + need].chunks_exact(4).map(|c| f32::from_le_bytes(c.try_into().unwrap())).collect()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn base64_round_trips_and_refuses_what_is_not_canonical() { + for n in 0..10 { + let b: Vec = (0..n).map(|i| (i * 37 + 1) as u8).collect(); + assert_eq!(unbase64(&base64(&b)).unwrap(), b); + } + assert_eq!(base64(b"foobar"), "Zm9vYmFy"); + assert_eq!(unbase64("Zm9=").map(|_| ()), None, "padding bits not zero"); + assert_eq!(unbase64("Zm9v="), None); + } + + #[test] + fn times_convert_only_when_whole() { + assert_eq!(time_to_sample(1.0, 48000), Some(48000)); + assert_eq!(time_to_sample(0.01, 48000), None); + assert_eq!(time_to_sample(0.5, 48000), Some(24000)); + assert_eq!(time_to_sample(ratio_to_f32(480, 48000), 48000), None, "0.01 s is not a binary32 time"); + assert!(time_to_sample(ratio_to_f32(3, 48000), 48000).is_none()); + assert_eq!(mime(Some("wai.audio.flac")), "application/wai; codecs=\"wai1.audio.flac\""); + assert_eq!(mime_capability(&mime(Some("wai.video.av1"))).as_deref(), Some("wai.video.av1")); + } +} diff --git a/wai/wai-rs/src/scene_map/num.rs b/wai/wai-rs/src/scene_map/num.rs new file mode 100644 index 000000000..08d41a2d8 --- /dev/null +++ b/wai/wai-rs/src/scene_map/num.rs @@ -0,0 +1,193 @@ +//! Numbers across the mapping (`extensions/scene-carriage.md` §3): WAI's Q32.32 fixed point +//! (`wai.det.fixed64`) to binary32 and binary64 and back, a rational time to binary32, and the +//! two decimal spellings the documents use (RFC 8785's for glTF's JSON, the USD Core +//! Specification's for USDA). Every conversion here is integer arithmetic or a single +//! correctly rounded cast, so it gives the same value on every machine. + +/// A Q32.32 value (its raw bits) as the nearest binary32, ties to even (§3.1). +pub fn fx_to_f32(raw: i64) -> f32 { + let m = raw.unsigned_abs(); + // `u64 as f32` rounds to nearest, ties to even; scaling by 2^-32 is then exact, since + // every non-zero result is at least 2^-32, a normal binary32. + let v = (m as f32) * f32::from_bits((127 - 32) << 23); + if raw < 0 { -v } else { v } +} + +/// A Q32.32 value as the nearest binary64, ties to even (§3.1). +pub fn fx_to_f64(raw: i64) -> f64 { + let m = raw.unsigned_abs(); + let v = (m as f64) * f64::from_bits((1023 - 32) << 52); + if raw < 0 { -v } else { v } +} + +/// A binary64 as a Q32.32 value when it is one exactly: finite, a multiple of 2^-32, and +/// within the Q32.32 range. `None` otherwise (§5.2, `position_not_exact`). +pub fn f64_to_fx(v: f64) -> Option { + if !v.is_finite() { + return None; + } + if v == 0.0 { + return Some(0); + } + let bits = v.to_bits(); + let neg = bits >> 63 == 1; + let exp = ((bits >> 52) & 0x7ff) as i64; + let frac = bits & ((1u64 << 52) - 1); + let (mant, e) = if exp == 0 { (frac, -1074i64) } else { (frac | (1u64 << 52), exp - 1075) }; + // v = mant · 2^e; as raw Q32.32 bits: mant · 2^(e + 32). + let s = e + 32; + let raw: i128 = if s >= 0 { + if s > 64 { + return None; + } + (mant as i128).checked_shl(s as u32)? + } else { + let sh = (-s) as u32; + if sh >= 64 || mant & ((1u64 << sh) - 1) != 0 { + return None; + } + (mant >> sh) as i128 + }; + let raw = if neg { -raw } else { raw }; + i64::try_from(raw).ok() +} + +/// The binary32 nearest to `num / den` (`den` > 0), ties to even (§3.1): the time of a sample. +pub fn ratio_to_f32(num: u64, den: u64) -> f32 { + assert!(den > 0); + if num == 0 { + return 0.0; + } + // Find k with 2^23 ≤ num · 2^k / den < 2^24, then round the quotient. + let (n, d) = (num as u128, den as u128); + let mut k: i32 = 23 - (127 - n.leading_zeros() as i32) + (127 - d.leading_zeros() as i32); + let scaled = |k: i32| -> (u128, u128) { + if k >= 0 { (n << k, d) } else { (n, d << (-k)) } + }; + loop { + let (a, b) = scaled(k); + let q = a / b; + if q < (1 << 23) { + k += 1; + } else if q >= (1 << 24) { + k -= 1; + } else { + break; + } + } + let (a, b) = scaled(k); + let (mut q, r) = (a / b, a % b); + if 2 * r > b || (2 * r == b && q & 1 == 1) { + q += 1; + } + // q · 2^-k, q < 2^24 + 1: exact in binary64, then exact in binary32. + ((q as f64) * 2f64.powi(-k)) as f32 +} + +/// The shortest decimal digits and exponent of a finite non-zero binary64: `(digits, e)` with +/// the value `0.d1d2… × 10^e`'s mantissa digits and its exponent written as for +/// `d1.d2… × 10^e`. +fn shortest(v: f64) -> (String, i32) { + let s = format!("{:e}", v.abs()); + let (m, e) = s.split_once('e').expect("exponent form"); + let digits: String = m.chars().filter(|c| *c != '.').collect(); + let digits = digits.trim_end_matches('0').to_string(); + (if digits.is_empty() { "0".into() } else { digits }, e.parse().expect("exponent")) +} + +/// RFC 8785's spelling of a binary64 (ECMAScript's `Number.prototype.toString`), as glTF's +/// JSON numbers are written (§3.2). +pub fn json_number(v: f64) -> String { + assert!(v.is_finite()); + if v == 0.0 { + return "0".into(); + } + let (d, e) = shortest(v); + let sign = if v < 0.0 { "-" } else { "" }; + let k = d.len() as i32; + let n = e + 1; + let body = if k <= n && n <= 21 { + format!("{d}{}", "0".repeat((n - k) as usize)) + } else if 0 < n && n <= 21 { + format!("{}.{}", &d[..n as usize], &d[n as usize..]) + } else if -6 < n && n <= 0 { + format!("0.{}{d}", "0".repeat((-n) as usize)) + } else { + let rest = if k > 1 { format!(".{}", &d[1..]) } else { String::new() }; + format!("{}{rest}e{}{}", &d[..1], if n - 1 >= 0 { "+" } else { "-" }, (n - 1).abs()) + }; + format!("{sign}{body}") +} + +/// The USD Core Specification's spelling of a binary64 in USDA (§3.3): the shortest digits +/// that read back to it; scientific notation when the decimal exponent is below −6 or at +/// least 15, decimal otherwise; no trailing point or zeros, no `+` in an exponent. +pub fn usd_number(v: f64) -> String { + assert!(v.is_finite()); + if v == 0.0 { + return "0".into(); + } + let (d, e) = shortest(v); + let sign = if v < 0.0 { "-" } else { "" }; + let k = d.len() as i32; + let body = if !(-6..15).contains(&e) { + let rest = if k > 1 { format!(".{}", &d[1..]) } else { String::new() }; + format!("{}{rest}e{e}", &d[..1]) + } else if e >= k - 1 { + format!("{d}{}", "0".repeat((e - (k - 1)) as usize)) + } else if e >= 0 { + format!("{}.{}", &d[..(e + 1) as usize], &d[(e + 1) as usize..]) + } else { + format!("0.{}{d}", "0".repeat((-e - 1) as usize)) + }; + format!("{sign}{body}") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fixed_point_converts_as_stated() { + let one = 1i64 << 32; + assert_eq!(fx_to_f32(one), 1.0); + assert_eq!(fx_to_f32(-one / 2), -0.5); + assert_eq!(fx_to_f32(1431655765), 0.333_333_34_f32); // 1/3 rounded once + assert_eq!(fx_to_f64(1431655765), 1431655765.0 / 4294967296.0); + assert_eq!(f64_to_fx(0.5), Some(one / 2)); + assert_eq!(f64_to_fx(-2.25), Some(-9 * one / 4)); + assert_eq!(f64_to_fx(0.1), None, "0.1 needs more than 32 fractional bits"); + assert_eq!(f64_to_fx(f64::from(0.1f32)), Some(429496736)); // 13421773 · 2^-27 + assert_eq!(f64_to_fx(2147483648.0), None, "past the Q32.32 range"); + assert_eq!(f64_to_fx(-2147483648.0), Some(i64::MIN)); + assert_eq!(f64_to_fx(1e-10), None); + } + + #[test] + fn times_round_once() { + assert_eq!(ratio_to_f32(48000, 48000), 1.0); + assert_eq!(ratio_to_f32(480, 48000), 0.01f32); + assert_eq!(ratio_to_f32(1, 3), (1.0f64 / 3.0) as f32); + assert_eq!(ratio_to_f32(u64::MAX, 1), 18446744073709551616.0f32); + } + + #[test] + fn numbers_are_spelt_as_each_format_spells_them() { + for (v, j, u) in [ + (1.0, "1", "1"), + (0.5, "0.5", "0.5"), + (-2.25, "-2.25", "-2.25"), + (1000.0, "1000", "1000"), + (0.000001, "0.000001", "0.000001"), + (0.0000001, "1e-7", "1e-7"), + (1e16, "10000000000000000", "1e16"), + (1.5e21, "1.5e+21", "1.5e21"), + (123456789012345.0, "123456789012345", "123456789012345"), + (1234567890123456.0, "1234567890123456", "1.234567890123456e15"), + (f64::from(0.1f32), "0.10000000149011612", "0.10000000149011612"), + ] { + assert_eq!(json_number(v), j, "{v}"); + assert_eq!(usd_number(v), u, "{v}"); + } + } +} diff --git a/wai/wai-rs/src/scene_map/usda.rs b/wai/wai-rs/src/scene_map/usda.rs new file mode 100644 index 000000000..5ba265c1b --- /dev/null +++ b/wai/wai-rs/src/scene_map/usda.rs @@ -0,0 +1,840 @@ +//! A bundle as a USD layer in the text format of the USD Core Specification v1.0.1, and back +//! (`extensions/scene-carriage.md` §6 and §7). + +use super::num::{f64_to_fx, fx_to_f64, usd_number}; +use super::{ + add, check_bundle, hex, kinematic_state, media_from_json, media_json, parse_urn, position, refuse, unhex, world_bytes, Bundle, + ImportParams, Imported, Refusal, ASSET_URN, PROFILE, +}; + +/// The largest time code the export writes: the binary64 integers are exact up to it. +pub const MAX_TIME_CODE: u64 = 1 << 53; + +/// Whether `c` may appear in a USDA string unescaped or as one of its escapes (§6.3). +fn usd_char_ok(c: char) -> bool { + let u = c as u32; + matches!(u, 0x00..=0x1F | 0x20..=0x7E | 0x7F | 0xA0..=0xD7FF | 0xF900..=0xFDCF | 0xFDF0..=0xFFEF) + || ((0x10000..=0xEFFFD).contains(&u) && (u & 0xFFFE) != 0xFFFE) +} + +/// A USDA string (§6.3): `"`, `\` and the escapes `\n`, `\r`, `\t`, other control characters +/// and U+007F as `\x` and two uppercase hexadecimal digits. +pub fn usd_string(s: &str) -> Result { + let mut o = String::from("\""); + for c in s.chars() { + if !usd_char_ok(c) { + return refuse("not_presentable", format!("U+{:04X} has no USDA spelling", c as u32)); + } + match c { + '"' => o.push_str("\\\""), + '\\' => o.push_str("\\\\"), + '\n' => o.push_str("\\n"), + '\r' => o.push_str("\\r"), + '\t' => o.push_str("\\t"), + c if (c as u32) < 0x20 || c == '\u{7f}' => o.push_str(&format!("\\x{:02X}", c as u32)), + c => o.push(c), + } + } + o.push('"'); + Ok(o) +} + +fn tuple3(p: [i64; 3]) -> String { + format!("({}, {}, {})", usd_number(fx_to_f64(p[0])), usd_number(fx_to_f64(p[1])), usd_number(fx_to_f64(p[2]))) +} + +struct Out { + s: String, +} + +impl Out { + fn line(&mut self, depth: usize, t: &str) { + self.s.push_str(&" ".repeat(depth)); + self.s.push_str(t); + self.s.push('\n'); + } +} + +/// Write `bundle` as a USDA layer (§6), byte for byte. +pub fn export(bundle: &Bundle) -> Result, Refusal> { + let (world, audio) = check_bundle(bundle)?; + let mut o = Out { s: String::from("#usda 1.0\n(\n") }; + o.line(1, "customLayerData = {"); + if let Some(a) = &bundle.audio { + o.line(2, &format!("string \"wai:audio\" = \"{}\"", hex(a))); + } + if !bundle.media.is_empty() { + o.line(2, &format!("string \"wai:media\" = {}", usd_string(&media_json(&bundle.media))?)); + } + o.line(2, &format!("string \"wai:profile\" = \"{PROFILE}\"")); + if let Some(w) = &bundle.world { + o.line(2, &format!("string \"wai:world\" = \"{}\"", hex(w))); + } + o.line(1, "}"); + o.line(1, "defaultPrim = \"World\""); + if let Some(a) = &audio { + if a.duration > MAX_TIME_CODE || a.sample_rate > MAX_TIME_CODE { + return refuse("not_presentable", "a duration past 2^53 time codes"); + } + o.line(1, &format!("endTimeCode = {}", a.duration)); + o.line(1, "startTimeCode = 0"); + o.line(1, &format!("timeCodesPerSecond = {}", a.sample_rate)); + } + o.s.push_str(")\n\ndef Xform \"World\"\n{\n"); + let mut first = true; + let mut sep = |o: &mut Out| { + if !first { + o.s.push('\n'); + } + first = false; + }; + if let Some(w) = &world { + for n in &w.nodes { + sep(&mut o); + o.line(1, &format!("def Xform \"node_{}\"", hex(&n.id))); + o.line(1, "{"); + if let Some(p) = position(&w.sim, &n.state)? { + o.line(2, &format!("double3 xformOp:translate = {}", tuple3(p))); + o.line(2, "uniform token[] xformOpOrder = [\"xformOp:translate\"]"); + } + o.line(1, "}"); + } + if !w.assets.is_empty() { + sep(&mut o); + o.line(1, "def Scope \"Assets\""); + o.line(1, "{"); + for (i, (h, role, name)) in w.assets.iter().enumerate() { + if i > 0 { + o.s.push('\n'); + } + o.line(2, &format!("def \"asset_{i}\" (")); + o.line(3, &format!("prepend references = @{ASSET_URN}{}@", hex(h))); + o.line(2, ")"); + o.line(2, "{"); + o.line(3, &format!("custom string wai:name = {}", usd_string(name)?)); + o.line(3, &format!("custom int wai:role = {role}")); + o.line(2, "}"); + } + o.line(1, "}"); + } + } + if let Some(a) = &audio + && !a.objects.is_empty() + { + sep(&mut o); + o.line(1, "def Scope \"Audio\""); + o.line(1, "{"); + for (i, ob) in a.objects.iter().enumerate() { + if i > 0 { + o.s.push('\n'); + } + for w in ob.keys.windows(2) { + if w[1].sample <= w[0].sample { + return refuse("not_presentable", format!("object {i}: its keys are not in ascending sample order")); + } + } + if ob.keys.iter().any(|k| k.sample > MAX_TIME_CODE) { + return refuse("not_presentable", format!("object {i}: a key past 2^53 time codes")); + } + o.line(2, &format!("def Xform \"object_{i}\"")); + o.line(2, "{"); + o.line(3, &format!("custom string wai:source = \"{}\"", hex(&ob.source))); + if let Some(k0) = ob.keys.first() { + o.line(3, &format!("double3 xformOp:translate = {}", tuple3(k0.pos))); + if ob.keys.len() >= 2 { + o.line(3, "double3 xformOp:translate.timeSamples = {"); + for k in &ob.keys { + o.line(4, &format!("{}: {},", k.sample, tuple3(k.pos))); + } + o.line(3, "}"); + } + o.line(3, "uniform token[] xformOpOrder = [\"xformOp:translate\"]"); + } + o.line(2, "}"); + } + o.line(1, "}"); + } + if !bundle.media.is_empty() { + sep(&mut o); + o.line(1, "def Scope \"Media\""); + o.line(1, "{"); + for (i, m) in bundle.media.iter().enumerate() { + if i > 0 { + o.s.push('\n'); + } + o.line(2, &format!("def \"media_{i}\" (")); + o.line(3, &format!("prepend references = @{ASSET_URN}{}@", hex(&m.asset))); + o.line(2, ")"); + o.line(2, "{"); + o.line(3, &format!("custom string wai:capability = {}", usd_string(&m.capability)?)); + if let Some((_, h)) = m.size { + o.line(3, &format!("custom int wai:height = {h}")); + } + o.line(3, &format!("custom string wai:name = {}", usd_string(&m.name)?)); + if let Some((w, _)) = m.size { + o.line(3, &format!("custom int wai:width = {w}")); + } + o.line(2, "}"); + } + o.line(1, "}"); + } + o.s.push_str("}\n"); + Ok(o.s.into_bytes()) +} + +// --------------------------------------------------------------------------------------- +// Reading the subset of §7.1 +// --------------------------------------------------------------------------------------- + +#[derive(Clone, Debug, PartialEq)] +enum Tok { + Punct(char), + Ident(String), + Str(String), + Num(String), + Asset(String), + Path(String), +} + +fn malformed(why: impl Into) -> Refusal { + Refusal::new("document_malformed", why) +} + +fn not_read(why: impl Into) -> Refusal { + Refusal::new("usd_not_read", why) +} + +fn lex(text: &str) -> Result, Refusal> { + let b: Vec = text.chars().collect(); + let mut i = 0usize; + let mut out = Vec::new(); + while i < b.len() { + let c = b[i]; + if matches!(c, ' ' | '\t' | '\r' | '\n') { + i += 1; + } else if c == '#' { + while i < b.len() && b[i] != '\n' && b[i] != '\r' { + i += 1; + } + } else if c == '/' && b.get(i + 1) == Some(&'/') { + while i < b.len() && b[i] != '\n' && b[i] != '\r' { + i += 1; + } + } else if c == '/' && b.get(i + 1) == Some(&'*') { + i += 2; + loop { + if i + 1 >= b.len() { + return Err(malformed("a comment that does not end")); + } + if b[i] == '*' && b[i + 1] == '/' { + i += 2; + break; + } + i += 1; + } + } else if c == '-' || c.is_ascii_digit() || (c == '.' && b.get(i + 1).is_some_and(char::is_ascii_digit)) { + let s0 = i; + if c == '-' { + i += 1; + } + if b[i..].starts_with(&['i', 'n', 'f']) { + i += 3; + } else { + while i < b.len() && (b[i].is_ascii_digit() || b[i] == '.' || b[i] == 'e' || b[i] == 'E' || ((b[i] == '-' || b[i] == '+') && matches!(b[i - 1], 'e' | 'E'))) { + i += 1; + } + } + out.push(Tok::Num(b[s0..i].iter().collect())); + } else if "()[]{}=:,;.".contains(c) { + out.push(Tok::Punct(c)); + i += 1; + } else if c == '"' || c == '\'' { + if b.get(i + 1) == Some(&c) && b.get(i + 2) == Some(&c) { + return Err(not_read("a triple-quoted string")); + } + i += 1; + let mut s = String::new(); + loop { + let Some(&x) = b.get(i) else { return Err(malformed("a string that does not end")) }; + i += 1; + if x == c { + break; + } + if x == '\n' || x == '\r' { + return Err(malformed("a line break in a string")); + } + if x != '\\' { + s.push(x); + continue; + } + let Some(&e) = b.get(i) else { return Err(malformed("an escape that does not end")) }; + i += 1; + match e { + 'n' => s.push('\n'), + 'r' => s.push('\r'), + 't' => s.push('\t'), + 'a' => s.push('\u{7}'), + 'b' => s.push('\u{8}'), + 'f' => s.push('\u{c}'), + 'v' => s.push('\u{b}'), + '\\' | '\'' | '"' => s.push(e), + 'x' => { + let mut v = 0u32; + let mut n = 0; + while n < 2 && b.get(i).is_some_and(|d| d.is_ascii_digit() || ('A'..='F').contains(d)) { + v = v * 16 + b[i].to_digit(16).unwrap(); + i += 1; + n += 1; + } + if n == 0 { + return Err(malformed("an empty \\x escape")); + } + if v >= 0x80 { + return Err(not_read("a \\x escape past U+007F")); + } + s.push(char::from_u32(v).unwrap()); + } + _ => return Err(not_read(format!("the escape \\{e}"))), + } + } + out.push(Tok::Str(s)); + } else if c == '@' { + if b.get(i + 1) == Some(&'@') { + return Err(not_read("a triple-@ asset path")); + } + i += 1; + let mut s = String::new(); + loop { + let Some(&x) = b.get(i) else { return Err(malformed("an asset path that does not end")) }; + i += 1; + if x == '@' { + break; + } + if x == '\n' || x == '\r' { + return Err(malformed("a line break in an asset path")); + } + s.push(x); + } + out.push(Tok::Asset(s)); + } else if c == '<' { + let mut s = String::new(); + i += 1; + loop { + let Some(&x) = b.get(i) else { return Err(malformed("a path that does not end")) }; + i += 1; + if x == '>' { + break; + } + s.push(x); + } + out.push(Tok::Path(s)); + } else if c.is_ascii_alphabetic() || c == '_' { + let s0 = i; + while i < b.len() && (b[i].is_ascii_alphanumeric() || b[i] == '_' || (b[i] == ':' && b.get(i + 1).is_some_and(|d| d.is_ascii_alphabetic() || *d == '_'))) { + i += 1; + } + out.push(Tok::Ident(b[s0..i].iter().collect())); + } else { + return Err(not_read(format!("the character {c:?}"))); + } + } + Ok(out) +} + +/// A value of the subset. +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum Val { + Num(String), + Str(String), + Ident(String), + Asset(String), + Path(String), + Tuple(Vec), + List(Vec), + Dict(Vec<(String, String, Val)>), + Samples(Vec<(String, Val)>), +} + +/// A property of a prim: its type, name and default value, and its time samples. +#[derive(Clone, Debug, Default)] +pub(crate) struct Prop { + pub ty: String, + pub default: Option, + pub samples: Option, +} + +#[derive(Clone, Debug, Default)] +pub(crate) struct Prim { + pub ty: String, + pub name: String, + pub references: Vec, + pub props: Vec<(String, Prop)>, + pub children: Vec, +} + +#[derive(Debug, Default)] +pub(crate) struct Layer { + pub custom: Vec<(String, Val)>, + pub default_prim: Option, + pub prims: Vec, +} + +struct P { + t: Vec, + i: usize, +} + +impl P { + fn peek(&self) -> Option<&Tok> { + self.t.get(self.i) + } + fn next(&mut self) -> Result { + let t = self.t.get(self.i).cloned().ok_or_else(|| malformed("the layer ends early"))?; + self.i += 1; + Ok(t) + } + fn punct(&mut self, c: char) -> Result<(), Refusal> { + match self.next()? { + Tok::Punct(x) if x == c => Ok(()), + t => Err(malformed(format!("{c:?} is due, not {t:?}"))), + } + } + fn is(&self, c: char) -> bool { + self.peek() == Some(&Tok::Punct(c)) + } + fn ident(&mut self) -> Result { + match self.next()? { + Tok::Ident(s) => Ok(s), + t => Err(malformed(format!("a name is due, not {t:?}"))), + } + } + fn skip_sep(&mut self) { + while self.is(';') { + self.i += 1; + } + } + fn value(&mut self) -> Result { + match self.next()? { + Tok::Num(s) => Ok(Val::Num(s)), + Tok::Str(s) => Ok(Val::Str(s)), + Tok::Ident(s) => Ok(Val::Ident(s)), + Tok::Asset(s) => Ok(Val::Asset(s)), + Tok::Path(s) => Ok(Val::Path(s)), + Tok::Punct('(') => { + let mut v = Vec::new(); + while !self.is(')') { + v.push(self.value()?); + if !self.is(')') { + self.punct(',')?; + } + } + self.i += 1; + Ok(Val::Tuple(v)) + } + Tok::Punct('[') => { + let mut v = Vec::new(); + while !self.is(']') { + v.push(self.value()?); + if !self.is(']') { + self.punct(',')?; + } + } + self.i += 1; + Ok(Val::List(v)) + } + Tok::Punct('{') => self.dict_body(), + t => Err(malformed(format!("a value is due, not {t:?}"))), + } + } + /// A dictionary's items after its `{`: `type key = value` and `dictionary key = {…}`. + fn dict_body(&mut self) -> Result { + let mut items = Vec::new(); + loop { + self.skip_sep(); + if self.is('}') { + self.i += 1; + return Ok(Val::Dict(items)); + } + let ty = self.ident()?; + let mut ty2 = ty.clone(); + if self.is('[') { + self.i += 1; + self.punct(']')?; + ty2.push_str("[]"); + } + let key = match self.next()? { + Tok::Str(s) | Tok::Ident(s) => s, + t => return Err(malformed(format!("a dictionary key is due, not {t:?}"))), + }; + self.punct('=')?; + let v = if ty == "dictionary" { + self.punct('{')?; + self.dict_body()? + } else { + self.value()? + }; + items.push((ty2, key, v)); + } + } + fn samples(&mut self) -> Result { + self.punct('{')?; + let mut v = Vec::new(); + while !self.is('}') { + let Tok::Num(t) = self.next()? else { return Err(malformed("a time code is due")) }; + self.punct(':')?; + v.push((t, self.value()?)); + if !self.is('}') { + self.punct(',')?; + } + } + self.i += 1; + Ok(Val::Samples(v)) + } + fn prim(&mut self) -> Result { + match self.ident()?.as_str() { + "def" => {} + s @ ("over" | "class") => return Err(not_read(format!("a prim specified by {s}"))), + s => return Err(malformed(format!("a prim is due, not {s}"))), + } + let mut p = Prim::default(); + if let Some(Tok::Ident(t)) = self.peek() { + p.ty = t.clone(); + self.i += 1; + } + let Tok::Str(name) = self.next()? else { return Err(malformed("a prim's name is due")) }; + p.name = name; + if self.is('(') { + self.i += 1; + loop { + self.skip_sep(); + if self.is(')') { + self.i += 1; + break; + } + if let Some(Tok::Str(_)) = self.peek() { + self.i += 1; // a comment + continue; + } + let mut key = self.ident()?; + let mut op = None; + if matches!(key.as_str(), "prepend" | "append" | "delete" | "add" | "reorder") { + op = Some(key); + key = self.ident()?; + } + match key.as_str() { + "references" => { + if op.as_deref().is_some_and(|o| o != "prepend" && o != "append") { + return Err(not_read(format!("{} references", op.unwrap()))); + } + self.punct('=')?; + let v = self.value()?; + let items = match v { + Val::List(v) => v, + Val::Ident(s) if s == "None" => Vec::new(), + v => vec![v], + }; + for it in items { + match it { + Val::Asset(a) => { + if matches!(self.peek(), Some(Tok::Path(_))) || self.is('(') { + return Err(not_read("a reference with a prim path or parameters")); + } + p.references.push(a) + } + _ => return Err(not_read("a reference that is not an asset path")), + } + } + } + "payload" | "inherits" | "specializes" | "variants" | "variantSets" | "relocates" => { + return Err(not_read(format!("the prim metadata {key}"))); + } + _ => { + if op.is_some() { + return Err(not_read(format!("a list operation on {key}"))); + } + self.punct('=')?; + self.value()?; + } + } + } + } + self.punct('{')?; + loop { + self.skip_sep(); + if self.is('}') { + self.i += 1; + break; + } + match self.peek() { + Some(Tok::Ident(k)) if matches!(k.as_str(), "def" | "over" | "class") => { + let c = self.prim()?; + if p.children.iter().any(|x| x.name == c.name) { + return Err(malformed(format!("the prim {:?} twice", c.name))); + } + p.children.push(c); + } + Some(Tok::Ident(k)) if matches!(k.as_str(), "rel" | "variantSet" | "reorder" | "prepend" | "append" | "delete" | "add") => { + return Err(not_read(format!("the statement {k}"))); + } + _ => { + let (name, prop, special) = self.property()?; + let entry = p.props.iter().position(|(n, _)| *n == name); + let e = match entry { + Some(i) => &mut p.props[i].1, + None => { + p.props.push((name.clone(), Prop::default())); + &mut p.props.last_mut().unwrap().1 + } + }; + if !e.ty.is_empty() && e.ty != prop.ty { + return Err(malformed(format!("the property {name} with two types"))); + } + e.ty = prop.ty; + if special { + if e.samples.is_some() { + return Err(malformed(format!("{name}.timeSamples twice"))); + } + e.samples = prop.samples; + } else { + if e.default.is_some() { + return Err(malformed(format!("{name} twice"))); + } + e.default = prop.default; + } + } + } + } + Ok(p) + } + /// An attribute: `[custom] [uniform|varying] type[[]] name` then `= value`, `.timeSamples + /// = {…}`, or nothing, then optional metadata. + fn property(&mut self) -> Result<(String, Prop, bool), Refusal> { + let mut t = self.ident()?; + if t == "custom" { + t = self.ident()?; + } + if t == "uniform" || t == "varying" { + t = self.ident()?; + } + let mut ty = t; + if self.is('[') { + self.i += 1; + self.punct(']')?; + ty.push_str("[]"); + } + let name = self.ident()?; + let mut prop = Prop { ty, ..Prop::default() }; + let mut special = false; + if self.is('.') { + self.i += 1; + let s = self.ident()?; + if s != "timeSamples" { + return Err(not_read(format!("{name}.{s}"))); + } + self.punct('=')?; + prop.samples = Some(self.samples()?); + special = true; + } else if self.is('=') { + self.i += 1; + prop.default = Some(self.value()?); + } + if self.is('(') { + // attribute metadata: read and ignored + self.i += 1; + loop { + self.skip_sep(); + if self.is(')') { + self.i += 1; + break; + } + if let Some(Tok::Str(_)) = self.peek() { + self.i += 1; + continue; + } + self.ident()?; + self.punct('=')?; + self.value()?; + } + } + Ok((name, prop, special)) + } +} + +pub(crate) fn parse(bytes: &[u8]) -> Result { + let text = std::str::from_utf8(bytes).map_err(|_| malformed("not UTF-8"))?; + let rest = text.strip_prefix("#usda").ok_or_else(|| malformed("no #usda header"))?; + let line_end = rest.find(['\n', '\r']).unwrap_or(rest.len()); + if rest[..line_end].trim_matches([' ', '\t']) != "1.0" { + return Err(not_read(format!("the layer version {:?}", rest[..line_end].trim()))); + } + let mut p = P { t: lex(&rest[line_end..])?, i: 0 }; + let mut layer = Layer::default(); + if p.is('(') { + p.i += 1; + loop { + p.skip_sep(); + if p.is(')') { + p.i += 1; + break; + } + if let Some(Tok::Str(_)) = p.peek() { + p.i += 1; + continue; + } + let key = p.ident()?; + if matches!(key.as_str(), "subLayers" | "relocates" | "prepend" | "append" | "delete") { + return Err(not_read(format!("the layer metadata {key}"))); + } + p.punct('=')?; + let v = p.value()?; + match key.as_str() { + "customLayerData" => { + let Val::Dict(items) = v else { return Err(malformed("customLayerData is not a dictionary")) }; + for (ty, k, v) in items { + if ty == "string" { + layer.custom.push((k, v)); + } + } + } + "defaultPrim" => { + let Val::Str(s) = v else { return Err(malformed("defaultPrim is not a string")) }; + layer.default_prim = Some(s); + } + _ => {} + } + } + } + while p.peek().is_some() { + let c = p.prim()?; + if layer.prims.iter().any(|x| x.name == c.name) { + return Err(malformed(format!("the prim {:?} twice", c.name))); + } + layer.prims.push(c); + } + Ok(layer) +} + +/// Read `bytes` as a USDA layer (§7): one WAI wrote, back to its bundle; any other, to a new +/// bundle under §7.2 with `params`. +pub fn import(bytes: &[u8], params: &ImportParams) -> Result { + let layer = parse(bytes)?; + let wai: Vec<&(String, Val)> = layer.custom.iter().filter(|(k, _)| k.starts_with("wai:")).collect(); + if !wai.is_empty() { + let cm = |w: &str| Refusal::new("carried_malformed", w.to_string()); + let mut b = Bundle::default(); + let mut profile = false; + for (k, v) in wai { + let Val::Str(s) = v else { return Err(cm("a carried value that is not a string")) }; + match k.as_str() { + "wai:profile" if s == PROFILE => profile = true, + "wai:world" => b.world = Some(unhex(s).ok_or_else(|| cm("wai:world is not hexadecimal"))?), + "wai:audio" => b.audio = Some(unhex(s).ok_or_else(|| cm("wai:audio is not hexadecimal"))?), + "wai:media" => { + let j = crate::strict_json::read(s.as_bytes()).map_err(|_| cm("wai:media is not JSON"))?; + b.media = media_from_json(&j).ok_or_else(|| cm("wai:media is not a media list"))?; + } + _ => return Err(cm(&format!("the key {k:?}"))), + } + } + if !profile { + return Err(cm("no wai:profile wai-scene/1")); + } + let again = export(&b).map_err(|r| Refusal::new("carried_malformed", r.to_string()))?; + if again != bytes { + return refuse("presentation_mismatch", "the layer is not the export of the bundle it carries"); + } + return Ok(Imported { bundle: b, dropped: Vec::new() }); + } + foreign(&layer, params) +} + +fn translate(p: &Prim) -> Result<[i64; 3], Refusal> { + let mut t = [0i64; 3]; + for (name, prop) in &p.props { + if name == "xformOpOrder" { + let ok = matches!(&prop.default, Some(Val::List(v)) if v.len() == 1 && v[0] == Val::Str("xformOp:translate".into())); + if !ok || prop.samples.is_some() { + return refuse("transform_not_carried", format!("{}: xformOpOrder", p.name)); + } + } else if name == "xformOp:translate" { + if prop.samples.is_some() { + return refuse("transform_not_carried", format!("{}: a translation over time", p.name)); + } + let Some(Val::Tuple(v)) = &prop.default else { return refuse("transform_not_carried", format!("{}: no default translation", p.name)) }; + if v.len() != 3 { + return Err(malformed(format!("{}: a translation of {} values", p.name, v.len()))); + } + for (i, x) in v.iter().enumerate() { + let Val::Num(s) = x else { return Err(malformed(format!("{}: a translation that is not numbers", p.name))) }; + let f: f64 = s.parse().map_err(|_| malformed(format!("{}: {s}", p.name)))?; + t[i] = f64_to_fx(f).ok_or_else(|| Refusal::new("position_not_exact", format!("{}: {s}", p.name)))?; + } + } else if name.starts_with("xformOp:") { + return refuse("transform_not_carried", format!("{}: {name}", p.name)); + } + } + Ok(t) +} + +fn foreign(layer: &Layer, params: &ImportParams) -> Result { + let root = match &layer.default_prim { + Some(n) => layer.prims.iter().find(|p| p.name == *n).ok_or_else(|| malformed(format!("defaultPrim {n:?} is not a root prim")))?, + None => layer.prims.first().ok_or_else(|| malformed("no prim"))?, + }; + let mut nodes: Vec<[i64; 3]> = Vec::new(); + let mut assets: Vec<(Vec, u8, String)> = Vec::new(); + let mut dropped = Vec::new(); + // Depth first, written order, the root included. + let mut stack: Vec<(&Prim, [i64; 3], String)> = vec![(root, [0; 3], format!("/{}", root.name))]; + while let Some((p, base, path)) = stack.pop() { + let at = add(base, translate(p)?).ok_or_else(|| Refusal::new("position_not_exact", format!("{path}: past the Q32.32 range")))?; + if !p.references.is_empty() { + for r in &p.references { + let h = parse_urn(r).ok_or_else(|| Refusal::new("asset_not_wai", format!("{path}: @{r}@")))?; + assets.push((h.to_vec(), 0, p.name.clone())); + } + } else if p.ty == "Xform" && p.props.iter().any(|(n, _)| n == "xformOp:translate") { + nodes.push(at); + } else if !matches!(p.ty.as_str(), "" | "Xform" | "Scope") { + dropped.push(format!("{path}: {}", p.ty)); + } + for c in p.children.iter().rev() { + stack.push((c, at, format!("{path}/{}", c.name))); + } + } + let mut bundle = Bundle::default(); + if !nodes.is_empty() || !assets.is_empty() { + let c = params.world_contract.as_ref().ok_or_else(|| Refusal::new("contract_absent", "world nodes or assets and no world contract"))?; + let j = crate::strict_json::read(c).map_err(|_| Refusal::new("contract_absent", "the world contract is not JSON"))?; + if j.get("sim").and_then(|v| v.as_str()) != Some("wai.world.sim.kinematic0") { + return refuse("sim_not_importable", "an imported world's sim is wai.world.sim.kinematic0"); + } + let mut ns: Vec<([u8; 16], Vec)> = nodes.iter().enumerate().map(|(i, p)| (crate::world::mint_node_id(¶ms.actor, i as u64 + 1), kinematic_state(*p))).collect(); + bundle.world = Some(world_bytes(c, &mut ns, &assets)); + } + Ok(Imported { bundle, dropped }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn strings_escape_as_stated() { + assert_eq!(usd_string("a\"b\\c\n\u{1}\u{7f}é").unwrap(), "\"a\\\"b\\\\c\\n\\x01\\x7Fé\""); + assert_eq!(usd_string("\u{e000}").unwrap_err().code, "not_presentable"); + assert_eq!(usd_string("\u{fffe}").unwrap_err().code, "not_presentable"); + } + + #[test] + fn the_subset_reads_and_refuses_as_stated() { + let l = parse(b"#usda 1.0\n(\n defaultPrim = \"W\" // c\n)\ndef Xform \"W\" (\n kind = \"group\"\n)\n{\n def Xform \"a\" { double3 xformOp:translate = (1, -2.5, 0.25) ; uniform token[] xformOpOrder = [\"xformOp:translate\"] }\n /* x */ def \"r\" ( prepend references = @urn:wai:asset:00@ ) { }\n}\n").unwrap(); + assert_eq!(l.prims[0].children.len(), 2); + let code = |s: &str| parse(s.as_bytes()).err().map(|r| r.code); + assert_eq!(code("#usda 1.1\n"), Some("usd_not_read")); + assert_eq!(code("#usda 1.0\nover \"x\" {}\n"), Some("usd_not_read")); + assert_eq!(code("#usda 1.0\ndef \"x\" { rel r = }\n"), Some("usd_not_read")); + assert_eq!(code("#usda 1.0\ndef \"x\" { int a = 1\n int a = 2 }\n"), Some("document_malformed")); + assert_eq!(code("#usda 1.0\ndef \"x\" {\n"), Some("document_malformed")); + } +} diff --git a/wai/wai-rs/tests/prior_wire.rs b/wai/wai-rs/tests/prior_wire.rs index 4cc5efbc7..8e21b4be9 100644 --- a/wai/wai-rs/tests/prior_wire.rs +++ b/wai/wai-rs/tests/prior_wire.rs @@ -19,7 +19,7 @@ fn walk(root: &Path, rel: &str, out: &mut BTreeMap>) { let key = if rel.is_empty() { name } else { format!("{rel}/{name}") }; if e.path().is_dir() { walk(root, &key, out); - } else if key != "README.md" { + } else if key != "README.md" && key != ".gitattributes" { out.insert(key, std::fs::read(e.path()).unwrap()); } } diff --git a/wai/wai-rs/tests/scene_map.rs b/wai/wai-rs/tests/scene_map.rs new file mode 100644 index 000000000..3fdc14f27 --- /dev/null +++ b/wai/wai-rs/tests/scene_map.rs @@ -0,0 +1,41 @@ +//! Scene carriage (`extensions/scene-carriage.md`): the committed corpus regenerates byte for +//! byte (every export written, every import read back to its bundle or refused as stated), and +//! the extension's §8 lists the codes the reference reports. + +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; + +use wai::scene_map::{corpus, CODES}; + +fn root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../live-conformance/scene") +} + +fn walk(root: &Path, rel: &str, out: &mut BTreeMap>) { + for e in std::fs::read_dir(root.join(rel)).unwrap().flatten() { + let name = e.file_name().to_string_lossy().into_owned(); + let key = if rel.is_empty() { name } else { format!("{rel}/{name}") }; + if e.path().is_dir() { + walk(root, &key, out); + } else if key != "README.md" && key != ".gitattributes" { + out.insert(key, std::fs::read(e.path()).unwrap()); + } + } +} + +#[test] +fn the_committed_corpus_regenerates_byte_for_byte() { + let mut files = BTreeMap::new(); + walk(&root(), "", &mut files); + let inputs: BTreeMap> = corpus::INPUT_FILES.iter().map(|(p, _)| (p.to_string(), std::fs::read(root().join(p)).unwrap())).collect(); + let bad = corpus::verify(&files, &inputs); + assert!(bad.is_empty(), "{bad:#?}"); +} + +#[test] +fn the_extension_lists_every_code_the_reference_reports() { + let text = std::fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("../extensions/scene-carriage.md")).unwrap(); + let sec = text.split("\n## 8.").nth(1).expect("§8").split("\n## ").next().unwrap(); + let listed: Vec<&str> = sec.lines().filter_map(|l| l.strip_prefix("| `")).filter_map(|l| l.split('`').next()).collect(); + assert_eq!(listed, CODES, "§8's table, in order"); +} -- 2.54.0 (Apple Git-157) From a1ecded5a2910290403f2a836011da57d3cd54f9 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:51:03 -0400 Subject: [PATCH 011/242] wai: scene-carriage text, corpus README and independent Python verifier extensions/scene-carriage.md: the bundle, the numbers that cross the mapping, the glTF document (MPEG_media, MPEG_buffer_circular, MPEG_accessor_timed, MPEG_audio_spatial, MPEG_texture_video) and the USD layer each direction writes and reads, the refusals and the corpus. scene_verify.py implements it from the text and agrees with wai-rs on every vector; it also flips bytes of every exported document and requires each copy to be caught. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/scene-carriage.md | 473 ++++++++ wai/live-conformance/scene/README.md | 40 +- wai/live-conformance/scene_verify.py | 1626 ++++++++++++++++++++++++++ wai/wai-rs/src/scene_map/gltf.rs | 15 +- 4 files changed, 2144 insertions(+), 10 deletions(-) create mode 100644 wai/extensions/scene-carriage.md create mode 100644 wai/live-conformance/scene_verify.py diff --git a/wai/extensions/scene-carriage.md b/wai/extensions/scene-carriage.md new file mode 100644 index 000000000..3afb4af78 --- /dev/null +++ b/wai/extensions/scene-carriage.md @@ -0,0 +1,473 @@ +# WAI Extension: Scene Carriage + +> Status: Draft. Maps WAI's scene content — a world (`WWLD`, +> [interactive-worlds](interactive-worlds.md)), an audio scene (`WSCN`, +> [spatial-audio](spatial-audio.md)), the assets a world names, and timed media — onto two +> scene-description formats, in both directions: ISO/IEC 23090-14 scene description, a set of +> glTF 2.0 extensions (§4, §5), and a USD layer in the text format of the USD Core +> Specification (§6, §7). A document WAI writes carries the WAI bytes exactly, beside the +> presentation a player of that format reads; reading it back gives the same bytes, and is +> refused unless writing them again gives the document. A document WAI did not write becomes +> new WAI bytes under stated rules, and is refused wherever a value would not carry exactly. +> No new capability and no transport. Reference implementation: the `scene_map` module of +> `wai-rs` (feature `scene_map`); corpus [`live-conformance/scene/`](../live-conformance/scene/), +> second implementation `live-conformance/scene_verify.py`. +> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. +> +> Texts read for this revision: glTF 2.0 as ISO/IEC 12113:2022; the descriptions and JSON +> schemas of the extensions of ISO/IEC 23090-14 that the glTF extension registry publishes +> (`MPEG_media`, `MPEG_buffer_circular`, `MPEG_accessor_timed`, `MPEG_audio_spatial`, +> `MPEG_texture_video`, `MPEG_animation_timing`, `MPEG_scene_dynamic`, `MPEG_mesh_linking`, +> `MPEG_viewport_recommended`; each states that it is based on ISO/IEC FDIS 23090-14, and +> the registry marks their licence as not yet determined, so none is copied here); and the USD +> Core Specification v1.0.1 (b7bc21a, 2025-12-12), published under CC-BY-ND-4.0. ISO/IEC +> 23090-14's own text, including its Annex D (the attenuation functions) and the timed +> accessor information header of its clause 5, was not read. + +## 1. Scope + +In scope: the bundle a mapping reads and writes (§2); the numbers that cross it (§3); the +document each direction writes or reads, for glTF (§4, §5) and for USD (§6, §7); the +refusals (§8); the corpus (§9). + +Out of scope: + +- **Presenting a world after tick 0.** A world's op log changes its nodes through the sim + that owns them; a scene description has no sim. A document presents the world's tick-0 + graph and carries its op log and seal in the WAI bytes, so a WAI sink replays it. + `MPEG_scene_dynamic` carries scene updates as JSON patches over a scene; an op is a sim + mutation, not a patch, so this mapping does not write one. +- **Mesh geometry in glTF.** A world names its assets by content hash and never embeds + them (interactive-worlds §4.1); glTF 2.0 and the extensions read give a scene no reference + to a separate glTF asset. So a `wai.mesh.glb` asset, like every asset of a world's table, + travels in the glTF document by its hash, in the carried bytes, and a glTF player does not + present it; a USD layer references it (§6.2), which USD composes where a reader for the + asset exists. Gaussian splats in glTF are `KHR_gaussian_splatting` with + `EXT_gaussian_splatting_compression_wai` (the exact-splat extension); this mapping does not + carry them. +- **Rendering.** The presentation is what each format says a player reads; how a player + renders it, and how a WAI sink renders the carried bytes, is the renderer's. + +## 2. The bundle + +### 2.1 Worlds and audio scenes + +A **bundle** holds at most one world (a `WWLD` payload of a `wai.world.*` capability), at +most one audio scene (a `WSCN` payload of `wai.audio.scene`) and a list of timed media items +(§2.2). The mapping reads each container's section table (interactive-worlds §4: the magic, +a little-endian u16 count, nine-byte entries of a kind, an offset from the end of the table +and a length; every section within the payload, no kind twice) and: + +- of a world: the contract section (0x01), a JSON text whose `sim` is a string; the graph + section (0x02), canonical state bytes (interactive-worlds §7.1: a u32 count, then each + node's 16-byte id, a u32 length and its state, ids strictly ascending, nothing after); and + the asset table (0x03) where present (interactive-worlds §4.1: a u32 count, then each + entry's 32-byte hash, its role byte, a u32 name length and the UTF-8 name, nothing after). + A world that breaks one of these, or a node whose sim §3.4 gives a position with a state + too short to hold it, is `world_malformed`. +- of an audio scene: the contract section (0x01), a JSON text with `sample_rate` (1 to + 2^32 − 1) and `duration`, each a plain decimal integer; the object table (0x02, + spatial-audio §2.2, read to its end and nothing after); the asset table (0x03) where + present; and a seal (0x04) of 104 bytes where present. One that breaks these is + `audio_malformed`. + +Other sections are carried and not read. The checks run world, then audio scene, then +media items in order. + +### 2.2 Timed media items + +A **timed media item** is a WAI object named by its content hash — the hash of +interactive-worlds §4.1, over a complete envelope — with a `name` (a string), its +`capability`, and for pictures a `width` and a `height`. Its capability is `wai.video.`, +`wai.audio.` or `wai.image.` followed by the capability grammar of +[http-delivery](http-delivery.md) §4.1 (segments of `a` to `z` and `0` to `9`, then +segments that may also hold `_`); a picture's (`wai.video.*`, `wai.image.*`) width and +height are each 1 to 65535, and an audio item has neither. An item that breaks this is +`media_malformed`. A document names a WAI object by the URI `urn:wai:asset:` followed by its +hash in 64 lowercase hexadecimal digits. + +### 2.3 What a document carries + +Every document this mapping writes names its profile, `wai-scene/1`, and carries the bundle +exactly: the world's and the audio scene's payloads in lowercase hexadecimal, and the media +list as a JSON text — an array of objects, each of exactly `name`, `asset` (64 lowercase +hexadecimal digits) and `capability`, then for a picture `width` and `height`, in that +order, written as §4.1 writes JSON. A reader takes the WAI bytes from what is carried and +nothing else; the rest of the document is presentation, a function of those bytes. + +## 3. Numbers + +### 3.1 Conversions + +A position in WAI is three Q32.32 values (`wai.det.fixed64`: a signed 64-bit integer `r` +standing for `r / 2^32`). Writing one, the mapping takes the binary32 (for glTF) or binary64 +(for USD) nearest `r / 2^32`, ties to even; every non-zero result is a normal number. A +time is a sample index `s` of an audio scene at rate `R`; writing it, the mapping takes the +binary32 nearest `s / R`, ties to even. Reading, a binary32 or binary64 value `v` is a +Q32.32 value only when `v · 2^32` is an integer from −2^63 to 2^63 − 1 (`position_not_exact` +otherwise), and a binary32 time `t ≥ 0` is a sample index only when `t · R` is an integer +of at most 2^64 − 1 (`time_not_exact`). + +### 3.2 JSON numbers + +glTF's JSON numbers are written as RFC 8785 §3.2.2.3 writes a binary64 (the shortest digits +that read back to it, spelt as ECMAScript's `Number.prototype.toString` spells them), the +binary64 being the binary32 value of §3.1 exactly. Integers are plain decimal. A reader takes +a JSON number for a glTF float as glTF does: the binary32 nearest its decimal value, ties to +even; one past the binary32 range is `document_malformed`. + +### 3.3 USDA numbers + +USDA numbers are written as the USD Core Specification (*Double-Precision Floating Point +Representation when Writing USDA content*) writes a binary64: the shortest digits that read +back to it; scientific notation, one digit before the point and a signed exponent with no +`+`, when the decimal exponent is below −6 or at least 15; decimal otherwise; no trailing +point, no trailing zero in a fraction, no leading zero but before the point of a value +below 1. Integers are plain decimal. A reader takes a USDA number as the binary64 nearest +its decimal value. + +### 3.4 Positions by sim + +A world node has a position when its sim's state begins with one: `wai.world.sim.kinematic0` +and `wai.world.sim.ballistic0`, the first 24 bytes (three little-endian Q32.32 values); +`wai.world.sim.truss0`, a particle (state byte 0 is 0), the 24 bytes after that byte. Other +nodes and sims have none. A node with no position is presented with no translation. + +## 4. Writing a glTF document + +### 4.1 The document + +The document is one JSON text in UTF-8, followed by a line feed. Every object is written +with the members this section lists, in the order it lists them, and none other; no +whitespace; strings escaped as RFC 8785 §3.2.2.2 escapes them; numbers as §3.2 writes them. +A member a step does not produce is not written. The document's members are, in order: +`asset` (`{"version":"2.0","generator":"wai-scene/1"}`); `extensionsUsed` (the names of the +extensions the document uses, in ascending byte order); `extensions`, holding `MPEG_media` +when there are media items (§4.4); `scene` (0); `scenes` (one scene whose `nodes` lists +every node, `[{}]` when there is none); `nodes`; `cameras`; `buffers`; `bufferViews`; +`accessors`; `animations`; `textures`; `extras` (§4.5). First the bundle is checked (§2). + +### 4.2 The world + +Each node of the world's tick-0 graph, in ascending id order, is a node +`{"name":"node-<32 lowercase hexadecimal digits of its id>","translation":[x,y,z]}`, its +translation the binary32 of §3.1 of its position (§3.4), absent when it has none. + +### 4.3 The audio scene + +An audio scene adds a camera, `{"type":"perspective","perspective":{"yfov":1,"znear":0.25}}`, +and after the world's nodes a listener node, +`{"name":"listener","camera":0,"extensions":{"MPEG_audio_spatial":{"listener":{"id":0}}}}`. +Then each object `i`, in table order, is a node +`{"name":"object-","translation":[…],"extensions":{"MPEG_audio_spatial":{"sources":[{"id":i,"type":"Object","attenuation":"custom","accessors":[a]}]}}}`, +its translation its first key's position (absent with no key), `a` its source's timed +accessor (§4.4). Its keys MUST be in strictly ascending sample order, and their times (§3.1) +strictly ascending, or the bundle is `not_presentable`. The attenuation is `custom`, which +ISO/IEC 23090-14 leaves outside its scope: WAI's distance law and gain are in the carried +bytes, and this mapping states no equivalence with a function of that standard's Annex D. + +An object with two or more keys is animated. The document's first buffer holds, object by +object, its times (binary32, little-endian), then its positions (three binary32 each), and +is `{"byteLength":n,"uri":"data:application/octet-stream;base64,"}`. +For each animated object, in order: two buffer views, +`{"buffer":0,"byteOffset":o,"byteLength":4k}` and `{"buffer":0,"byteOffset":o',"byteLength":12k}`; +two accessors, +`{"bufferView":v,"componentType":5126,"count":k,"type":"SCALAR","min":[t0],"max":[tlast]}` and +`{"bufferView":v+1,"componentType":5126,"count":k,"type":"VEC3"}`; a sampler +`{"input":a,"interpolation":"LINEAR","output":a+1}`; and a channel +`{"sampler":s,"target":{"node":n,"path":"translation"}}`. One animation holds them all: +`{"name":"audio","channels":[…],"samplers":[…]}`. glTF's linear interpolation of positions +between keys is the presentation; WAI's own interpolation of keys is spatial-audio §2.2's, +in the carried bytes. + +### 4.4 Media + +`MPEG_media`'s `media` lists first the audio scene's sources, each once, in object order, +then the bundle's other items in list order (an item whose hash is already listed is not +listed again). Each is +`{"name":…,"autoplay":true,"alternatives":[{"uri":"urn:wai:asset:","mimeType":…}]}`: +for a source, the name of the first bundle item with its hash, else `source-` and the first +16 digits of its hash; the `mimeType` is `application/wai` +([http-delivery](http-delivery.md) §3) followed, when the item's capability is known, by +`; codecs="wai1."`, the `codecs` grammar of +http-delivery §4.1. + +Each source, and each other item of a picture capability, gets in media order a circular +buffer, `{"byteLength":B,"extensions":{"MPEG_buffer_circular":{"media":m}}}`, a view +`{"buffer":b,"byteLength":B}`, and a timed accessor: for a source, +`{"bufferView":v,"componentType":5126,"count":1024,"type":"SCALAR","extensions":{"MPEG_accessor_timed":{"immutable":true}}}` +with `B` = 4096 (one frame of 1024 mono binary32 samples); for a picture of width `w` and +height `h`, +`{"bufferView":v,"componentType":5121,"normalized":true,"count":w·h,"type":"VEC3","extensions":{"MPEG_accessor_timed":{"immutable":true}}}` +with `B` = 3·w·h, and a texture +`{"extensions":{"MPEG_texture_video":{"accessor":a,"width":w,"height":h,"format":"RGB"}}}`. +Buffers, views and accessors follow the animation's. A circular buffer's `byteLength` is one +frame; how many frames a pipeline keeps is `MPEG_buffer_circular`'s `count`, left at its +default. An accessor with no header view is `immutable`, as `MPEG_accessor_timed`'s schema +requires. + +The extensions used are `MPEG_audio_spatial` with an audio scene, `MPEG_media` with media +items, `MPEG_buffer_circular` and `MPEG_accessor_timed` with a circular buffer, and +`MPEG_texture_video` with a texture. + +### 4.5 What is carried + +The document's `extras` is `{"wai":{"profile":"wai-scene/1","world":"","audio":"","media":""}}` +(§2.3), `world`, `audio` and `media` present when the bundle has them. + +## 5. Reading a glTF document + +### 5.1 Which reading + +A document that is not a JSON text (RFC 8259, UTF-8, no member named twice, at most 64 +levels) whose value is an object with `asset.version` `"2.0"` is `document_malformed`. One +whose `extras` has a member `wai` is read as §5.2; any other, as §5.3. + +### 5.2 A document WAI wrote + +`extras.wai` is an object of the members of §4.5 and no other, `profile` being +`wai-scene/1`, `world` and `audio` lowercase hexadecimal of even length, and `media` a JSON +text of §2.3's form; otherwise the document is `carried_malformed`. The bundle is what it +carries. Writing that bundle (§4) MUST give the document's bytes: a bundle §4 refuses is +`carried_malformed`, and other bytes are `presentation_mismatch`. So a player's view and the +WAI bytes cannot drift apart unnoticed. + +### 5.3 A document WAI did not write + +Reading one needs the key that mints its node ids (an `actor`, 32 bytes) and the contract of +the world and of the audio scene it yields. The steps run in order, and the first refusal is +the one reported; each index a step follows MUST be in range, and a member a step reads MUST +have the type it names, or the document is `document_malformed`. + +1. **The nodes.** The scene is `scenes[scene]` (`scene` defaulting to 0; no scenes, no + nodes). Its nodes are visited depth first in preorder: its `nodes` in order, each node's + `children` in order. A node visited twice is `document_malformed`. +2. **Positions**, in visiting order. A node with a `matrix`, a `rotation` other than + (0, 0, 0, 1) or a `scale` other than (1, 1, 1), each read as binary32 (§3.2), is + `transform_not_carried`: a node of the world has a position and nothing more. Its + translation (zero when absent) is read as binary32 and then as Q32.32 (§3.1, + `position_not_exact`), and its position is its parent's plus its translation + (`position_not_exact` past the Q32.32 range). +3. **Kinds**, in visiting order: a node whose `extensions.MPEG_audio_spatial` has `sources` + is an audio node; one with a `camera`, or with `MPEG_audio_spatial` and no sources, is + skipped; every other node is a world node, and a world node with a `mesh` is reported as + presentation not carried (`node : mesh`). +4. **Animations**, in document order, each animation's channels in order: a channel's + `sampler` is followed first, then its `target`; a target with no `node` is ignored. A + channel whose node is not an audio node, whose `path` is not `translation`, whose + sampler's `interpolation` (`LINEAR` by default) is not `LINEAR`, or that is a node's + second channel, is `animation_not_carried`. +5. **Audio objects**, audio nodes in visiting order, each node's sources in order. A source + of `type` other than `Object` is `audio_not_object` (an `HOA` source has no position to + carry); a `pregain` other than 0 is `gain_not_exact` (a gain in decibels has no exact + linear value). Its first accessor's buffer view's buffer is a circular buffer whose + `media` names an `MPEG_media` item; the source is the first of that item's alternatives + whose `uri` is `urn:wai:asset:` (`media_not_wai` if none). Its keys: with no channel, + one key at sample 0 at the node's position; with one, the audio contract is due now + (`contract_absent` if absent, not JSON, or without a `sample_rate` of 1 to 2^32 − 1), and + the sampler's input (`FLOAT`, `SCALAR`) and output (`FLOAT`, `VEC3`) accessors, neither + sparse, read from views with no `byteStride` of a buffer whose `uri` is + `data:application/octet-stream;base64,` and canonical base64, each within its view and + buffer, the output three values to each input value, give its keys: each time a sample + (§3.1, `time_not_exact`), each position the parent's position plus the output as Q32.32 + (`position_not_exact`). Its gain is 1. +6. **Other media**, the `MPEG_media` items no source uses, in order: its first `urn:wai:asset` + alternative (`media_not_wai` if none), whose `mimeType` names a capability as §4.4 writes + it (`media_not_wai` otherwise); its name (empty when absent); for a picture capability, + the width and height of the first `MPEG_texture_video` texture whose accessor's circular + buffer is this item. An item that then breaks §2.2 is `media_not_wai`. +7. **The bytes.** World nodes need the world contract (`contract_absent` if absent or not + JSON), whose `sim` MUST be `wai.world.sim.kinematic0` (`sim_not_importable`): the `i`-th + world node, from 1 in visiting order, gets the id interactive-worlds §5.4 mints for the + actor and lamport `i`, and a kinematic0 state at its position, at rest, with scalar 0. + The world is the contract given, those nodes in ascending id order, no asset table, and an + empty op log, laid out as interactive-worlds §4 lays a container out. Audio objects need + the audio contract (as in step 5); the audio scene is the contract given, the objects in + order (gain 1), and an asset table of each distinct source in object order, role 0, named + by its item's name. The media list is step 6's. + +## 6. Writing a USD layer + +### 6.1 The layer + +The layer is UTF-8 text in USD's text format, every line ended by a line feed, indented four +spaces a level: + +``` +#usda 1.0 +( + customLayerData = { + string "wai:audio" = "" + string "wai:media" = + string "wai:profile" = "wai-scene/1" + string "wai:world" = "" + } + defaultPrim = "World" + endTimeCode = + startTimeCode = 0 + timeCodesPerSecond = +) + +def Xform "World" +{ + … +} +``` + +`wai:audio`, `wai:media` and `wai:world` are written when the bundle has them, and the three +time fields with an audio scene: time codes are its sample indices. The prims of §6.2 are +`World`'s children, with one empty line before each but the first. First the bundle is +checked (§2). + +### 6.2 Prims + +- **World nodes**, in ascending id order: `def Xform "node_<32 hex>"`, holding + `double3 xformOp:translate = (x, y, z)` and `uniform token[] xformOpOrder = ["xformOp:translate"]` + when the node has a position (§3.4), each value the binary64 of §3.1 written as §3.3; the + meaning of the translate operation is a domain specification's, which the core + specification leaves to them, and the core specification fixes its syntax and value + resolution. +- **Assets**, when the world has an asset table: `def Scope "Assets"`, and in it, for entry + `i` in table order, `def "asset_"` with the prim metadata + `prepend references = @urn:wai:asset:@` and the properties + `custom string wai:name = ` and `custom int wai:role = `. A reference is a + composition arc to the asset. Where the reader cannot compute a layer stack for it — no + reader for a WAI object — the core specification's composition treats it as a composition + error and ignores that reference, and the rest of the stage composes. +- **Audio objects**, when the audio scene has any: `def Scope "Audio"`, and in it, for + object `i`, `def Xform "object_"` with `custom string wai:source = ""`, then, with + keys, its first key's position as `xformOp:translate`, with two or more keys + `double3 xformOp:translate.timeSamples = {` and one line `: (x, y, z),` per key and + `}`, and the `xformOpOrder` above. Keys MUST be in strictly ascending sample order, and + a sample, the duration and the rate at most 2^53, or the bundle is `not_presentable`. +- **Media**, when the list has items: `def Scope "Media"`, and in it, for item `i`, + `def "media_"` with `prepend references = @urn:wai:asset:@` and the properties + `custom string wai:capability`, for a picture `custom int wai:height`, + `custom string wai:name`, and for a picture `custom int wai:width`, in that order. + +Prims inside a scope are separated by one empty line. A prim with metadata is written +`def "" (`, the metadata line, `)`, then its body `{` … `}` on the following lines; +an empty body is `{` and `}`. + +### 6.3 Strings + +A string is written in double quotes, with `\"`, `\\`, `\n`, `\r` and `\t`, and with `\x` +and two uppercase hexadecimal digits for any other character below U+0020 and for U+007F. A +character outside the grammar's `Utf8Character` ranges has no spelling, and a bundle that +needs one is `not_presentable`. + +## 7. Reading a USD layer + +### 7.1 The subset read + +A layer that is not UTF-8, or does not begin `#usda`, is `document_malformed`; a version on +that line other than `1.0` is `usd_not_read`. The rest is read as the core specification's +grammar reads it, for this subset: + +- **Tokens.** Spaces, tabs and line breaks separate tokens; `#…` and `//…` to the end of a + line and `/*…*/` are comments. Strings in `"` or `'`, on one line, with the escapes `\n`, + `\r`, `\t`, `\a`, `\b`, `\f`, `\v`, `\\`, `\'`, `\"` and `\x` with one or two uppercase + hexadecimal digits below 0x80; asset paths `@…@` on one line; paths `<…>`; numbers (`-`, + digits, `.`, `e`, `E` and an exponent's sign, or `inf`); identifiers (`A`–`Z`, `a`–`z`, + `0`–`9`, `_`, and `:` before a letter or `_`); and `( ) [ ] { } = : , ; .`. A + triple-quoted string, a `@@@` asset path, another escape or character is `usd_not_read`; + a string, path, asset path or comment that does not end is `document_malformed`. +- **Values**: numbers, strings, identifiers, asset paths, paths, tuples `( … )`, lists + `[ … ]` and dictionaries (`{` items `type key = value` or `dictionary key = { … }` `}`). +- **Layer metadata** `( … )`: a bare string is a comment; `subLayers`, `relocates` and a + list operation are `usd_not_read`; `customLayerData` is a dictionary and `defaultPrim` a + string (`document_malformed` otherwise); other items are read and not used. +- **Prims**: `def`, an optional type name, the name as a string, optional prim metadata, and + a body. `over` and `class` are `usd_not_read`. In prim metadata, a bare string is a + comment; `references`, alone or after `prepend` or `append`, is `None`, an asset path or a + list of them, each without a prim path or parameters (`usd_not_read` otherwise; `delete` + and other list operations on it too); `payload`, `inherits`, `specializes`, `variants`, + `variantSets` and `relocates`, and a list operation on anything else, are `usd_not_read`; + other items are read and not used. +- **Bodies**: child prims (a name twice is `document_malformed`, as two root prims of one + name are); `rel`, `variantSet`, `reorder` and list-operation statements are + `usd_not_read`; and attributes, `[custom] [uniform|varying] type[[]] name` then `= value`, + `.timeSamples = { time: value, … }` or nothing, then optional metadata. A `.` other than + `.timeSamples` is `usd_not_read`; an attribute declared twice with two types, or given two + defaults or two sets of time samples, is `document_malformed`. + +Anything else is `document_malformed`. + +### 7.2 A layer WAI wrote + +A layer whose `customLayerData` has a string key beginning `wai:` is read as WAI wrote it: +its `wai:` items MUST be the strings of §6.1, `wai:profile` being `wai-scene/1` +(`carried_malformed` otherwise), and writing the bundle they carry (§6) MUST give the +layer's bytes (`carried_malformed` for a bundle §6 refuses, `presentation_mismatch` for other +bytes). + +### 7.3 A layer WAI did not write + +The root is the prim `defaultPrim` names (`document_malformed` if no root prim has that +name), else the first root prim (`document_malformed` if none). Prims are visited depth first +in preorder from the root, the root included, children in written order. For each, in +visiting order: + +1. **Its offset**, from its properties in written order: `xformOpOrder` MUST be a list of + the one string `xformOp:translate` with no time samples; `xformOp:translate` MUST have a + default value and no time samples; any other `xformOp:` property is not carried; each + breach is `transform_not_carried`. The translate's default is a tuple of three numbers + (`document_malformed`), each read as binary64 (§3.3) and then as Q32.32 + (`position_not_exact`). With no translate the offset is zero. Its position is its + parent's plus its offset (`position_not_exact` past the range). +2. **Its kind.** A prim with references is a set of assets: each asset path MUST be + `urn:wai:asset:` (`asset_not_wai`), and gives an asset entry of role 0 named by the + prim. Otherwise an `Xform` prim with an `xformOp:translate` is a world node at its + position. Otherwise a prim of a type other than none, `Xform` and `Scope` is reported as + presentation not carried (`: `). + +Nodes or assets need the world contract (`contract_absent` if absent or not JSON), whose sim +MUST be `wai.world.sim.kinematic0` (`sim_not_importable`). The world is built as §5.3 step 7 +builds one, with an asset table of the entries in visiting order. A layer gives no audio +scene and no media: the core specification defines no audio prims, so the audio and media of +a layer are read only from the bytes a WAI-written layer carries (§7.2). + +## 8. Refusals + +| code | where | +|---|---| +| `world_malformed` | §2.1: the world does not read | +| `audio_malformed` | §2.1: the audio scene does not read | +| `media_malformed` | §2.2: a media item's capability, width or height | +| `not_presentable` | §4.3, §6.2, §6.3: keys out of order, two keys at one binary32 time, a time code past 2^53, a character with no USDA spelling | +| `document_malformed` | §5.1, §5.3, §7.1, §7.3: the document does not read as its format, or a value a step needs is not there | +| `carried_malformed` | §5.2, §7.2: what a WAI-written document carries does not read, or is a bundle §4 or §6 refuses | +| `presentation_mismatch` | §5.2, §7.2: the document is not the export of the bundle it carries | +| `usd_not_read` | §7.1: a construct outside the subset | +| `transform_not_carried` | §5.3 step 2, §7.3 step 1: a rotation, scale, matrix or other transform operation | +| `position_not_exact` | §3.1, §5.3, §7.3: a value that is not a Q32.32 value | +| `animation_not_carried` | §5.3 step 4: an animation channel the mapping does not carry | +| `time_not_exact` | §3.1, §5.3 step 5: a time that is not a whole sample | +| `gain_not_exact` | §5.3 step 5: a pregain other than 0 | +| `audio_not_object` | §5.3 step 5: a source that is not an object | +| `media_not_wai` | §5.3 steps 5 and 6: media that names no WAI object or capability | +| `asset_not_wai` | §7.3 step 2: a reference to something other than a WAI object | +| `contract_absent` | §5.3, §7.3: nodes or objects and no contract to give them | +| `sim_not_importable` | §5.3 step 7, §7.3: a world contract whose sim is not `wai.world.sim.kinematic0` | + +## 9. Conformance corpus + +[`live-conformance/scene/`](../live-conformance/scene/) holds bundles — worlds built in code +(kinematic0, ballistic0 and truss0 nodes, an asset table) and the worlds and audio scenes +other corpora commit, each pinned by SHA-256 — each written as a glTF document and as a USD +layer and read back; bundles refused on export; documents WAI wrote, altered and refused; +and documents WAI did not write, built in code, read to new WAI bytes or refused with each +code of §8. A vector is an *export* (a bundle to a document's bytes, or a code) or an +*import* (a document, with the actor and contracts §5.3 needs, to a bundle and its report of +presentation not carried, or a code). A verifier conforms when it reaches every vector's +outcome and bytes. `wai_scene_vectors gen|verify` writes and checks it, `tests/scene_map.rs` +regenerates it, and `live-conformance/scene_verify.py`, written from this text and sharing +no code with `wai-rs`, is its second implementation. + +## 10. Reference implementation + +The `scene_map` module of `wai-rs` (feature `scene_map`, on `world` and `audio_scene`): +`Bundle`, `MediaItem`, `ImportParams` and `Imported`; `gltf::export` and `gltf::import`; +`usda::export` and `usda::import`; `num` for §3; `corpus` for §9. Its unit tests check the +conversions and spellings of §3 against fixed values, and that the containers §5.3 and §7.3 +build are the bytes `world::WorldContainer` and `audio_scene::SpatialScene` write. diff --git a/wai/live-conformance/scene/README.md b/wai/live-conformance/scene/README.md index b5c5e504d..838ec64e0 100644 --- a/wai/live-conformance/scene/README.md +++ b/wai/live-conformance/scene/README.md @@ -1 +1,39 @@ -# live-conformance/scene +# live-conformance/scene — scene carriage + +The corpus of [`extensions/scene-carriage.md`](../../extensions/scene-carriage.md) §9: +`vectors.json` and the files it names. + +`vectors.json` holds: + +- `inputs`: the worlds and audio scenes other corpora commit, by path from this directory, + each pinned by its SHA-256: `scene.wwld` and `basic_kinematics.wwld` of + [`../../world-conformance/vectors/`](../../world-conformance/vectors/), and `moving.wscn`, + `distance.wscn` and `stereo_pair.wscn` of + [`../../audio-conformance/vectors/`](../../audio-conformance/vectors/); +- `files`: every other file with its SHA-256; +- `vectors`, each one of: + - `export`: a `bundle` — `world` and `audio`, each a path or `null`, and `media`, the + media list of §2.2 — written in a `format` (`gltf`, §4; `usda`, §6). `accept` gives the + `file` the writer must produce, byte for byte; `refuse` gives the `code` (§8); + - `import`: a `file` in a `format`, read with `params` (`null` for a document WAI wrote; + otherwise the `actor` in hexadecimal and the `world_contract` and `audio_contract`, each a + JSON text or `null`, of §5.3). `accept` gives the `bundle` it must read to, byte for byte, + and `dropped`, its report of presentation not carried; `refuse` gives the `code`. + +Files: `inputs/` the worlds built in code (kinematic0, ballistic0 and truss0 nodes at tick 0, +an asset table) and, in `inputs/neg/`, a world and two audio scenes the export refuses; +`out/` every exported document; `docs/` documents WAI wrote and then altered, and documents +WAI did not write; `imported/` the WAI bytes the accepted foreign documents read to. + +**Exactness.** A verifier conforms when it reaches every vector's outcome, bytes and report. +A document WAI wrote reads back to the bundle it was written from, byte for byte. + +**Provenance.** `wai_scene_vectors gen` writes every file but `README.md` and +`.gitattributes` from the pinned inputs and from values written in code +(`scene_map::corpus::generate`): node ids minted from SHA-256 of a label, asset hashes +SHA-256 of a label, positions and times constants, documents written as text in code. No +media, model or recording is in the corpus. + +``` +python3 -I ../scene_verify.py . +``` diff --git a/wai/live-conformance/scene_verify.py b/wai/live-conformance/scene_verify.py new file mode 100644 index 000000000..06ce6c0ef --- /dev/null +++ b/wai/live-conformance/scene_verify.py @@ -0,0 +1,1626 @@ +#!/usr/bin/env python3 +"""Independent verifier of the scene-carriage corpus (extensions/scene-carriage.md §9). + +Written from the extension's text: §2 (the bundle), §3 (numbers), §4 and §5 (glTF), §6 and +§7 (USD). It shares no code with wai-rs, which generates the corpus; it mints node ids with +BLAKE3 written from its specification (claim-conformance/_blake3.py) and reads §8's codes +from the extension. + +For every vector it requires the outcome the corpus states: for an export, the document's +bytes or the code; for an import, the bundle's bytes, its media list and its report of +presentation not carried, or the code. Then it flips one byte of each document the corpus +exports, in several places, and requires every copy to be refused or to read to another +bundle. + + python3 -I scene_verify.py + +Exit status 0 only if every vector verifies and every mutated copy is caught. +""" +import base64 +import binascii +import hashlib +import json +import os +import re +import struct +import sys +from fractions import Fraction + +HERE = os.path.dirname(os.path.abspath(__file__)) +WAI = os.path.normpath(os.path.join(HERE, "..")) +sys.path.insert(0, os.path.join(WAI, "claim-conformance")) +from _blake3 import blake3 # noqa: E402 + +EXTENSION = os.path.join(WAI, "extensions", "scene-carriage.md") +PROFILE = "wai-scene/1" +URN = "urn:wai:asset:" +KIN = "wai.world.sim.kinematic0" + + +class Refuse(Exception): + def __init__(self, code, why=""): + super().__init__(f"{code}: {why}") + self.code = code + + +def codes(): + text = open(EXTENSION, encoding="utf-8").read() + sec = text.split("\n## 8.")[1].split("\n## ")[0] + return [m.group(1) for m in re.finditer(r"^\| `([a-z_]+)` \|", sec, re.M)] + + +# ------------------------------------------------------------------ §3 numbers + +def round_binary(q, mant_bits, emin): + """The binary floating-point value nearest the Fraction q, ties to even, as a Fraction: + mant_bits significand bits, emin the least normal exponent (unbounded above).""" + if q == 0: + return Fraction(0) + sign = -1 if q < 0 else 1 + a = abs(q) + e = a.numerator.bit_length() - a.denominator.bit_length() + if Fraction(2) ** e > a: + e -= 1 + while Fraction(2) ** (e + 1) <= a: + e += 1 + e = max(e, emin) + scale = Fraction(2) ** (e - (mant_bits - 1)) + m = a / scale + fl = m.numerator // m.denominator + rem = m - fl + if rem > Fraction(1, 2) or (rem == Fraction(1, 2) and fl % 2 == 1): + fl += 1 + return sign * fl * scale + + +F32_MAX = (2 - Fraction(1, 2 ** 23)) * Fraction(2) ** 127 + + +def to_f32(q): + """§3.1, §3.2: the binary32 nearest q, ties to even; None past the range.""" + v = round_binary(Fraction(q), 24, -126) + return None if abs(v) > F32_MAX else v + + +def fx_frac(r): + return Fraction(r, 1 << 32) + + +def to_fx(v): + """§3.1: a value as Q32.32 when exact, else None.""" + if isinstance(v, float) and (v != v or v in (float("inf"), float("-inf"))): + return None + q = Fraction(v) * (1 << 32) + if q.denominator != 1 or not -(1 << 63) <= q.numerator < (1 << 63): + return None + return q.numerator + + +def shortest(v): + """(digits, e) with v = d1.d2... x 10^e, the shortest digits reading back to v.""" + s = repr(abs(float(v))) + if "e" in s: + m, ex = s.split("e") + ex = int(ex) + else: + m, ex = s, 0 + if "." in m: + ip, fp = m.split(".") + else: + ip, fp = m, "" + digits = (ip + fp).lstrip("0") + lead = len(ip.lstrip("0")) if ip.strip("0") else -(len(fp) - len(fp.lstrip("0"))) + e = ex + (lead - 1 if ip.strip("0") else lead - 1) + digits = digits.rstrip("0") or "0" + return digits, e + + +def json_number(v): + """§3.2: RFC 8785's spelling of a binary64.""" + v = float(v) + if v == 0: + return "0" + d, e = shortest(v) + k, n = len(d), e + 1 + if k <= n <= 21: + body = d + "0" * (n - k) + elif 0 < n <= 21: + body = d[:n] + "." + d[n:] + elif -6 < n <= 0: + body = "0." + "0" * (-n) + d + else: + body = d[0] + ("." + d[1:] if k > 1 else "") + "e" + ("+" if n - 1 >= 0 else "-") + str(abs(n - 1)) + return ("-" if v < 0 else "") + body + + +def usd_number(v): + """§3.3: the USD Core Specification's spelling of a binary64.""" + v = float(v) + if v == 0: + return "0" + d, e = shortest(v) + k = len(d) + if e < -6 or e >= 15: + body = d[0] + ("." + d[1:] if k > 1 else "") + "e" + str(e) + elif e >= k - 1: + body = d + "0" * (e - (k - 1)) + elif e >= 0: + body = d[:e + 1] + "." + d[e + 1:] + else: + body = "0." + "0" * (-e - 1) + d + return ("-" if v < 0 else "") + body + + +# ------------------------------------------------------------------ JSON + +def jquote(s): + return json.dumps(s, ensure_ascii=False) + + +def strict_json(b): + """§5.1: RFC 8259 over UTF-8, no member twice, at most 64 levels; numbers as spelt.""" + try: + text = b.decode("utf-8") + except UnicodeDecodeError: + raise Refuse("document_malformed", "not UTF-8") + + def pairs(ps): + seen = set() + for k, _ in ps: + if k in seen: + raise Refuse("document_malformed", "member twice") + seen.add(k) + return Obj(ps) + + def bad(c): + raise Refuse("document_malformed", c) + try: + v = json.loads(text, object_pairs_hook=pairs, parse_int=Num, parse_float=Num, parse_constant=bad) + except Refuse: + raise + except (ValueError, RecursionError) as e: + raise Refuse("document_malformed", str(e)) + + def walk(x, d): + if d > 64: + raise Refuse("document_malformed", "too deep") + if isinstance(x, str): + try: + x.encode("utf-8") + except UnicodeEncodeError: + raise Refuse("document_malformed", "surrogate") + elif isinstance(x, list): + for y in x: + walk(y, d + 1) + elif isinstance(x, Obj): + for k, y in x.items: + walk(k, d) + walk(y, d + 1) + walk(v, 1) + return v + + +class Num(str): + pass + + +class Obj: + def __init__(self, items): + self.items = items + + def get(self, k, default=None): + for n, v in self.items: + if n == k: + return v + return default + + def has(self, k): + return any(n == k for n, _ in self.items) + + +def uint(x): + if isinstance(x, Num) and re.fullmatch(r"0|[1-9][0-9]*", x): + return int(x) + return None + + +# ------------------------------------------------------------------ §2 the bundle + +def sections(b, magic, code): + if len(b) < 6 or b[:4] != magic: + raise Refuse(code, "magic") + n = struct.unpack_from(" len(b): + raise Refuse(code, "table") + out = {} + for i in range(n): + k, off, ln = struct.unpack_from(" len(b): + raise Refuse(code, "section past the end") + if k in out: + raise Refuse(code, "kind twice") + out[k] = b[base + off:base + off + ln] + return out + + +def assets_of(a, code): + if len(a) < 4: + raise Refuse(code, "assets") + n = struct.unpack_from(" len(a): + raise Refuse(code, "assets") + h, role, nl = a[at:at + 32], a[at + 32], struct.unpack_from(" len(a): + raise Refuse(code, "assets") + try: + name = a[at:at + nl].decode("utf-8") + except UnicodeDecodeError: + raise Refuse(code, "asset name") + at += nl + out.append((h, role, name)) + if at != len(a): + raise Refuse(code, "assets") + return out + + +def position(sim, st): + """§3.4.""" + def take(b): + if len(b) < 24: + raise Refuse("world_malformed", "short state") + return list(struct.unpack_from("<3q", b, 0)) + if sim in ("wai.world.sim.kinematic0", "wai.world.sim.ballistic0"): + return take(st) + if sim == "wai.world.sim.truss0": + if not st: + raise Refuse("world_malformed", "empty truss node") + return take(st[1:]) if st[0] == 0 else None + return None + + +def read_world(b): + s = sections(b, b"WWLD", "world_malformed") + if 1 not in s or 2 not in s: + raise Refuse("world_malformed", "sections") + try: + c = strict_json(s[1]) + except Refuse: + raise Refuse("world_malformed", "contract") + sim = c.get("sim") if isinstance(c, Obj) else None + if not isinstance(sim, str): + raise Refuse("world_malformed", "sim") + g = s[2] + if len(g) < 4: + raise Refuse("world_malformed", "graph") + n = struct.unpack_from(" len(g): + raise Refuse("world_malformed", "graph") + nid, ln = g[at:at + 16], struct.unpack_from(" len(g): + raise Refuse("world_malformed", "graph") + if nodes and nodes[-1][0] >= nid: + raise Refuse("world_malformed", "order") + nodes.append((nid, g[at:at + ln])) + at += ln + if at != len(g): + raise Refuse("world_malformed", "graph") + assets = assets_of(s[3], "world_malformed") if 3 in s else [] + for _, st in nodes: + position(sim, st) + return {"sim": sim, "nodes": nodes, "assets": assets} + + +def read_audio(b): + s = sections(b, b"WSCN", "audio_malformed") + if 1 not in s or 2 not in s: + raise Refuse("audio_malformed", "sections") + try: + c = strict_json(s[1]) + except Refuse: + raise Refuse("audio_malformed", "contract") + rate = uint(c.get("sample_rate")) if isinstance(c, Obj) else None + dur = uint(c.get("duration")) if isinstance(c, Obj) else None + if rate is None or not 1 <= rate <= 0xFFFFFFFF or dur is None: + raise Refuse("audio_malformed", "contract") + t = s[2] + + def rd(at, n): + if at + n > len(t): + raise Refuse("audio_malformed", "objects") + return t[at:at + n] + n = struct.unpack(" 1 and all(re.fullmatch(r"[a-z0-9_]+", x) for x in segs[1:]) + + +def picture(c): + return c.startswith(("wai.video.", "wai.image.")) + + +def check_media(m, code="media_malformed"): + if not cap_ok(m["capability"]): + raise Refuse(code, "capability") + if picture(m["capability"]): + if m.get("width") is None or not (1 <= m["width"] <= 65535 and 1 <= m["height"] <= 65535): + raise Refuse(code, "size") + elif m.get("width") is not None: + raise Refuse(code, "size") + + +def media_text(media): + out = [] + for m in media: + s = f'{{"name":{jquote(m["name"])},"asset":"{m["asset"].hex()}","capability":{jquote(m["capability"])}' + if m.get("width") is not None: + s += f',"width":{m["width"]},"height":{m["height"]}' + out.append(s + "}") + return "[" + ",".join(out) + "]" + + +def media_from(v): + if not isinstance(v, list): + return None + out = [] + for it in v: + if not isinstance(it, Obj): + return None + keys = [k for k, _ in it.items] + if keys not in (["name", "asset", "capability"], ["name", "asset", "capability", "width", "height"]): + return None + name, asset, cap = it.get("name"), it.get("asset"), it.get("capability") + if not (isinstance(name, str) and isinstance(asset, str) and isinstance(cap, str) and re.fullmatch(r"[0-9a-f]{64}", asset)): + return None + m = {"name": name, "asset": bytes.fromhex(asset), "capability": cap, "width": None, "height": None} + if len(keys) == 5: + w, h = uint(it.get("width")), uint(it.get("height")) + if w is None or h is None or w >= 1 << 32 or h >= 1 << 32: + return None + m["width"], m["height"] = w, h + out.append(m) + return out + + +def check_bundle(b): + w = read_world(b["world"]) if b["world"] is not None else None + a = read_audio(b["audio"]) if b["audio"] is not None else None + for m in b["media"]: + check_media(m) + return w, a + + +def mime(cap): + return "application/wai" if cap is None else f'application/wai; codecs="wai1.{cap[4:]}"' + + +def mime_cap(m): + mt = re.fullmatch(r'application/wai; codecs="wai1\.([^"]*)"', m or "") + if not mt: + return None + c = "wai." + mt.group(1) + return c if cap_ok(c) else None + + +# ------------------------------------------------------------------ §4 writing glTF + +def jw(v): + """Write the JSON of §4.1: dicts in insertion order, F(...) numbers per §3.2.""" + if isinstance(v, dict): + return "{" + ",".join(f"{jquote(k)}:{jw(x)}" for k, x in v.items()) + "}" + if isinstance(v, list): + return "[" + ",".join(jw(x) for x in v) + "]" + if isinstance(v, bool): + return "true" if v else "false" + if isinstance(v, int): + return str(v) + if isinstance(v, F): + return json_number(v.v) + return jquote(v) + + +class F: + def __init__(self, v): + self.v = v + + +def f32(q): + return float(to_f32(q)) + + +def vec3(p): + return [F(f32(fx_frac(x))) for x in p] + + +def export_gltf(b): + w, a = check_bundle(b) + nodes, used = [], [] + if w: + for nid, st in w["nodes"]: + n = {"name": "node-" + nid.hex()} + p = position(w["sim"], st) + if p is not None: + n["translation"] = vec3(p) + nodes.append(n) + items = [] + first = {} + for m in b["media"]: + first.setdefault(m["asset"], m) + if a: + for src, _ in a["objects"]: + if all(i[1] != src for i in items): + m = first.get(src) + items.append((m["name"] if m else "source-" + src.hex()[:16], src, m["capability"] if m else None, None)) + nsrc = len(items) + for m in b["media"]: + if all(i[1] != m["asset"] for i in items): + items.append((m["name"], m["asset"], m["capability"], (m["width"], m["height"]) if m["width"] is not None else None)) + anim = bytearray() + views, accs, samplers, channels, cams, plan, objnodes = [], [], [], [], [], [], [] + if a: + cams.append({"type": "perspective", "perspective": {"yfov": F(1.0), "znear": F(0.25)}}) + nodes.append({"name": "listener", "camera": 0, "extensions": {"MPEG_audio_spatial": {"listener": {"id": 0}}}}) + used.append("MPEG_audio_spatial") + for i, (src, keys) in enumerate(a["objects"]): + if any(keys[j + 1][0] <= keys[j][0] for j in range(len(keys) - 1)): + raise Refuse("not_presentable", "order") + times = [to_f32(Fraction(s, a["rate"])) for s, _ in keys] + if any(times[j + 1] <= times[j] for j in range(len(times) - 1)): + raise Refuse("not_presentable", "times") + objnodes.append(len(nodes)) + if len(keys) >= 2: + plan.append((len(nodes), times, [p for _, p in keys])) + nodes.append(None) + buffers = [None] if plan else [] + for node, times, poss in plan: + ti = len(anim) + for t in times: + anim += struct.pack("= nsrc and size is None: + continue + if size: + cnt = size[0] * size[1] + nb, acc = 3 * cnt, {"componentType": 5121, "normalized": True, "count": cnt, "type": "VEC3"} + else: + nb, acc = 4096, {"componentType": 5126, "count": 1024, "type": "SCALAR"} + bi = len(buffers) + buffers.append({"byteLength": nb, "extensions": {"MPEG_buffer_circular": {"media": k}}}) + vi = len(views) + views.append({"buffer": bi, "byteLength": nb}) + full = {"bufferView": vi} + full.update(acc) + full["extensions"] = {"MPEG_accessor_timed": {"immutable": True}} + item_acc[k] = len(accs) + accs.append(full) + if size: + textures.append({"extensions": {"MPEG_texture_video": {"accessor": item_acc[k], "width": size[0], "height": size[1], "format": "RGB"}}}) + if a: + for i, (src, keys) in enumerate(a["objects"]): + k = next(j for j, it in enumerate(items) if it[1] == src) + n = {"name": f"object-{i}"} + if keys: + n["translation"] = vec3(keys[0][1]) + n["extensions"] = {"MPEG_audio_spatial": {"sources": [{"id": i, "type": "Object", "attenuation": "custom", "accessors": [item_acc[k]]}]}} + nodes[objnodes[i]] = n + if items: + used.append("MPEG_media") + if any("extensions" in x for x in buffers): + used += ["MPEG_buffer_circular", "MPEG_accessor_timed"] + if textures: + used.append("MPEG_texture_video") + used.sort() + media = [{"name": nm, "autoplay": True, "alternatives": [{"uri": URN + h.hex(), "mimeType": mime(c)}]} for nm, h, c, _ in items] + doc = {"asset": {"version": "2.0", "generator": PROFILE}} + if used: + doc["extensionsUsed"] = used + if media: + doc["extensions"] = {"MPEG_media": {"media": media}} + doc["scene"] = 0 + doc["scenes"] = [{"nodes": list(range(len(nodes)))} if nodes else {}] + for k, v in (("nodes", nodes), ("cameras", cams), ("buffers", buffers), ("bufferViews", views), ("accessors", accs)): + if v: + doc[k] = v + if channels: + doc["animations"] = [{"name": "audio", "channels": channels, "samplers": samplers}] + if textures: + doc["textures"] = textures + wai = {"profile": PROFILE} + if b["world"] is not None: + wai["world"] = b["world"].hex() + if b["audio"] is not None: + wai["audio"] = b["audio"].hex() + if b["media"]: + wai["media"] = media_text(b["media"]) + doc["extras"] = {"wai": wai} + return (jw(doc) + "\n").encode() + + +# ------------------------------------------------------------------ §5 reading glTF + +def DM(why): + return Refuse("document_malformed", why) + + +def arr(o, k): + v = o.get(k) if isinstance(o, Obj) else None + return v if isinstance(v, list) else [] + + +def index(v, n, what): + i = uint(v) + if i is None or i >= n: + raise DM(what) + return i + + +def f32_of(v): + if not isinstance(v, Num): + return None + return to_f32(Fraction(v)) + + +def mint(actor, i): + return blake3(b"wai:world-node\x01" + actor + struct.pack(" len(data) or off + need > vstart + ln: + raise DM("past the view") + return [struct.unpack_from("= 1 << 64: + raise Refuse("time_not_exact") + p = [] + for c in range(3): + x = to_fx(vals[3 * j + c]) + if x is None: + raise Refuse("position_not_exact") + p.append(base[c] + x) + if any(not -(1 << 63) <= x < (1 << 63) for x in p): + raise Refuse("position_not_exact") + keys.append((q.numerator, p)) + objects.append((h, keys)) + # Step 6 + texs = arr(doc, "textures") + media = [] + for k in range(len(items)): + if k in src_items: + continue + alt = wai_alt(k) + if alt is None or alt[1] is None: + raise Refuse("media_not_wai") + nm = items[k].get("name") if isinstance(items[k], Obj) else None + m = {"name": nm if isinstance(nm, str) else "", "asset": alt[0], "capability": alt[1], "width": None, "height": None} + if picture(alt[1]): + for t in texs: + e = t.get("extensions") if isinstance(t, Obj) else None + tv = e.get("MPEG_texture_video") if isinstance(e, Obj) else None + if not isinstance(tv, Obj): + continue + a = index(tv.get("accessor"), len(accs), "texture accessor") + if media_of(a) == k: + w, h = uint(tv.get("width")), uint(tv.get("height")) + if w is None or h is None: + raise DM("texture size") + m["width"], m["height"] = (w if w < 1 << 32 else 0), (h if h < 1 << 32 else 0) + break + check_media(m, "media_not_wai") + media.append(m) + # Step 7 + bundle = {"world": None, "audio": None, "media": media} + if world_nodes: + j = contract_json(params.get("world_contract")) + if not isinstance(j, Obj) or j.get("sim") != KIN: + raise Refuse("sim_not_importable") + ns = [(mint(params["actor"], i + 1), kin_state(p)) for i, p in enumerate(world_nodes)] + bundle["world"] = world_bytes(params["world_contract"], ns, []) + if objects: + audio_rate(params) + bundle["audio"] = audio_bytes(params["audio_contract"], objects, [(h, 0, n) for h, n in src_names]) + return bundle, dropped + + +# ------------------------------------------------------------------ §6 writing USD + +def usd_char_ok(c): + u = ord(c) + return (u <= 0x7F or 0xA0 <= u <= 0xD7FF or 0xF900 <= u <= 0xFDCF or 0xFDF0 <= u <= 0xFFEF + or (0x10000 <= u <= 0xEFFFD and (u & 0xFFFE) != 0xFFFE)) + + +def usd_string(s): + o = '"' + for c in s: + if not usd_char_ok(c): + raise Refuse("not_presentable", "character") + o += {'"': '\\"', "\\": "\\\\", "\n": "\\n", "\r": "\\r", "\t": "\\t"}.get(c) or (f"\\x{ord(c):02X}" if ord(c) < 0x20 or ord(c) == 0x7F else c) + return o + '"' + + +def tuple3(p): + return "(" + ", ".join(usd_number(float(fx_frac(x))) for x in p) + ")" + + +def export_usda(b): + w, a = check_bundle(b) + L = ["#usda 1.0", "(", " customLayerData = {"] + if b["audio"] is not None: + L.append(f' string "wai:audio" = "{b["audio"].hex()}"') + if b["media"]: + L.append(f' string "wai:media" = {usd_string(media_text(b["media"]))}') + L.append(f' string "wai:profile" = "{PROFILE}"') + if b["world"] is not None: + L.append(f' string "wai:world" = "{b["world"].hex()}"') + L += [" }", ' defaultPrim = "World"'] + if a: + if a["duration"] > 1 << 53 or a["rate"] > 1 << 53: + raise Refuse("not_presentable", "duration") + L += [f" endTimeCode = {a['duration']}", " startTimeCode = 0", f" timeCodesPerSecond = {a['rate']}"] + L += [")", "", 'def Xform "World"', "{"] + blocks = [] + if w: + for nid, st in w["nodes"]: + p = position(w["sim"], st) + blk = [f' def Xform "node_{nid.hex()}"', " {"] + if p is not None: + blk += [f" double3 xformOp:translate = {tuple3(p)}", ' uniform token[] xformOpOrder = ["xformOp:translate"]'] + blocks.append(blk + [" }"]) + if w["assets"]: + blk = [' def Scope "Assets"', " {"] + for i, (h, role, name) in enumerate(w["assets"]): + if i: + blk.append("") + blk += [f' def "asset_{i}" (', f" prepend references = @{URN}{h.hex()}@", " )", " {", + f" custom string wai:name = {usd_string(name)}", f" custom int wai:role = {role}", " }"] + blocks.append(blk + [" }"]) + if a and a["objects"]: + blk = [' def Scope "Audio"', " {"] + for i, (src, keys) in enumerate(a["objects"]): + if i: + blk.append("") + if any(keys[j + 1][0] <= keys[j][0] for j in range(len(keys) - 1)): + raise Refuse("not_presentable", "order") + if any(s > 1 << 53 for s, _ in keys): + raise Refuse("not_presentable", "time code") + blk += [f' def Xform "object_{i}"', " {", f' custom string wai:source = "{src.hex()}"'] + if keys: + blk.append(f" double3 xformOp:translate = {tuple3(keys[0][1])}") + if len(keys) >= 2: + blk.append(" double3 xformOp:translate.timeSamples = {") + blk += [f" {s}: {tuple3(p)}," for s, p in keys] + blk.append(" }") + blk.append(' uniform token[] xformOpOrder = ["xformOp:translate"]') + blk.append(" }") + blocks.append(blk + [" }"]) + if b["media"]: + blk = [' def Scope "Media"', " {"] + for i, m in enumerate(b["media"]): + if i: + blk.append("") + blk += [f' def "media_{i}" (', f" prepend references = @{URN}{m['asset'].hex()}@", " )", " {", + f" custom string wai:capability = {usd_string(m['capability'])}"] + if m["width"] is not None: + blk.append(f" custom int wai:height = {m['height']}") + blk.append(f" custom string wai:name = {usd_string(m['name'])}") + if m["width"] is not None: + blk.append(f" custom int wai:width = {m['width']}") + blk.append(" }") + blocks.append(blk + [" }"]) + for i, blk in enumerate(blocks): + if i: + L.append("") + L += blk + L.append("}") + return ("\n".join(L) + "\n").encode() + + +# ------------------------------------------------------------------ §7 reading USD + +def NR(why): + return Refuse("usd_not_read", why) + + +def lex(t): + out, i, n = [], 0, len(t) + while i < n: + c = t[i] + if c in " \t\r\n": + i += 1 + elif c == "#" or t.startswith("//", i): + while i < n and t[i] not in "\r\n": + i += 1 + elif t.startswith("/*", i): + j = t.find("*/", i + 2) + if j < 0: + raise DM("comment") + i = j + 2 + elif c == "-" or c.isdigit() and c.isascii() or (c == "." and i + 1 < n and t[i + 1].isdigit() and t[i + 1].isascii()): + s0 = i + if c == "-": + i += 1 + if t.startswith("inf", i): + i += 3 + else: + while i < n and ((t[i].isascii() and t[i].isdigit()) or t[i] in ".eE" or (t[i] in "+-" and t[i - 1] in "eE")): + i += 1 + out.append(("num", t[s0:i])) + elif c in "()[]{}=:,;.": + out.append(("p", c)) + i += 1 + elif c in "\"'": + if t.startswith(c * 3, i): + raise NR("triple quotes") + i += 1 + s = "" + while True: + if i >= n: + raise DM("string") + x = t[i] + i += 1 + if x == c: + break + if x in "\r\n": + raise DM("line break") + if x != "\\": + s += x + continue + if i >= n: + raise DM("escape") + e = t[i] + i += 1 + simple = {"n": "\n", "r": "\r", "t": "\t", "a": "\a", "b": "\b", "f": "\f", "v": "\v", "\\": "\\", "'": "'", '"': '"'} + if e in simple: + s += simple[e] + elif e == "x": + v = d = 0 + while d < 2 and i < n and t[i] in "0123456789ABCDEF": + v = v * 16 + int(t[i], 16) + i += 1 + d += 1 + if d == 0: + raise DM("empty \\x") + if v >= 0x80: + raise NR("\\x past 0x7F") + s += chr(v) + else: + raise NR("escape") + out.append(("str", s)) + elif c == "@": + if t.startswith("@@", i + 1): + raise NR("@@@") + if t.startswith("@", i + 1): + raise NR("@@@") + i += 1 + s = "" + while True: + if i >= n: + raise DM("asset path") + x = t[i] + i += 1 + if x == "@": + break + if x in "\r\n": + raise DM("line break") + s += x + out.append(("asset", s)) + elif c == "<": + j = t.find(">", i + 1) + if j < 0: + raise DM("path") + out.append(("path", t[i + 1:j])) + i = j + 1 + elif (c.isascii() and c.isalpha()) or c == "_": + s0 = i + while i < n and ((t[i].isascii() and (t[i].isalnum() or t[i] == "_")) or (t[i] == ":" and i + 1 < n and ((t[i + 1].isascii() and t[i + 1].isalpha()) or t[i + 1] == "_"))): + i += 1 + out.append(("id", t[s0:i])) + else: + raise NR(f"character {c!r}") + return out + + +class UP: + def __init__(self, toks): + self.t, self.i = toks, 0 + + def peek(self): + return self.t[self.i] if self.i < len(self.t) else None + + def next(self): + if self.i >= len(self.t): + raise DM("ends early") + self.i += 1 + return self.t[self.i - 1] + + def is_(self, c): + return self.peek() == ("p", c) + + def punct(self, c): + if self.next() != ("p", c): + raise DM(f"{c} due") + + def ident(self): + k, v = self.next() + if k != "id": + raise DM("name due") + return v + + def skip(self): + while self.is_(";"): + self.i += 1 + + def value(self): + k, v = self.next() + if k in ("num", "str", "id", "asset", "path"): + return (k, v) + if (k, v) == ("p", "("): + items = [] + while not self.is_(")"): + items.append(self.value()) + if not self.is_(")"): + self.punct(",") + self.i += 1 + return ("tuple", items) + if (k, v) == ("p", "["): + items = [] + while not self.is_("]"): + items.append(self.value()) + if not self.is_("]"): + self.punct(",") + self.i += 1 + return ("list", items) + if (k, v) == ("p", "{"): + return self.dict_body() + raise DM("value due") + + def dict_body(self): + items = [] + while True: + self.skip() + if self.is_("}"): + self.i += 1 + return ("dict", items) + ty = self.ident() + ty2 = ty + if self.is_("["): + self.i += 1 + self.punct("]") + ty2 += "[]" + k, key = self.next() + if k not in ("str", "id"): + raise DM("key") + self.punct("=") + if ty == "dictionary": + self.punct("{") + v = self.dict_body() + else: + v = self.value() + items.append((ty2, key, v)) + + def samples(self): + self.punct("{") + v = [] + while not self.is_("}"): + k, tcode = self.next() + if k != "num": + raise DM("time code") + self.punct(":") + v.append((tcode, self.value())) + if not self.is_("}"): + self.punct(",") + self.i += 1 + return ("samples", v) + + def prim(self): + s = self.ident() + if s in ("over", "class"): + raise NR(s) + if s != "def": + raise DM("prim due") + p = {"ty": "", "name": None, "refs": [], "props": [], "children": []} + if self.peek() and self.peek()[0] == "id": + p["ty"] = self.next()[1] + k, name = self.next() + if k != "str": + raise DM("prim name") + p["name"] = name + if self.is_("("): + self.i += 1 + while True: + self.skip() + if self.is_(")"): + self.i += 1 + break + if self.peek() and self.peek()[0] == "str": + self.i += 1 + continue + key = self.ident() + op = None + if key in ("prepend", "append", "delete", "add", "reorder"): + op, key = key, self.ident() + if key == "references": + if op not in (None, "prepend", "append"): + raise NR("references op") + self.punct("=") + v = self.value() + its = v[1] if v[0] == "list" else ([] if v == ("id", "None") else [v]) + for it in its: + if it[0] != "asset": + raise NR("not an asset path") + if (self.peek() and self.peek()[0] == "path") or self.is_("("): + raise NR("prim path or parameters") + p["refs"].append(it[1]) + elif key in ("payload", "inherits", "specializes", "variants", "variantSets", "relocates"): + raise NR(key) + else: + if op is not None: + raise NR("list op") + self.punct("=") + self.value() + self.punct("{") + while True: + self.skip() + if self.is_("}"): + self.i += 1 + break + pk = self.peek() + if pk and pk[0] == "id" and pk[1] in ("def", "over", "class"): + c = self.prim() + if any(x["name"] == c["name"] for x in p["children"]): + raise DM("prim twice") + p["children"].append(c) + elif pk and pk[0] == "id" and pk[1] in ("rel", "variantSet", "reorder", "prepend", "append", "delete", "add"): + raise NR(pk[1]) + else: + name, ty, default, smp, special = self.prop() + e = next((x for x in p["props"] if x[0] == name), None) + if e is None: + e = [name, "", None, None] + p["props"].append(e) + if e[1] and e[1] != ty: + raise DM("two types") + e[1] = ty + if special: + if e[3] is not None: + raise DM("samples twice") + e[3] = smp + else: + if e[2] is not None: + raise DM("default twice") + e[2] = default + return p + + def prop(self): + t = self.ident() + if t == "custom": + t = self.ident() + if t in ("uniform", "varying"): + t = self.ident() + ty = t + if self.is_("["): + self.i += 1 + self.punct("]") + ty += "[]" + name = self.ident() + default = smp = None + special = False + if self.is_("."): + self.i += 1 + if self.ident() != "timeSamples": + raise NR("not timeSamples") + self.punct("=") + smp = self.samples() + special = True + elif self.is_("="): + self.i += 1 + default = self.value() + if self.is_("("): + self.i += 1 + while True: + self.skip() + if self.is_(")"): + self.i += 1 + break + if self.peek() and self.peek()[0] == "str": + self.i += 1 + continue + self.ident() + self.punct("=") + self.value() + return name, ty, default, smp, special + + +def parse_usda(b): + try: + text = b.decode("utf-8") + except UnicodeDecodeError: + raise DM("not UTF-8") + if not text.startswith("#usda"): + raise DM("header") + rest = text[5:] + m = re.search(r"[\r\n]", rest) + le = m.start() if m else len(rest) + if rest[:le].strip(" \t") != "1.0": + raise NR("version") + p = UP(lex(rest[le:])) + custom, default_prim, prims = [], None, [] + if p.is_("("): + p.i += 1 + while True: + p.skip() + if p.is_(")"): + p.i += 1 + break + if p.peek() and p.peek()[0] == "str": + p.i += 1 + continue + key = p.ident() + if key in ("subLayers", "relocates", "prepend", "append", "delete"): + raise NR(key) + p.punct("=") + v = p.value() + if key == "customLayerData": + if v[0] != "dict": + raise DM("customLayerData") + custom += [(k, x) for ty, k, x in v[1] if ty == "string"] + elif key == "defaultPrim": + if v[0] != "str": + raise DM("defaultPrim") + default_prim = v[1] + while p.peek() is not None: + c = p.prim() + if any(x["name"] == c["name"] for x in prims): + raise DM("prim twice") + prims.append(c) + return custom, default_prim, prims + + +def import_usda(b, params): + custom, default_prim, prims = parse_usda(b) + wai = [(k, v) for k, v in custom if k.startswith("wai:")] + if wai: + bundle = {"world": None, "audio": None, "media": []} + prof = False + for k, v in wai: + if v[0] != "str": + raise Refuse("carried_malformed", "not a string") + s = v[1] + if k == "wai:profile" and s == PROFILE: + prof = True + elif k in ("wai:world", "wai:audio"): + x = unhex(s) + if x is None: + raise Refuse("carried_malformed", k) + bundle[k[4:]] = x + elif k == "wai:media": + try: + m = media_from(strict_json(s.encode())) + except Refuse: + m = None + if m is None: + raise Refuse("carried_malformed", "media") + bundle["media"] = m + else: + raise Refuse("carried_malformed", k) + if not prof: + raise Refuse("carried_malformed", "profile") + try: + again = export_usda(bundle) + except Refuse as r: + raise Refuse("carried_malformed", r.code) + if again != b: + raise Refuse("presentation_mismatch") + return bundle, [] + # §7.3 + if default_prim is not None: + root = next((x for x in prims if x["name"] == default_prim), None) + if root is None: + raise DM("defaultPrim") + else: + if not prims: + raise DM("no prim") + root = prims[0] + nodes, assets, dropped = [], [], [] + stack = [(root, [0, 0, 0], "/" + root["name"])] + while stack: + pr, base, path = stack.pop() + off = [0, 0, 0] + for name, ty, default, smp in pr["props"]: + if name == "xformOpOrder": + if not (default is not None and default[0] == "list" and default[1] == [("str", "xformOp:translate")]) or smp is not None: + raise Refuse("transform_not_carried", "order") + elif name == "xformOp:translate": + if smp is not None or default is None or default[0] != "tuple": + raise Refuse("transform_not_carried", "translate") + if len(default[1]) != 3: + raise DM("three values") + for i, x in enumerate(default[1]): + if x[0] != "num": + raise DM("numbers") + try: + f = float(x[1]) + except ValueError: + raise DM("number") + if f != f or f in (float("inf"), float("-inf")): + raise Refuse("position_not_exact") + q = to_fx(f) + if q is None: + raise Refuse("position_not_exact") + off[i] = q + elif name.startswith("xformOp:"): + raise Refuse("transform_not_carried", name) + at = [base[i] + off[i] for i in range(3)] + if any(not -(1 << 63) <= x < (1 << 63) for x in at): + raise Refuse("position_not_exact") + if pr["refs"]: + for r in pr["refs"]: + if not (r.startswith(URN) and re.fullmatch(r"[0-9a-f]{64}", r[len(URN):])): + raise Refuse("asset_not_wai", r) + assets.append((bytes.fromhex(r[len(URN):]), 0, pr["name"])) + elif pr["ty"] == "Xform" and any(x[0] == "xformOp:translate" for x in pr["props"]): + nodes.append(at) + elif pr["ty"] not in ("", "Xform", "Scope"): + dropped.append(f"{path}: {pr['ty']}") + for c in reversed(pr["children"]): + stack.append((c, at, path + "/" + c["name"])) + bundle = {"world": None, "audio": None, "media": []} + if nodes or assets: + j = contract_json(params.get("world_contract")) + if not isinstance(j, Obj) or j.get("sim") != KIN: + raise Refuse("sim_not_importable") + ns = [(mint(params["actor"], i + 1), kin_state(p)) for i, p in enumerate(nodes)] + bundle["world"] = world_bytes(params["world_contract"], ns, assets) + return bundle, dropped + + +# ------------------------------------------------------------------ the corpus + +def run(corpus): + doc = json.load(open(os.path.join(corpus, "vectors.json"), encoding="utf-8")) + known = codes() + fails = [] + for inp in doc["inputs"]: + b = open(os.path.join(corpus, inp["path"]), "rb").read() + if hashlib.sha256(b).hexdigest() != inp["sha256"]: + fails.append(f"input {inp['path']} does not match its pin") + files = {} + for f in doc["files"]: + b = open(os.path.join(corpus, f["file"]), "rb").read() + if hashlib.sha256(b).hexdigest() != f["sha256"]: + fails.append(f"{f['file']} does not match its digest") + files[f["file"]] = b + listed = set(files) | {"vectors.json", "README.md", ".gitattributes"} + for root, _, fs in os.walk(corpus): + for x in fs: + rel = os.path.relpath(os.path.join(root, x), corpus).replace(os.sep, "/") + if rel not in listed: + fails.append(f"{rel} is not listed") + + def load(path): + return files[path] if path in files else open(os.path.join(corpus, path), "rb").read() + + def bundle_of(bv): + media = [] + for m in bv["media"]: + media.append({"name": m["name"], "asset": bytes.fromhex(m["asset"]), "capability": m["capability"], "width": m.get("width"), "height": m.get("height")}) + return {"world": load(bv["world"]) if bv["world"] else None, "audio": load(bv["audio"]) if bv["audio"] else None, "media": media} + + exported = [] + for v in doc["vectors"]: + name, op, fmt = v["name"], v["op"], v["format"] + if v["outcome"] == "refuse" and v["code"] not in known: + fails.append(f"{name}: {v['code']} is not in §8") + try: + if op == "export": + got = (export_gltf if fmt == "gltf" else export_usda)(bundle_of(v["bundle"])) + else: + pv = v["params"] or {} + params = {"actor": bytes.fromhex(pv["actor"]) if pv.get("actor") else bytes(32), + "world_contract": pv["world_contract"].encode() if pv.get("world_contract") is not None else None, + "audio_contract": pv["audio_contract"].encode() if pv.get("audio_contract") is not None else None} + got = (import_gltf if fmt == "gltf" else import_usda)(files[v["file"]], params) + outcome = ("accept", got) + except Refuse as r: + outcome = ("refuse", r.code) + if v["outcome"] == "refuse": + if outcome != ("refuse", v["code"]): + fails.append(f"{name}: wanted {v['code']}, got {outcome[0]} {outcome[1] if outcome[0] == 'refuse' else ''}") + continue + if outcome[0] != "accept": + fails.append(f"{name}: wanted accept, got {outcome[1]}") + continue + if op == "export": + if outcome[1] != files[v["file"]]: + fails.append(f"{name}: the document differs from {v['file']}") + else: + exported.append((fmt, files[v["file"]], bundle_of(v["bundle"]))) + else: + want = bundle_of(v["bundle"]) + b, dropped = outcome[1] + if (b["world"], b["audio"]) != (want["world"], want["audio"]) or b["media"] != want["media"]: + fails.append(f"{name}: read to another bundle") + if dropped != v["dropped"]: + fails.append(f"{name}: reported {dropped}, wanted {v['dropped']}") + missing = set(known) - {v.get("code") for v in doc["vectors"]} + if missing: + fails.append(f"codes no vector gives: {sorted(missing)}") + caught = 0 + for fmt, b, want in exported: + for at in sorted({0, len(b) // 4, len(b) // 2, 3 * len(b) // 4, len(b) - 2}): + m = bytearray(b) + m[at] ^= 0x04 + try: + got, _ = (import_gltf if fmt == "gltf" else import_usda)(bytes(m), {"actor": bytes(32), "world_contract": None, "audio_contract": None}) + ok = got != want + except Refuse: + ok = True + if ok: + caught += 1 + else: + fails.append(f"a flip at byte {at} of a {fmt} export read to the same bundle") + return fails, len(doc["vectors"]), caught + + +def main(): + if len(sys.argv) != 2: + print(__doc__) + sys.exit(2) + fails, n, caught = run(sys.argv[1]) + for f in fails: + print("FAIL", f) + if fails: + sys.exit(1) + print(f"scene: every vector verifies ({n}), and every mutated copy is caught ({caught})") + + +if __name__ == "__main__": + main() diff --git a/wai/wai-rs/src/scene_map/gltf.rs b/wai/wai-rs/src/scene_map/gltf.rs index cb72919c4..ac5c1db9e 100644 --- a/wai/wai-rs/src/scene_map/gltf.rs +++ b/wai/wai-rs/src/scene_map/gltf.rs @@ -560,6 +560,7 @@ fn foreign(doc: &Json, p: &ImportParams) -> Result { }; let mut objects: Vec<([u8; 32], Vec<(u64, [i64; 3])>)> = Vec::new(); let mut source_items: Vec = Vec::new(); + let mut source_names: Vec<([u8; 32], String)> = Vec::new(); for &n in &audio_nodes { for s in arr(spatial(n).unwrap(), "sources") { if s.get("type").and_then(Json::as_str) != Some("Object") { @@ -576,6 +577,9 @@ fn foreign(doc: &Json, p: &ImportParams) -> Result { if !source_items.contains(&k) { source_items.push(k); } + if source_names.iter().all(|(h, _)| *h != hash) { + source_names.push((hash, media_items[k].get("name").and_then(Json::as_str).unwrap_or("").to_string())); + } let keys = match anim_of.get(&n) { None => vec![(0u64, pos[&n])], Some(&(ai, si)) => { @@ -622,7 +626,7 @@ fn foreign(doc: &Json, p: &ImportParams) -> Result { if media_of_accessor(a)? == k { let wh = (tv.get("width").and_then(Json::as_u64), tv.get("height").and_then(Json::as_u64)); let (Some(w), Some(h)) = wh else { return Err(bad("a video texture's width or height is not an integer")) }; - size = Some((w as u32, h as u32)); + size = Some((u32::try_from(w).unwrap_or(0), u32::try_from(h).unwrap_or(0))); break; } } @@ -649,14 +653,7 @@ fn foreign(doc: &Json, p: &ImportParams) -> Result { if !objects.is_empty() { let c = p.audio_contract.as_ref().ok_or_else(|| Refusal::new("contract_absent", "audio objects and no audio contract"))?; audio_rate()?; - let mut assets: Vec<(Vec, u8, String)> = Vec::new(); - for (h, _) in &objects { - if assets.iter().all(|a| a.0 != h.as_slice()) { - let k = source_items[assets.len()]; - let name = media_items[k].get("name").and_then(Json::as_str).unwrap_or("").to_string(); - assets.push((h.to_vec(), 0, name)); - } - } + let assets: Vec<(Vec, u8, String)> = source_names.iter().map(|(h, n)| (h.to_vec(), 0, n.clone())).collect(); bundle.audio = Some(audio_bytes(c, &objects, &assets)); } Ok(Imported { bundle, dropped }) -- 2.54.0 (Apple Git-157) From 1f5cfe406854c8ade0e28fa312d0916b437836c4 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:55:38 -0400 Subject: [PATCH 012/242] =?UTF-8?q?wai-rs:=20format=5Fprobe=20=E2=80=94=20?= =?UTF-8?q?header=20probes=20for=20JPEG=20XS,=20H.265=20with=20MV-HEVC,=20?= =?UTF-8?q?H.266,=20LCEVC,=20MHAS,=20AVS3,=20the=20IEEE=201857.11=20header?= =?UTF-8?q?=20and=20the=20projection=20descriptor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each probe identifies a payload as its capability's format and reads the parameters its headers state, or refuses it with one of thirteen codes (extensions/format-probes.md §12). Pure integer code over bytes, feature format_probe, no new dependency. The corpus module generates and checks format-probe-conformance/; wai_format_probe probes a file, generates the corpus and verifies it, with the fetched LCEVC streams. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/Cargo.toml | 15 + wai/wai-rs/src/bin/wai_format_probe.rs | 113 +++ wai/wai-rs/src/format_probe.rs | 177 +++++ wai/wai-rs/src/format_probe/avs3.rs | 217 ++++++ wai/wai-rs/src/format_probe/bits.rs | 266 +++++++ wai/wai-rs/src/format_probe/corpus.rs | 843 ++++++++++++++++++++++ wai/wai-rs/src/format_probe/hevc.rs | 520 +++++++++++++ wai/wai-rs/src/format_probe/jxs.rs | 300 ++++++++ wai/wai-rs/src/format_probe/lcevc.rs | 241 +++++++ wai/wai-rs/src/format_probe/mhas.rs | 164 +++++ wai/wai-rs/src/format_probe/nic.rs | 40 + wai/wai-rs/src/format_probe/projection.rs | 129 ++++ wai/wai-rs/src/format_probe/vvc.rs | 173 +++++ wai/wai-rs/src/lib.rs | 8 + wai/wai-rs/tests/format_probe.rs | 80 ++ 15 files changed, 3286 insertions(+) create mode 100644 wai/wai-rs/src/bin/wai_format_probe.rs create mode 100644 wai/wai-rs/src/format_probe.rs create mode 100644 wai/wai-rs/src/format_probe/avs3.rs create mode 100644 wai/wai-rs/src/format_probe/bits.rs create mode 100644 wai/wai-rs/src/format_probe/corpus.rs create mode 100644 wai/wai-rs/src/format_probe/hevc.rs create mode 100644 wai/wai-rs/src/format_probe/jxs.rs create mode 100644 wai/wai-rs/src/format_probe/lcevc.rs create mode 100644 wai/wai-rs/src/format_probe/mhas.rs create mode 100644 wai/wai-rs/src/format_probe/nic.rs create mode 100644 wai/wai-rs/src/format_probe/projection.rs create mode 100644 wai/wai-rs/src/format_probe/vvc.rs create mode 100644 wai/wai-rs/tests/format_probe.rs diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index e5e2eafc1..8c6c1d300 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -481,6 +481,12 @@ stage_measure = ["staged", "video_int", "audio_int", "splat_int"] # their CMCD version 2 and CMSD reports as RFC 9651 Structured Field Values, and session # metrics. No dependency beyond serde_json; wasm32-clean. telemetry = [] +# Header probes (extensions/format-probes.md): JPEG XS, H.265 with MV-HEVC, H.266, LCEVC, +# MPEG-H 3D Audio's MHAS, AVS3 video and audio, IEEE 1857.11's picture header and the +# projection descriptor, each identifying a payload and reading its parameters or refusing +# it with a code; and the generator and checker of format-probe-conformance/. No dependency +# beyond serde_json and sha2; wasm32-clean. +format_probe = [] # Typed receipt classes on the JWP chain (extensions/jwp-receipts.md §2.4.3–§2.4.7, # §2.6, §3.1–§3.2): `claim` receipts of class energy / lawful-basis / log-inclusion, # the soft-binding class, and recovery under the ancestor rule. Needs `provenance` @@ -1379,6 +1385,15 @@ required-features = ["stage_measure"] # Delivery telemetry (extensions/delivery-telemetry.md): `encode` an observation's CMCD # report, `decode` one, `verify` the corpus telemetry-conformance/, `gen` (never in CI). +[[bin]] +# Header probes (extensions/format-probes.md): `probe ` prints a payload's +# parameters or its refusal; `gen ` writes format-probe-conformance/'s generated files and +# vectors.json from its ext/ (never in CI); `verify [--fetched ]` checks the corpus, +# and with --fetched the LCEVC streams fetch.sh fetched. +name = "wai_format_probe" +path = "src/bin/wai_format_probe.rs" +required-features = ["format_probe"] + [[bin]] name = "wai_telemetry" path = "src/bin/wai_telemetry.rs" diff --git a/wai/wai-rs/src/bin/wai_format_probe.rs b/wai/wai-rs/src/bin/wai_format_probe.rs new file mode 100644 index 000000000..d9f9b4a58 --- /dev/null +++ b/wai/wai-rs/src/bin/wai_format_probe.rs @@ -0,0 +1,113 @@ +//! `wai_format_probe` — header probes (`extensions/format-probes.md`). +//! +//! - `probe `: the payload's parameters as JSON, or its refusal code +//! (exit status 1). +//! - `gen `: writes `/gen/` and `/vectors.json` from `/ext/` (never in CI). +//! - `verify [--fetched ]`: checks every vector, binding and fact of the corpus, +//! that the generated files regenerate byte for byte, and with `--fetched` the LCEVC streams +//! `fetch.sh` fetched, every one of which must be there. + +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; +use std::process::ExitCode; +use wai::format_probe::{corpus, probe}; + +fn walk(root: &Path, dir: &Path, out: &mut BTreeMap>) -> std::io::Result<()> { + for e in std::fs::read_dir(dir)? { + let p = e?.path(); + if p.is_dir() { + walk(root, &p, out)?; + } else { + let rel = p.strip_prefix(root).expect("under the root").to_string_lossy().replace('\\', "/"); + out.insert(rel, std::fs::read(&p)?); + } + } + Ok(()) +} + +/// The corpus's files that the generator and the checker read: `vectors.json`, `ext/` and +/// `gen/`. +fn corpus_files(dir: &Path) -> std::io::Result>> { + let mut out = BTreeMap::new(); + for sub in ["ext", "gen"] { + let d = dir.join(sub); + if d.is_dir() { + walk(dir, &d, &mut out)?; + } + } + let v = dir.join("vectors.json"); + if v.is_file() { + out.insert("vectors.json".into(), std::fs::read(v)?); + } + Ok(out) +} + +fn run() -> Result { + let args: Vec = std::env::args().skip(1).collect(); + match args.first().map(String::as_str) { + Some("probe") if args.len() == 3 => { + let b = std::fs::read(&args[2]).map_err(|e| format!("{}: {e}", args[2]))?; + match probe(&args[1], &b) { + Ok(v) => { + println!("{}", serde_json::to_string_pretty(&v).map_err(|e| e.to_string())?); + Ok(true) + } + Err(r) => { + println!("{}", r.code()); + Ok(false) + } + } + } + Some("gen") if args.len() == 2 => { + let dir = PathBuf::from(&args[1]); + let files = corpus_files(&dir).map_err(|e| e.to_string())?; + let ext: BTreeMap> = files.into_iter().filter(|(k, _)| k.starts_with("ext/")).collect(); + let generated = corpus::generate(&ext)?; + let gen_dir = dir.join("gen"); + if gen_dir.exists() { + std::fs::remove_dir_all(&gen_dir).map_err(|e| e.to_string())?; + } + for (k, v) in &generated { + let p = dir.join(k); + if let Some(parent) = p.parent() { + std::fs::create_dir_all(parent).map_err(|e| e.to_string())?; + } + std::fs::write(&p, v).map_err(|e| format!("{}: {e}", p.display()))?; + } + eprintln!("wrote {} files", generated.len()); + Ok(true) + } + Some("verify") if args.len() == 2 || (args.len() == 4 && args[2] == "--fetched") => { + let files = corpus_files(Path::new(&args[1])).map_err(|e| e.to_string())?; + let mut fails = corpus::verify(&files); + let vectors = files.keys().filter(|k| !k.ends_with(".json")).count(); + if args.len() == 4 { + let mut fetched = BTreeMap::new(); + for e in std::fs::read_dir(&args[3]).map_err(|e| format!("{}: {e}", args[3]))? { + let p = e.map_err(|e| e.to_string())?.path(); + if p.is_file() { + fetched.insert(p.file_name().unwrap().to_string_lossy().into_owned(), std::fs::read(&p).map_err(|e| e.to_string())?); + } + } + fails.extend(corpus::verify_fetched(&fetched)); + } + for f in &fails { + eprintln!("FAIL {f}"); + } + eprintln!("{} files, {} failures", vectors, fails.len()); + Ok(fails.is_empty()) + } + _ => Err("usage: wai_format_probe probe | gen | verify [--fetched ]".into()), + } +} + +fn main() -> ExitCode { + match run() { + Ok(true) => ExitCode::SUCCESS, + Ok(false) => ExitCode::from(1), + Err(e) => { + eprintln!("{e}"); + ExitCode::from(2) + } + } +} diff --git a/wai/wai-rs/src/format_probe.rs b/wai/wai-rs/src/format_probe.rs new file mode 100644 index 000000000..b04380cc1 --- /dev/null +++ b/wai/wai-rs/src/format_probe.rs @@ -0,0 +1,177 @@ +//! Header probes for the sink-supplied formats the registry names (extensions/format-probes.md): +//! each identifies a payload as the format its capability names and reads the parameters +//! its headers state, or refuses it with one of [`Refusal::ALL`]'s codes. No probe decodes +//! a picture or a sound; the decoders stay sink-supplied. +//! +//! Every probe is pure integer code over bytes, with no dependency beyond `serde_json`, so +//! it builds for wasm32. Its result is a JSON object of the format's parameters, written +//! the same way on every machine (keys in sorted order). + +use crate::codecs::*; +use serde_json::Value; + +pub mod avs3; +pub mod bits; +pub mod corpus; +pub mod hevc; +pub mod jxs; +pub mod lcevc; +pub mod mhas; +pub mod nic; +pub mod projection; +pub mod vvc; + +/// Why a probe refuses a payload (format-probes §12). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Refusal { + /// The payload does not begin with the format's signature. + NoSignature, + /// The bytes end inside a structure the probe reads. + Truncated, + /// A structure is where the format does not allow it. + OutOfOrder, + /// A field holds a value the format forbids or reserves. + ReservedValue, + /// A length a structure declares disagrees with the bytes. + LengthMismatch, + /// A structure the format requires is absent. + MissingStructure, + /// A `wai.video.mvhevc` payload with no second view. + NotMultiview, + /// The framing of a `wai.video.jxs` payload is wrong. + FramingInvalid, + /// Bytes follow the end of the payload's last structure. + TrailingBytes, + /// A projection descriptor's fields disagree with each other. + DescriptorInvalid, + /// A projection descriptor names a layer its rendition does not hold. + LayerAbsent, + /// The payload uses a feature whose syntax the probe's sources do not establish. + OutsideProbe, + /// No probe is defined for the capability. + NoProbe, +} + +impl Refusal { + /// Every refusal, in the order of format-probes §12's table. + pub const ALL: [Refusal; 13] = [ + Refusal::NoSignature, Refusal::Truncated, Refusal::OutOfOrder, Refusal::ReservedValue, + Refusal::LengthMismatch, Refusal::MissingStructure, Refusal::NotMultiview, + Refusal::FramingInvalid, Refusal::TrailingBytes, Refusal::DescriptorInvalid, + Refusal::LayerAbsent, Refusal::OutsideProbe, Refusal::NoProbe, + ]; + + pub fn code(self) -> &'static str { + match self { + Refusal::NoSignature => "no_signature", + Refusal::Truncated => "truncated", + Refusal::OutOfOrder => "out_of_order", + Refusal::ReservedValue => "reserved_value", + Refusal::LengthMismatch => "length_mismatch", + Refusal::MissingStructure => "missing_structure", + Refusal::NotMultiview => "not_multiview", + Refusal::FramingInvalid => "framing_invalid", + Refusal::TrailingBytes => "trailing_bytes", + Refusal::DescriptorInvalid => "descriptor_invalid", + Refusal::LayerAbsent => "layer_absent", + Refusal::OutsideProbe => "outside_probe", + Refusal::NoProbe => "no_probe", + } + } + + pub fn from_code(code: &str) -> Option { + Refusal::ALL.iter().copied().find(|r| r.code() == code) + } +} + +impl std::fmt::Display for Refusal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.code()) + } +} + +impl std::error::Error for Refusal {} + +/// Where a probe's syntax comes from (format-probes §2.3). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Basis { + /// The format's own openly readable text. + Standard, + /// Open implementations of a format whose text is not openly readable. + Implementation, + /// WAI's own format. + Wai, +} + +impl Basis { + pub fn label(self) -> &'static str { + match self { + Basis::Standard => "standard", + Basis::Implementation => "implementation", + Basis::Wai => "wai", + } + } +} + +/// The capabilities with a probe, each with its basis, in format-probes §2.4's order. +pub const PROBES: &[(&str, Basis)] = &[ + (CAP_IMAGE_JXS, Basis::Implementation), + (CAP_VIDEO_JXS, Basis::Implementation), + (CAP_VIDEO_HEVC, Basis::Standard), + (CAP_VIDEO_MVHEVC, Basis::Standard), + (CAP_VIDEO_VVC, Basis::Standard), + (CAP_VIDEO_LCEVC, Basis::Implementation), + (CAP_AUDIO_MPEGH, Basis::Implementation), + (CAP_VIDEO_AVS3, Basis::Implementation), + (CAP_AUDIO_AVS3, Basis::Implementation), + (CAP_IMAGE_IEEE1857_11, Basis::Implementation), + (CAP_META_PROJECTION, Basis::Wai), +]; + +/// The basis of `capability`'s probe, or `None` when it has none. +pub fn basis(capability: &str) -> Option { + PROBES.iter().find(|(c, _)| *c == capability).map(|&(_, b)| b) +} + +/// Probes `payload` as the payload of `capability`. +pub fn probe(capability: &str, payload: &[u8]) -> Result { + match capability { + CAP_IMAGE_JXS => jxs::probe_image(payload), + CAP_VIDEO_JXS => jxs::probe_video(payload), + CAP_VIDEO_HEVC => hevc::probe(payload, false), + CAP_VIDEO_MVHEVC => hevc::probe(payload, true), + CAP_VIDEO_VVC => vvc::probe(payload), + CAP_VIDEO_LCEVC => lcevc::probe(payload), + CAP_AUDIO_MPEGH => mhas::probe(payload), + CAP_VIDEO_AVS3 => avs3::probe_video(payload), + CAP_AUDIO_AVS3 => avs3::probe_audio(payload), + CAP_IMAGE_IEEE1857_11 => nic::probe(payload), + CAP_META_PROJECTION => projection::probe(payload), + _ => Err(Refusal::NoProbe), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn every_probed_capability_is_registered_and_dispatches() { + for &(c, _) in PROBES { + assert!(is_known_capability(c), "{c}"); + assert_ne!(probe(c, &[]), Err(Refusal::NoProbe), "{c}"); + } + assert_eq!(probe(CAP_IMAGE_PNG, &[]), Err(Refusal::NoProbe)); + } + + #[test] + fn codes_are_distinct_and_read_back() { + for r in Refusal::ALL { + assert_eq!(Refusal::from_code(r.code()), Some(r)); + } + let mut codes: Vec<&str> = Refusal::ALL.iter().map(|r| r.code()).collect(); + codes.sort_unstable(); + codes.dedup(); + assert_eq!(codes.len(), Refusal::ALL.len()); + } +} diff --git a/wai/wai-rs/src/format_probe/avs3.rs b/wai/wai-rs/src/format_probe/avs3.rs new file mode 100644 index 000000000..e6086f259 --- /dev/null +++ b/wai/wai-rs/src/format_probe/avs3.rs @@ -0,0 +1,217 @@ +//! The AVS3 probes (`wai.video.avs3`, `wai.audio.avs3`; format-probes §8 and §9). T/AI +//! 109.2 and T/AI 109.3 are not openly readable. The video sequence header and the +//! pseudo-start-code prevention are read as an open decoder reads them, within the values +//! ETSI TS 101 154 V2.10.1 §5.16.2 constrains; the audio frame header as an open +//! demultiplexer reads it, within the values that document's §6.10.2 constrains. + +use super::bits::BitReader; +use super::Refusal; +use serde_json::{json, Value}; + +pub const SEQUENCE_HEADER: u8 = 0xb0; +pub const SEQUENCE_END: u8 = 0xb1; +pub const INTRA_PICTURE: u8 = 0xb3; +pub const INTER_PICTURE: u8 = 0xb6; + +/// The units of an AVS3 video stream: the byte after each start code prefix `0x000001` +/// and the bytes up to the next prefix. The stream must begin, after any zero bytes, with +/// a prefix. +pub fn units(b: &[u8]) -> Result, Refusal> { + let mut i = 0; + while i < b.len() && b[i] == 0 { + i += 1; + } + if i < 2 || i >= b.len() || b[i] != 1 { + return Err(Refusal::NoSignature); + } + let mut starts = Vec::new(); + let mut j = 0; + while j + 3 < b.len() { + if b[j] == 0 && b[j + 1] == 0 && b[j + 2] == 1 { + starts.push(j + 3); + j += 3; + } else { + j += 1; + } + } + let mut out = Vec::new(); + for (k, &s) in starts.iter().enumerate() { + let end = if k + 1 < starts.len() { starts[k + 1] - 3 } else { b.len() }; + out.push((b[s], &b[s + 1..end])); + } + Ok(out) +} + +/// Removes the pseudo-start-code prevention bits: in each byte `0x02` that follows two +/// zero bytes of the unit, its two low bits `10` (format-probes §8.2). +pub fn demulate(b: &[u8]) -> Vec { + let mut bits: Vec = Vec::with_capacity(b.len() * 8); + for (i, &x) in b.iter().enumerate() { + let n = if i >= 2 && b[i - 2] == 0 && b[i - 1] == 0 && x == 2 { 6 } else { 8 }; + for k in 0..n { + bits.push((x >> (7 - k)) & 1); + } + } + bits.chunks(8).map(|c| c.iter().enumerate().fold(0u8, |a, (k, &bit)| a | (bit << (7 - k)))).collect() +} + +fn marker(r: &mut BitReader) -> Result<(), Refusal> { + if r.u(1)? != 1 { + return Err(Refusal::ReservedValue); + } + Ok(()) +} + +pub fn sequence_header(rbsp: &[u8]) -> Result { + let mut r = BitReader::new(rbsp); + let profile_id = r.u(8)?; + let level_id = r.u(8)?; + let progressive = r.flag()?; + let field_coded = r.flag()?; + if r.flag()? { + // A library stream: what follows is not read by this probe. + return Err(Refusal::OutsideProbe); + } + if r.flag()? { + // Library pictures enabled: a further flag follows whose place the open texts do + // not establish. + return Err(Refusal::OutsideProbe); + } + marker(&mut r)?; + let width = r.u(14)?; + marker(&mut r)?; + let height = r.u(14)?; + let chroma_format = r.u(2)?; + let sample_precision = r.u(3)?; + let encoding_precision = if profile_id == 0x22 || profile_id == 0x32 { Some(r.u(3)?) } else { None }; + marker(&mut r)?; + let aspect_ratio = r.u(4)?; + let frame_rate_code = r.u(4)?; + marker(&mut r)?; + let bit_rate_lower = r.u(18)?; + marker(&mut r)?; + let bit_rate_upper = r.u(12)?; + let low_delay = r.flag()?; + let temporal_id = r.flag()?; + marker(&mut r)?; + let bbv = r.u(18)?; + marker(&mut r)?; + let max_dpb_size = r.u(4)? + 1; + if width == 0 || height == 0 || chroma_format == 0 || sample_precision == 0 || frame_rate_code == 0 || frame_rate_code > 13 { + return Err(Refusal::ReservedValue); + } + Ok(json!({ + "profile_id": profile_id, + "level_id": level_id, + "progressive": progressive, + "field_coded": field_coded, + "size": [width, height], + "chroma_format": chroma_format, + "sample_precision": sample_precision, + "encoding_precision": encoding_precision, + "aspect_ratio": aspect_ratio, + "frame_rate_code": frame_rate_code, + "bit_rate": (bit_rate_upper << 18) | bit_rate_lower, + "low_delay": low_delay, + "temporal_id_enabled": temporal_id, + "bbv_buffer_size": bbv, + "max_dpb_size": max_dpb_size, + })) +} + +pub fn probe_video(b: &[u8]) -> Result { + let us = units(b)?; + let Some(&(first, _)) = us.first() else { return Err(Refusal::NoSignature) }; + let Some(seq) = us.iter().find(|(c, _)| *c == SEQUENCE_HEADER) else { return Err(Refusal::MissingStructure) }; + if first != SEQUENCE_HEADER { + return Err(Refusal::OutOfOrder); + } + let mut out = sequence_header(&demulate(seq.1))?; + let intra = us.iter().filter(|(c, _)| *c == INTRA_PICTURE).count(); + let inter = us.iter().filter(|(c, _)| *c == INTER_PICTURE).count(); + if intra == 0 { + return Err(Refusal::MissingStructure); + } + out["pictures"] = json!([intra, inter]); + out["sequence_end"] = json!(us.iter().any(|(c, _)| *c == SEQUENCE_END)); + Ok(out) +} + +/// The sampling rates of `sampling_frequency_index` 0 to 8. +pub const AUDIO_RATES: [u32; 9] = [192000, 96000, 48000, 44100, 32000, 24000, 22050, 16000, 8000]; + +/// `wai.audio.avs3`: the first frame header of the stream (format-probes §9). +pub fn probe_audio(b: &[u8]) -> Result { + let mut r = BitReader::new(b); + if r.u(12).map_err(|_| Refusal::NoSignature)? != 0xfff { + return Err(Refusal::NoSignature); + } + // The lossless codec (1) and frames carrying ancillary data are outside what the open + // reading covers; any other codec id is not one it defines. + let codec_id = r.u(4)?; + if codec_id == 1 { + return Err(Refusal::OutsideProbe); + } + if codec_id != 2 { + return Err(Refusal::ReservedValue); + } + if r.flag()? { + return Err(Refusal::OutsideProbe); + } + let nn_type = r.u(3)?; + if nn_type > 1 { + return Err(Refusal::ReservedValue); + } + let coding_profile = r.u(3)?; + if coding_profile > 2 { + return Err(Refusal::ReservedValue); + } + let sfi = r.u(4)?; + if sfi > 8 { + return Err(Refusal::ReservedValue); + } + r.skip(8)?; + let mut out = json!({ + "codec_id": codec_id, + "nn_type": nn_type, + "coding_profile": coding_profile, + "sampling_rate": AUDIO_RATES[sfi as usize], + }); + match coding_profile { + 0 => out["channel_number_index"] = json!(r.u(7)?), + 1 => { + let soundbed = r.u(2)?; + out["soundbed_type"] = json!(soundbed); + match soundbed { + 0 => { + out["objects"] = json!(r.u(7)? + 1); + out["bitrate_index_per_object"] = json!(r.u(4)?); + } + 1 => { + out["channel_number_index"] = json!(r.u(7)?); + out["bitrate_index"] = json!(r.u(4)?); + out["objects"] = json!(r.u(7)? + 1); + out["bitrate_index_per_object"] = json!(r.u(4)?); + } + _ => return Err(Refusal::ReservedValue), + } + } + _ => { + let order = r.u(4)? + 1; + if !(1..=3).contains(&order) { + return Err(Refusal::ReservedValue); + } + out["ambisonic_order"] = json!(order); + } + } + let resolution = r.u(2)?; + if resolution > 2 { + return Err(Refusal::ReservedValue); + } + out["resolution_bits"] = json!(8 * (resolution + 1)); + if coding_profile != 1 { + out["bitrate_index"] = json!(r.u(4)?); + } + r.skip(8)?; + Ok(out) +} diff --git a/wai/wai-rs/src/format_probe/bits.rs b/wai/wai-rs/src/format_probe/bits.rs new file mode 100644 index 000000000..becce9884 --- /dev/null +++ b/wai/wai-rs/src/format_probe/bits.rs @@ -0,0 +1,266 @@ +//! Bit-level reading and writing shared by the probes: an MSB-first bit reader with +//! `u(n)` and Exp-Golomb `ue(v)`, the Annex B byte-stream split of ITU-T H.265 and H.266 +//! (and the start-code form ISO/IEC 23094-2 uses), and the removal of emulation-prevention +//! bytes. The writer is the corpus generator's. + +use super::Refusal; + +/// An MSB-first reader over a byte slice. Every read past the end is +/// [`Refusal::Truncated`]. +pub struct BitReader<'a> { + data: &'a [u8], + pos: usize, +} + +impl<'a> BitReader<'a> { + pub fn new(data: &'a [u8]) -> Self { + Self { data, pos: 0 } + } + /// The bit position from the start. + pub fn position(&self) -> usize { + self.pos + } + /// Bits left to read. + pub fn remaining(&self) -> usize { + self.data.len() * 8 - self.pos + } + pub fn byte_aligned(&self) -> bool { + self.pos % 8 == 0 + } + /// `u(n)` for `n` ≤ 64. + pub fn u(&mut self, n: u32) -> Result { + debug_assert!(n <= 64); + if self.remaining() < n as usize { + return Err(Refusal::Truncated); + } + let mut v: u64 = 0; + for _ in 0..n { + let byte = self.data[self.pos / 8]; + let bit = (byte >> (7 - (self.pos % 8))) & 1; + v = (v << 1) | u64::from(bit); + self.pos += 1; + } + Ok(v) + } + pub fn flag(&mut self) -> Result { + Ok(self.u(1)? == 1) + } + /// Skips `n` bits. + pub fn skip(&mut self, n: usize) -> Result<(), Refusal> { + if self.remaining() < n { + return Err(Refusal::Truncated); + } + self.pos += n; + Ok(()) + } + /// `ue(v)` (ITU-T H.265 §9.2, H.266 §9.2). A code with more than 31 leading zero bits + /// has no value a syntax element of these standards may take: [`Refusal::ReservedValue`]. + pub fn ue(&mut self) -> Result { + let mut zeros = 0u32; + while self.u(1)? == 0 { + zeros += 1; + if zeros > 31 { + return Err(Refusal::ReservedValue); + } + } + if zeros == 0 { + return Ok(0); + } + Ok((1u64 << zeros) - 1 + self.u(zeros)?) + } + /// Skips to the next byte boundary, requiring every skipped bit to equal `bit`, or any + /// value when `bit` is `None`. + pub fn align(&mut self, bit: Option) -> Result<(), Refusal> { + while !self.byte_aligned() { + let b = self.u(1)?; + if let Some(want) = bit { + if b != want { + return Err(Refusal::ReservedValue); + } + } + } + Ok(()) + } +} + +/// An MSB-first bit writer (the corpus generator's). +#[derive(Default)] +pub struct BitWriter { + out: Vec, + acc: u8, + nbits: u32, +} + +impl BitWriter { + pub fn new() -> Self { + Self::default() + } + pub fn u(&mut self, n: u32, v: u64) { + for i in (0..n).rev() { + self.acc = (self.acc << 1) | ((v >> i) & 1) as u8; + self.nbits += 1; + if self.nbits == 8 { + self.out.push(self.acc); + self.acc = 0; + self.nbits = 0; + } + } + } + pub fn flag(&mut self, b: bool) { + self.u(1, u64::from(b)); + } + pub fn ue(&mut self, v: u64) { + let x = v + 1; + let len = 64 - x.leading_zeros(); + self.u(len - 1, 0); + self.u(len, x); + } + pub fn byte_aligned(&self) -> bool { + self.nbits == 0 + } + /// Pads with `bit` to the next byte boundary. + pub fn align(&mut self, bit: u64) { + while self.nbits != 0 { + self.u(1, bit); + } + } + /// `rbsp_trailing_bits()`: a one, then zeros to the byte boundary. + pub fn trailing(&mut self) { + self.u(1, 1); + self.align(0); + } + pub fn bytes(&mut self, b: &[u8]) { + for &x in b { + self.u(8, u64::from(x)); + } + } + pub fn finish(mut self) -> Vec { + self.align(0); + self.out + } +} + +/// One NAL unit of an Annex B byte stream: its bytes after the start code, emulation +/// prevention still in place, trailing zero bytes removed. +#[derive(Debug, Clone, Copy)] +pub struct NalSpan<'a> { + pub bytes: &'a [u8], +} + +/// Splits an Annex B byte stream (ITU-T H.265 and H.266 Annex B; ISO/IEC 23094-2's byte +/// stream) into NAL units. The stream must begin, after any leading zero bytes, with a +/// start code prefix `0x000001`; otherwise [`Refusal::NoSignature`]. A NAL unit ends where +/// the next start code prefix begins; the zero bytes before a prefix belong to no NAL unit. +pub fn annex_b_units(stream: &[u8]) -> Result>, Refusal> { + let mut i = 0; + while i < stream.len() && stream[i] == 0 { + i += 1; + } + if i < 2 || i >= stream.len() || stream[i] != 1 { + return Err(Refusal::NoSignature); + } + let mut starts = Vec::new(); + let mut j = 0; + while j + 2 < stream.len() { + if stream[j] == 0 && stream[j + 1] == 0 && stream[j + 2] == 1 { + starts.push(j + 3); + j += 3; + } else { + j += 1; + } + } + let mut units = Vec::with_capacity(starts.len()); + for (k, &s) in starts.iter().enumerate() { + let mut end = if k + 1 < starts.len() { starts[k + 1] - 3 } else { stream.len() }; + while end > s && stream[end - 1] == 0 { + end -= 1; + } + units.push(NalSpan { bytes: &stream[s..end] }); + } + Ok(units) +} + +/// Removes emulation-prevention bytes: in every `0x000003`, the `0x03` (ITU-T H.265 and +/// H.266 §7.4.2, `emulation_prevention_three_byte`). +pub fn unescape(nal: &[u8]) -> Vec { + let mut out = Vec::with_capacity(nal.len()); + let mut zeros = 0; + for &b in nal { + if zeros >= 2 && b == 3 { + zeros = 0; + continue; + } + if b == 0 { + zeros += 1; + } else { + zeros = 0; + } + out.push(b); + } + out +} + +/// Inserts emulation-prevention bytes (the generator's inverse of [`unescape`]). +pub fn escape(rbsp: &[u8]) -> Vec { + let mut out = Vec::with_capacity(rbsp.len() + rbsp.len() / 64); + let mut zeros = 0; + for &b in rbsp { + if zeros >= 2 && b <= 3 { + out.push(3); + zeros = 0; + } + if b == 0 { + zeros += 1; + } else { + zeros = 0; + } + out.push(b); + } + out +} + +/// Prefixes each NAL unit with a four-byte start code `0x00000001`. +pub fn annex_b(units: &[Vec]) -> Vec { + let mut out = Vec::new(); + for u in units { + out.extend_from_slice(&[0, 0, 0, 1]); + out.extend_from_slice(u); + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn exp_golomb_round_trips() { + let mut w = BitWriter::new(); + for v in [0u64, 1, 2, 3, 7, 254, 1 << 20, (1 << 32) - 2] { + w.ue(v); + } + let b = w.finish(); + let mut r = BitReader::new(&b); + for v in [0u64, 1, 2, 3, 7, 254, 1 << 20, (1 << 32) - 2] { + assert_eq!(r.ue().unwrap(), v); + } + } + + #[test] + fn emulation_prevention_round_trips() { + let rbsp = [0u8, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4]; + let e = escape(&rbsp); + assert_eq!(e, [0, 0, 3, 0, 0, 3, 0, 1, 0, 0, 3, 2, 0, 0, 3, 3, 0, 0, 4]); + assert_eq!(unescape(&e), rbsp); + } + + #[test] + fn annex_b_needs_a_start_code() { + assert_eq!(annex_b_units(&[1, 2, 3]).unwrap_err(), Refusal::NoSignature); + assert_eq!(annex_b_units(&[0, 1, 2]).unwrap_err(), Refusal::NoSignature); + let u = annex_b_units(&[0, 0, 0, 1, 0x40, 1, 0, 0, 1, 0x42, 1, 0, 0]).unwrap(); + assert_eq!(u.len(), 2); + assert_eq!(u[0].bytes, &[0x40, 1]); + assert_eq!(u[1].bytes, &[0x42, 1]); + } +} diff --git a/wai/wai-rs/src/format_probe/corpus.rs b/wai/wai-rs/src/format_probe/corpus.rs new file mode 100644 index 000000000..eedb0cdd9 --- /dev/null +++ b/wai/wai-rs/src/format_probe/corpus.rs @@ -0,0 +1,843 @@ +//! The corpus `format-probe-conformance/` (format-probes §13): the external vectors real +//! encoders wrote (`ext/`, committed, read here), the vectors this generator writes +//! (`gen/`), the negative controls it derives from both, and `vectors.json`, which states +//! every vector's outcome. [`generate`] is deterministic: the same `ext/` gives the same +//! bytes on every machine. [`verify`] reads a corpus and reports every disagreement. + +use super::bits::{annex_b, annex_b_units, escape, BitWriter}; +use super::mhas::write_escaped; +use super::projection::{Projection, DEG}; +use super::*; +use serde_json::{json, Map, Value}; +use sha2::{Digest, Sha256}; +use std::collections::BTreeMap; + +fn hex(b: &[u8]) -> String { + b.iter().map(|x| format!("{x:02x}")).collect() +} + +fn sha256(b: &[u8]) -> String { + hex(&Sha256::digest(b)) +} + +/// The independently known facts of the external vectors: what each encoder was asked +/// for (tools/make_external.sh), as `(file, JSON pointer into the probe's parameters, +/// value)`. The generator refuses to write a corpus whose probe disagrees with one. +pub const FACTS: &[(&str, &str, &str)] = &[ + ("ext/vvc/main10_64x64.266", "/cropped", "[64,64]"), + ("ext/vvc/main10_64x64.266", "/chroma_format_idc", "1"), + ("ext/vvc/main10_64x64.266", "/profile_idc", "1"), + ("ext/vvc/main10_100x58.266", "/cropped", "[100,58]"), + ("ext/mvhevc/2view_64x64.265", "/cropped", "[64,64]"), + ("ext/mvhevc/2view_64x64.265", "/multilayer/views", "2"), + ("ext/mvhevc/2view_64x64.265", "/multilayer/view_ids", "[1,0]"), + ("ext/mvhevc/2view_64x64.265", "/multilayer/multiview_profile", "true"), + ("ext/mvhevc/2view_64x64.265", "/layers_present", "[0,1]"), + ("ext/hevc/base_64x64.265", "/layers_present", "[0]"), + ("ext/hevc/base_64x64.265", "/bit_depth", "[8,8]"), + ("ext/jxs/main422_10_64x64.jxs", "/structure", "\"4:2:2\""), + ("ext/jxs/main422_10_64x64.jxs", "/rtp/depth", "10"), + ("ext/jxs/main422_10_64x64.jxs", "/width", "64"), + ("ext/jxs/rgb_rct_8_64x64.jxs", "/colour_transform", "1"), + ("ext/jxs/rgb_rct_8_64x64.jxs", "/rtp/sampling", "\"RGB\""), + ("ext/jxs/lossless_444_8_48x32.jxs", "/width", "48"), + ("ext/jxs/lossless_444_8_48x32.jxs", "/height", "32"), + ("ext/jxs/lossless_444_8_48x32.jxs", "/fractional_bits", "0"), + ("ext/jxs-video/main420_8_64x48_3f.jxsv", "/frames", "3"), + ("ext/jxs-video/main420_8_64x48_3f.jxsv", "/first/structure", "\"4:2:0\""), + ("ext/jxs-video/main420_8_64x48_3f.jxsv", "/fps", "[30,1]"), + ("ext/avs3/main_128x64.avs3", "/size", "[128,64]"), + ("ext/avs3/main_128x64.avs3", "/sample_precision", "1"), + ("ext/avs3/main_128x64.avs3", "/chroma_format", "1"), +]; + +/// The capability each external directory holds. +fn ext_capability(path: &str) -> &'static str { + match path.split('/').nth(1) { + Some("vvc") => CAP_VIDEO_VVC, + Some("hevc") => CAP_VIDEO_HEVC, + Some("mvhevc") => CAP_VIDEO_MVHEVC, + Some("jxs") => CAP_IMAGE_JXS, + Some("jxs-video") => CAP_VIDEO_JXS, + Some("avs3") => CAP_VIDEO_AVS3, + _ => "", + } +} + +struct Builder { + files: BTreeMap>, + vectors: Vec, +} + +impl Builder { + fn add(&mut self, name: &str, cap: &str, file: &str, origin: &str, bytes: Vec) { + let outcome = match probe(cap, &bytes) { + Ok(p) => json!({ "accept": p }), + Err(r) => json!({ "refuse": r.code() }), + }; + self.vectors.push(json!({ + "name": name, + "capability": cap, + "file": file, + "origin": origin, + "sha256": sha256(&bytes), + "outcome": outcome, + })); + if file.starts_with("gen/") { + self.files.insert(file.to_string(), bytes); + } + } + fn put(&mut self, name: &str, cap: &str, dir: &str, origin: &str, bytes: Vec) { + let file = format!("gen/{dir}/{name}.bin"); + self.add(name, cap, &file, origin, bytes); + } +} + +// ---- H.265 and H.266 mutations ------------------------------------------------------ + +fn nals(b: &[u8]) -> Vec> { + annex_b_units(b).expect("an external Annex B vector").iter().map(|u| u.bytes.to_vec()).collect() +} + +fn hevc_type(n: &[u8]) -> u8 { + (n[0] >> 1) & 0x3f +} + +fn vvc_type(n: &[u8]) -> u8 { + n[1] >> 3 +} + +// ---- LCEVC -------------------------------------------------------------------------- + +fn lcevc_block(kind: u8, body: &[u8]) -> Vec { + let mut out = Vec::new(); + if body.len() <= 5 { + out.push(((body.len() as u8) << 5) | kind); + } else { + out.push((7 << 5) | kind); + let mut groups = Vec::new(); + let mut v = body.len(); + loop { + groups.push((v & 0x7f) as u8); + v >>= 7; + if v == 0 { + break; + } + } + for (i, g) in groups.iter().rev().enumerate() { + out.push(if i + 1 < groups.len() { g | 0x80 } else { *g }); + } + } + out.extend_from_slice(body); + out +} + +fn lcevc_nal(idr: bool, blocks: &[Vec]) -> Vec { + let nut = if idr { lcevc::NUT_IDR } else { lcevc::NUT_NON_IDR }; + let mut rbsp: Vec = blocks.concat(); + rbsp.push(0x80); + let mut n = vec![0x41 | (nut << 1), 0xff]; + n.extend_from_slice(&escape(&rbsp)); + n +} + +/// A sequence configuration: profile, level, sublevel and an optional window. +fn seq_config(profile: u64, level: u64, sublevel: u64, window: Option<[u64; 4]>, reserved: u64) -> Vec { + let mut w = BitWriter::new(); + w.u(4, profile); + w.u(4, level); + w.u(2, sublevel); + w.flag(window.is_some()); + w.u(5, reserved); + let mut out = w.finish(); + if let Some(win) = window { + for v in win { + let mut groups = Vec::new(); + let mut x = v; + loop { + groups.push((x & 0x7f) as u8); + x >>= 7; + if x == 0 { + break; + } + } + for (i, g) in groups.iter().rev().enumerate() { + out.push(if i + 1 < groups.len() { g | 0x80 } else { *g }); + } + } + } + out +} + +#[derive(Clone, Copy)] +struct Global { + resolution_type: u64, + temporal: bool, + upsample: u64, + tiles: u64, + depth: (u64, u64), + chroma: u64, + custom: Option<(u64, u64)>, +} + +fn global_config(g: Global) -> Vec { + let mut w = BitWriter::new(); + w.flag(false); + w.u(6, g.resolution_type); + w.u(1, 0); + w.u(2, g.chroma); + w.u(2, g.depth.0); + w.u(2, g.depth.1); + w.flag(false); + w.flag(false); + w.flag(false); + w.flag(g.temporal); + w.u(3, g.upsample); + w.flag(false); + w.u(2, 1); + w.u(2, 2); + w.u(2, g.tiles); + w.u(2, 0); + w.flag(false); + w.flag(false); + if g.upsample == 4 { + for c in [1500u64, 800, 300, 40] { + w.u(16, c); + } + } + if g.tiles > 0 { + if g.tiles == 3 { + w.u(16, 256); + w.u(16, 128); + } + w.u(8, 0); + } + if let Some((cw, ch)) = g.custom { + w.u(16, cw); + w.u(16, ch); + } + w.finish() +} + +const GLOBAL: Global = Global { resolution_type: 26, temporal: true, upsample: 2, tiles: 0, depth: (0, 0), chroma: 1, custom: None }; + +fn lcevc_stream(seq: &[u8], glob: &[u8], pictures: usize) -> Vec { + // picture_config and encoded_data bodies: fixed bytes, one holding 00 00 01 so that + // emulation prevention is exercised. + let pic = [0x80u8, 0x12, 0x00]; + let data = [0u8, 0, 1, 0x55, 0, 0, 0, 0x2a, 0x99]; + let mut units = vec![lcevc_nal(true, &[lcevc_block(0, seq), lcevc_block(1, glob), lcevc_block(2, &pic), lcevc_block(3, &data)])]; + for _ in 1..pictures { + units.push(lcevc_nal(false, &[lcevc_block(2, &pic), lcevc_block(3, &data)])); + } + annex_b(&units) +} + +// ---- MHAS --------------------------------------------------------------------------- + +fn mhas_packet(kind: u64, label: u64, payload: &[u8]) -> Vec { + let mut w = BitWriter::new(); + write_escaped(&mut w, kind, 3, 8, 8); + write_escaped(&mut w, label, 2, 8, 32); + write_escaped(&mut w, payload.len() as u64, 11, 24, 24); + let mut out = w.finish(); + out.extend_from_slice(payload); + out +} + +fn mhas_config(pl: u64, sfi: u64, rate: Option, layout: u64, speakers: u64) -> Vec { + let mut w = BitWriter::new(); + w.u(8, pl); + w.u(5, sfi); + if let Some(r) = rate { + w.u(24, r); + } + w.u(3, 1); + w.u(1, 0); + w.u(1, 0); + w.u(2, layout); + if layout == 0 { + w.u(6, speakers); + } else { + write_escaped(&mut w, speakers - 1, 5, 8, 16); + for _ in 0..speakers { + w.u(7, 2); + } + } + // the rest of a configuration this probe does not read: fixed filler bits + w.u(8, 0xa0); + w.finish() +} + +fn mhas_stream(cfg: &[u8], frames: usize, sync: bool) -> Vec { + let mut out = Vec::new(); + if sync { + out.extend(mhas_packet(mhas::PACTYP_SYNC, 0, &[mhas::SYNC_BYTE])); + } + out.extend(mhas_packet(mhas::PACTYP_MPEGH3DACFG, 1, cfg)); + for i in 0..frames { + out.extend(mhas_packet(mhas::PACTYP_MPEGH3DAFRAME, 1, &[0x21, i as u8, 0x7e, 0x00, 0x3c])); + } + out +} + +// ---- AVS3 --------------------------------------------------------------------------- + +/// Inserts the pseudo-start-code prevention bits: `10` after every 22 zero bits that end +/// at the sixth bit of a byte (the inverse of [`avs3::demulate`]). +fn emulate(rbsp: &[u8]) -> Vec { + let mut w = BitWriter::new(); + let mut zeros = 0u32; + let mut written = 0u64; + for &x in rbsp { + for k in 0..8 { + let bit = u64::from((x >> (7 - k)) & 1); + w.u(1, bit); + written += 1; + zeros = if bit == 0 { zeros + 1 } else { 0 }; + if zeros >= 22 && written % 8 == 6 { + w.u(2, 2); + written += 2; + zeros = 0; + } + } + } + w.finish() +} + +/// A sequence header's fields, as [`avs3::sequence_header`] reads them. +fn avs3_sequence(profile: u64, width: u64, height: u64, frame_rate_code: u64, bit_rate: u64, library: bool) -> Vec { + let mut w = BitWriter::new(); + w.u(8, profile); + w.u(8, 0x20); + w.flag(true); + w.flag(false); + w.flag(library); + w.flag(false); + w.u(1, 1); + w.u(14, width); + w.u(1, 1); + w.u(14, height); + w.u(2, 1); + w.u(3, 1); + if profile == 0x22 || profile == 0x32 { + w.u(3, 1); + } + w.u(1, 1); + w.u(4, 3); + w.u(4, frame_rate_code); + w.u(1, 1); + w.u(18, bit_rate & 0x3ffff); + w.u(1, 1); + w.u(12, bit_rate >> 18); + w.flag(false); + w.flag(false); + w.u(1, 1); + w.u(18, 0); + w.u(1, 1); + w.u(4, 3); + // the reference-picture-list part, not read by the probe: zero bits + w.u(16, 0); + w.trailing(); + w.finish() +} + +fn avs3_unit(code: u8, payload: &[u8]) -> Vec { + let mut u = vec![0, 0, 1, code]; + u.extend_from_slice(payload); + u +} + +fn aatf(profile: u64, sfi: u64, codec: u64, resolution: u64, tail: &[u64]) -> Vec { + let mut w = BitWriter::new(); + w.u(12, 0xfff); + w.u(4, codec); + w.flag(false); + w.u(3, 0); + w.u(3, profile); + w.u(4, sfi); + w.u(8, 0); + match profile { + 0 => w.u(7, tail[0]), + 1 => { + w.u(2, 1); + w.u(7, tail[0]); + w.u(4, tail[1]); + w.u(7, tail[2]); + w.u(4, tail[3]); + } + _ => w.u(4, tail[0]), + } + w.u(2, resolution); + if profile != 1 { + w.u(4, 5); + } + w.u(8, 0); + let mut out = w.finish(); + out.extend_from_slice(&[0x5a; 24]); + out +} + +// ---- IEEE 1857.11 ------------------------------------------------------------------- + +fn nic_header(profile: u32, level: u32, model: u32, pset: u32, quality: u32, w: u32, h: u32, depth: u16, fmt: u16, top: u16) -> Vec { + let ids = profile << 21 | level << 15 | model << 12 | pset << 5 | quality; + let mut b = ids.to_be_bytes()[1..].to_vec(); + b.extend_from_slice(&h.to_be_bytes()); + b.extend_from_slice(&w.to_be_bytes()); + let code: u16 = top << 14 | depth << 11 | fmt << 8 | 2 << 4 | 2; + b.extend_from_slice(&code.to_be_bytes()); + // the coding-tool block and substreams, not read by the parse + b.extend_from_slice(&[0x01, 0x00, 0x10, 0x20, 0x30]); + b +} + +// ---- Generation --------------------------------------------------------------------- + +/// Builds the corpus from the external vectors (`ext/...` paths and bytes). Returns the +/// generated files and `vectors.json`, or why the external vectors disagree with their +/// facts. +pub fn generate(ext: &BTreeMap>) -> Result)>, String> { + let mut b = Builder { files: BTreeMap::new(), vectors: Vec::new() }; + for (path, bytes) in ext { + let cap = ext_capability(path); + if cap.is_empty() { + return Err(format!("{path}: not in a directory of a probed capability")); + } + let name = path.trim_start_matches("ext/").replace('/', "_"); + b.add(&name, cap, path, "encoder", bytes.clone()); + } + let get = |p: &str| ext.get(p).cloned().ok_or_else(|| format!("missing {p}")); + + // H.265: the multiview stream read as a single-layer capability, and its base layer as + // a multiview one. + let mv = get("ext/mvhevc/2view_64x64.265")?; + let base = get("ext/hevc/base_64x64.265")?; + b.add("mvhevc_as_hevc", CAP_VIDEO_HEVC, "ext/mvhevc/2view_64x64.265", "encoder", mv.clone()); + b.add("base_as_mvhevc", CAP_VIDEO_MVHEVC, "ext/hevc/base_64x64.265", "encoder", base.clone()); + let mvn = nals(&mv); + let m = |f: &dyn Fn(&mut Vec>)| { + let mut u = mvn.clone(); + f(&mut u); + annex_b(&u) + }; + let origin = "mutation of ext/mvhevc/2view_64x64.265"; + b.put("forbidden_bit", CAP_VIDEO_MVHEVC, "mvhevc", origin, m(&|u| { + let i = u.iter().position(|n| hevc_type(n) == 32).unwrap(); + u[i][0] |= 0x80; + })); + b.put("tid_zero", CAP_VIDEO_MVHEVC, "mvhevc", origin, m(&|u| { + let i = u.iter().position(|n| hevc_type(n) == 33).unwrap(); + u[i][1] &= 0xf8; + })); + b.put("no_sps", CAP_VIDEO_MVHEVC, "mvhevc", origin, m(&|u| u.retain(|n| hevc_type(n) != 33))); + b.put("slice_first", CAP_VIDEO_MVHEVC, "mvhevc", origin, m(&|u| { + let i = u.iter().position(|n| hevc_type(n) < 32).unwrap(); + let s = u.remove(i); + u.insert(0, s); + })); + b.put("vps_reserved", CAP_VIDEO_MVHEVC, "mvhevc", origin, m(&|u| { + // vps_reserved_0xffff_16bits begins at bit 16 of the RBSP: header (2) + 2 bytes. + let i = u.iter().position(|n| hevc_type(n) == 32).unwrap(); + u[i][4] = 0xfe; + })); + b.put("sps_truncated", CAP_VIDEO_MVHEVC, "mvhevc", origin, m(&|u| { + let i = u.iter().position(|n| hevc_type(n) == 33).unwrap(); + u[i].truncate(8); + })); + let mut no_start = mv.clone(); + no_start.drain(..4); + b.put("no_start_code", CAP_VIDEO_MVHEVC, "mvhevc", origin, no_start); + + // H.266. + let vv = get("ext/vvc/main10_64x64.266")?; + let vn = nals(&vv); + let v = |f: &dyn Fn(&mut Vec>)| { + let mut u = vn.clone(); + f(&mut u); + annex_b(&u) + }; + let origin = "mutation of ext/vvc/main10_64x64.266"; + b.put("reserved_zero_bit", CAP_VIDEO_VVC, "vvc", origin, v(&|u| { + let i = u.iter().position(|n| vvc_type(n) == 15).unwrap(); + u[i][0] |= 0x40; + })); + b.put("no_sps", CAP_VIDEO_VVC, "vvc", origin, v(&|u| u.retain(|n| vvc_type(n) != 15))); + b.put("slice_first", CAP_VIDEO_VVC, "vvc", origin, v(&|u| { + let i = u.iter().position(|n| vvc_type(n) <= 11).unwrap(); + let s = u.remove(i); + u.insert(0, s); + })); + b.put("ctu_size_reserved", CAP_VIDEO_VVC, "vvc", origin, v(&|u| { + // sps_log2_ctu_size_minus5: the RBSP's bits 13 and 14. + let i = u.iter().position(|n| vvc_type(n) == 15).unwrap(); + u[i][3] |= 0x06; + })); + b.put("tid_zero", CAP_VIDEO_VVC, "vvc", origin, v(&|u| { + let i = u.iter().position(|n| vvc_type(n) == 15).unwrap(); + u[i][1] &= 0xf8; + })); + + // JPEG XS. + let jx = get("ext/jxs/main422_10_64x64.jxs")?; + let origin = "mutation of ext/jxs/main422_10_64x64.jxs"; + let lcap = u16::from_be_bytes([jx[4], jx[5]]) as usize; + let pih = 4 + lcap; + let jm = |f: &dyn Fn(&mut Vec)| { + let mut x = jx.clone(); + f(&mut x); + x + }; + b.put("no_soc", CAP_IMAGE_JXS, "jxs", origin, jx[2..].to_vec()); + b.put("cap_missing", CAP_IMAGE_JXS, "jxs", origin, jm(&|x| x[3] = 0x15)); + b.put("lpih_25", CAP_IMAGE_JXS, "jxs", origin, jm(&|x| x[pih + 3] = 25)); + b.put("ng_3", CAP_IMAGE_JXS, "jxs", origin, jm(&|x| x[pih + 21] = 3)); + b.put("cpih_2", CAP_IMAGE_JXS, "jxs", origin, jm(&|x| x[pih + 25] = (x[pih + 25] & 0xf0) | 2)); + b.put("lcod_off_by_one", CAP_IMAGE_JXS, "jxs", origin, jm(&|x| x[pih + 7] = x[pih + 7].wrapping_add(1))); + b.put("no_eoc", CAP_IMAGE_JXS, "jxs", origin, jm(&|x| { + let n = x.len(); + x[n - 1] = 0x00; + })); + b.put("unknown_marker", CAP_IMAGE_JXS, "jxs", origin, jm(&|x| { + // A segment of an unknown marker before the first slice header. + let at = pih + 28; + x.splice(at..at, [0xff, 0x30, 0x00, 0x02]); + })); + b.put("cdt_length", CAP_IMAGE_JXS, "jxs", origin, jm(&|x| { + let at = pih + 28; + assert_eq!(&x[at..at + 2], &[0xff, 0x13]); + x[at + 3] += 2; + })); + b.put("truncated", CAP_IMAGE_JXS, "jxs", origin, jx[..pih + 20].to_vec()); + let jv = get("ext/jxs-video/main420_8_64x48_3f.jxsv")?; + let origin = "mutation of ext/jxs-video/main420_8_64x48_3f.jxsv"; + let vm = |f: &dyn Fn(&mut Vec)| { + let mut x = jv.clone(); + f(&mut x); + x + }; + b.put("zero_frames", CAP_VIDEO_JXS, "jxs-video", origin, vm(&|x| x[..4].copy_from_slice(&0u32.to_le_bytes()))); + b.put("width_too_small", CAP_VIDEO_JXS, "jxs-video", origin, vm(&|x| x[8..12].copy_from_slice(&32u32.to_le_bytes()))); + b.put("trailing_byte", CAP_VIDEO_JXS, "jxs-video", origin, vm(&|x| x.push(0))); + b.put("frame_cut", CAP_VIDEO_JXS, "jxs-video", origin, jv[..jv.len() - 7].to_vec()); + b.put("four_frames_stated", CAP_VIDEO_JXS, "jxs-video", origin, vm(&|x| x[..4].copy_from_slice(&4u32.to_le_bytes()))); + + // AVS3 video: mutations of the encoder's stream, and generated sequence headers. + let av = get("ext/avs3/main_128x64.avs3")?; + let origin = "mutation of ext/avs3/main_128x64.avs3"; + let units = avs3::units(&av).map_err(|e| format!("avs3: {e}"))?; + let rebuild = |us: &[(u8, Vec)]| -> Vec { us.iter().flat_map(|(c, p)| avs3_unit(*c, p)).collect() }; + let owned: Vec<(u8, Vec)> = units.iter().map(|(c, p)| (*c, p.to_vec())).collect(); + let am = |f: &dyn Fn(&mut Vec<(u8, Vec)>)| { + let mut u = owned.clone(); + f(&mut u); + rebuild(&u) + }; + b.put("no_sequence_header", CAP_VIDEO_AVS3, "avs3", origin, am(&|u| u.retain(|(c, _)| *c != avs3::SEQUENCE_HEADER))); + b.put("picture_first", CAP_VIDEO_AVS3, "avs3", origin, am(&|u| { + let i = u.iter().position(|(c, _)| *c == avs3::INTRA_PICTURE).unwrap(); + let p = u.remove(i); + u.insert(0, p); + })); + b.put("marker_cleared", CAP_VIDEO_AVS3, "avs3", origin, am(&|u| { + // the marker bit before horizontal_size: bit 20 of the sequence header's payload + u[0].1[2] &= !0x08; + })); + b.put("library_stream", CAP_VIDEO_AVS3, "avs3", origin, am(&|u| u[0].1[2] |= 0x20)); + b.put("truncated_header", CAP_VIDEO_AVS3, "avs3", origin, am(&|u| u[0].1.truncate(6))); + b.put("no_start_code", CAP_VIDEO_AVS3, "avs3", origin, av[3..].to_vec()); + let pic = avs3_unit(avs3::INTRA_PICTURE, &[0x00, 0x00, 0x80]); + let mut hdr = avs3_unit(avs3::SEQUENCE_HEADER, &emulate(&avs3_sequence(0x22, 1920, 1080, 6, 0, false))); + hdr.extend_from_slice(&pic); + b.put("main10_1080p_emulated", CAP_VIDEO_AVS3, "avs3", "generator", hdr); + let mut hdr = avs3_unit(avs3::SEQUENCE_HEADER, &emulate(&avs3_sequence(0x20, 720, 576, 0, 5000, false))); + hdr.extend_from_slice(&pic); + b.put("frame_rate_zero", CAP_VIDEO_AVS3, "avs3", "generator", hdr); + + // AVS3 audio. + let d = "avs3-audio"; + b.put("channels_5_1", CAP_AUDIO_AVS3, d, "generator", aatf(0, 2, 2, 1, &[2])); + b.put("bed_and_objects", CAP_AUDIO_AVS3, d, "generator", aatf(1, 2, 2, 2, &[7, 3, 3, 2])); + b.put("ambisonic_order_2", CAP_AUDIO_AVS3, d, "generator", aatf(2, 3, 2, 1, &[1])); + b.put("no_sync", CAP_AUDIO_AVS3, d, "generator", { let mut x = aatf(0, 2, 2, 1, &[1]); x[0] = 0xfe; x }); + b.put("lossless", CAP_AUDIO_AVS3, d, "generator", aatf(0, 2, 1, 1, &[1])); + b.put("profile_3", CAP_AUDIO_AVS3, d, "generator", aatf(3, 2, 2, 1, &[1])); + b.put("rate_index_9", CAP_AUDIO_AVS3, d, "generator", aatf(0, 9, 2, 1, &[1])); + b.put("resolution_3", CAP_AUDIO_AVS3, d, "generator", aatf(0, 2, 2, 3, &[1])); + b.put("truncated", CAP_AUDIO_AVS3, d, "generator", aatf(0, 2, 2, 1, &[1])[..3].to_vec()); + + // LCEVC. + let d = "lcevc"; + let seq = seq_config(1, 2, 1, None, 0); + b.put("main_1080p", CAP_VIDEO_LCEVC, d, "generator", lcevc_stream(&seq, &global_config(GLOBAL), 3)); + b.put("window_custom_tiles", CAP_VIDEO_LCEVC, d, "generator", lcevc_stream( + &seq_config(0, 4, 1, Some([1, 2, 300, 4]), 0), + &global_config(Global { resolution_type: 63, custom: Some((360, 100)), tiles: 3, upsample: 4, depth: (1, 1), temporal: false, chroma: 1 }), + 2, + )); + let main = lcevc_stream(&seq, &global_config(GLOBAL), 2); + let mut no_start = main.clone(); + no_start.drain(..4); + b.put("no_start_code", CAP_VIDEO_LCEVC, d, "generator", no_start); + b.put("non_idr_first", CAP_VIDEO_LCEVC, d, "generator", annex_b(&[lcevc_nal(false, &[lcevc_block(0, &seq), lcevc_block(1, &global_config(GLOBAL))])])); + b.put("reserved_flag", CAP_VIDEO_LCEVC, d, "generator", { let mut x = main.clone(); x[5] = 0xfe; x }); + b.put("nal_type_27", CAP_VIDEO_LCEVC, d, "generator", { let mut x = main.clone(); x[4] = 0x41 | (27 << 1); x }); + b.put("stop_byte_0x81", CAP_VIDEO_LCEVC, d, "generator", { + let mut n = lcevc_nal(true, &[lcevc_block(0, &seq), lcevc_block(1, &global_config(GLOBAL))]); + *n.last_mut().unwrap() = 0x81; + annex_b(&[n]) + }); + b.put("block_overrun", CAP_VIDEO_LCEVC, d, "generator", annex_b(&[lcevc_nal(true, &[vec![(5 << 5) | 3, 1, 2]])])); + b.put("size_type_6", CAP_VIDEO_LCEVC, d, "generator", annex_b(&[lcevc_nal(true, &[vec![(6 << 5) | 3, 1, 2]])])); + b.put("no_global_config", CAP_VIDEO_LCEVC, d, "generator", annex_b(&[lcevc_nal(true, &[lcevc_block(0, &seq), lcevc_block(3, &[0x10, 0x20])])])); + b.put("picture_before_global", CAP_VIDEO_LCEVC, d, "generator", annex_b(&[lcevc_nal(true, &[lcevc_block(0, &seq), lcevc_block(2, &[0x80, 0x12, 0x00]), lcevc_block(1, &global_config(GLOBAL))])])); + b.put("resolution_type_0", CAP_VIDEO_LCEVC, d, "generator", lcevc_stream(&seq, &global_config(Global { resolution_type: 0, ..GLOBAL }), 1)); + b.put("resolution_type_55", CAP_VIDEO_LCEVC, d, "generator", lcevc_stream(&seq, &global_config(Global { resolution_type: 55, ..GLOBAL }), 1)); + b.put("upsample_type_6", CAP_VIDEO_LCEVC, d, "generator", lcevc_stream(&seq, &global_config(Global { upsample: 6, ..GLOBAL }), 1)); + b.put("sequence_reserved_bits", CAP_VIDEO_LCEVC, d, "generator", lcevc_stream(&seq_config(1, 2, 1, None, 4), &global_config(GLOBAL), 1)); + b.put("extended_profile", CAP_VIDEO_LCEVC, d, "generator", lcevc_stream(&[seq_config(15, 2, 1, None, 0), vec![0x24]].concat(), &global_config(GLOBAL), 1)); + + // MPEG-H 3D Audio. + let d = "mhas"; + let cfg = mhas_config(0x0d, 3, None, 0, 6); + b.put("lc3_48k_5_1", CAP_AUDIO_MPEGH, d, "generator", mhas_stream(&cfg, 3, true)); + b.put("baseline2_escaped_rate_speakers", CAP_AUDIO_MPEGH, d, "generator", { + let mut s = mhas_stream(&mhas_config(0x11, 0x1f, Some(47_952), 1, 3), 1, false); + // a packet of an escaped type (PCMDATA, 21) with an escaped length, skipped + s.extend(mhas_packet(21, 2, &vec![0x33; 2100])); + s + }); + b.put("frame_first", CAP_AUDIO_MPEGH, d, "generator", [mhas_packet(mhas::PACTYP_MPEGH3DAFRAME, 1, &[1, 2]), mhas_stream(&cfg, 1, false)].concat()); + b.put("frame_before_config", CAP_AUDIO_MPEGH, d, "generator", [mhas_packet(mhas::PACTYP_SYNC, 0, &[mhas::SYNC_BYTE]), mhas_packet(mhas::PACTYP_MPEGH3DAFRAME, 1, &[1, 2]), mhas_packet(mhas::PACTYP_MPEGH3DACFG, 1, &cfg)].concat()); + b.put("sync_byte", CAP_AUDIO_MPEGH, d, "generator", [mhas_packet(mhas::PACTYP_SYNC, 0, &[0xa4]), mhas_stream(&cfg, 1, false)].concat()); + b.put("packet_overrun", CAP_AUDIO_MPEGH, d, "generator", { let mut s = mhas_stream(&cfg, 2, true); s.truncate(s.len() - 2); s }); + b.put("rate_index_13", CAP_AUDIO_MPEGH, d, "generator", mhas_stream(&mhas_config(0x0d, 13, None, 0, 6), 1, true)); + b.put("no_config", CAP_AUDIO_MPEGH, d, "generator", [mhas_packet(mhas::PACTYP_SYNC, 0, &[mhas::SYNC_BYTE]), mhas_packet(mhas::PACTYP_FILLDATA, 0, &[0; 4])].concat()); + + // IEEE 1857.11 (a candidate: its header as the reference software writes it). + let d = "ieee1857_11"; + b.put("base_8bit", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header(0, 1, 0, 7, 3, 768, 512, 1, 0, 0)); + b.put("main_10bit", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header(1, 2, 1, 33, 5, 1920, 1080, 2, 1, 0)); + b.put("profile_3", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header(3, 1, 0, 0, 0, 64, 64, 1, 0, 0)); + b.put("depth_code_0", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header(0, 1, 0, 0, 0, 64, 64, 0, 0, 0)); + b.put("top_bits", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header(0, 1, 0, 0, 0, 64, 64, 1, 0, 1)); + b.put("truncated", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header(0, 1, 0, 0, 0, 64, 64, 1, 0, 0)[..12].to_vec()); + + // The projection descriptor. + let d = "projection"; + let base = Projection { projection: 1, stereo: 0, first_view: 0, azimuth_range: (360 * DEG) as u32, elevation_range: (180 * DEG) as u32, centre_azimuth: 0, centre_elevation: 0, centre_tilt: 0, baseline_um: 0, left_layer: 0, right_layer: 0 }; + let half = Projection { projection: 2, stereo: 1, azimuth_range: (180 * DEG) as u32, baseline_um: 63_000, ..base }; + let fish = Projection { projection: 3, stereo: 2, first_view: 1, azimuth_range: (190 * DEG) as u32, elevation_range: (190 * DEG) as u32, baseline_um: 60_000, ..base }; + let layers = Projection { projection: 0, stereo: 3, azimuth_range: (90 * DEG) as u32, elevation_range: (59 * DEG) as u32, baseline_um: 64_000, left_layer: 0, right_layer: 1, ..base }; + let flat = Projection { projection: 0, azimuth_range: 0, elevation_range: 0, ..base }; + for (n, p) in [("erp_mono", base), ("half_erp_side_by_side", half), ("fisheye_top_bottom", fish), ("rectilinear_layers", layers), ("rectilinear_unstated", flat)] { + b.put(n, CAP_META_PROJECTION, d, "generator", p.to_bytes()); + } + let pm = |p: Projection, f: &dyn Fn(&mut Vec)| { + let mut x = p.to_bytes(); + f(&mut x); + x + }; + b.put("magic", CAP_META_PROJECTION, d, "generator", pm(base, &|x| x[3] = b'2')); + b.put("length_39", CAP_META_PROJECTION, d, "generator", pm(base, &|x| { x.pop(); })); + b.put("version_2", CAP_META_PROJECTION, d, "generator", pm(base, &|x| x[4] = 2)); + b.put("projection_4", CAP_META_PROJECTION, d, "generator", pm(base, &|x| x[5] = 4)); + b.put("reserved_byte", CAP_META_PROJECTION, d, "generator", pm(base, &|x| x[39] = 1)); + b.put("erp_359", CAP_META_PROJECTION, d, "generator", Projection { azimuth_range: (359 * DEG) as u32, ..base }.to_bytes()); + b.put("mono_baseline", CAP_META_PROJECTION, d, "generator", Projection { baseline_um: 1, ..base }.to_bytes()); + b.put("layers_equal", CAP_META_PROJECTION, d, "generator", Projection { right_layer: 0, ..layers }.to_bytes()); + b.put("centre_180", CAP_META_PROJECTION, d, "generator", Projection { centre_azimuth: (180 * DEG) as i32, ..base }.to_bytes()); + + // A capability with no probe. + b.put("png_has_no_probe", CAP_IMAGE_PNG, "other", "generator", b"\x89PNG\r\n\x1a\n".to_vec()); + + // Bindings of a descriptor to the rendition it accompanies (projection.md §5). + let mut bindings = Vec::new(); + let layers2 = Projection { right_layer: 2, ..layers }; + b.put("rectilinear_layers_0_2", CAP_META_PROJECTION, d, "generator", layers2.to_bytes()); + for (n, desc, p, cap, file) in [ + ("layers_over_mvhevc", "gen/projection/rectilinear_layers.bin", layers, CAP_VIDEO_MVHEVC, "ext/mvhevc/2view_64x64.265"), + ("layer_2_over_mvhevc", "gen/projection/rectilinear_layers_0_2.bin", layers2, CAP_VIDEO_MVHEVC, "ext/mvhevc/2view_64x64.265"), + ("layers_over_hevc", "gen/projection/rectilinear_layers.bin", layers, CAP_VIDEO_HEVC, "ext/hevc/base_64x64.265"), + ("side_by_side_over_hevc", "gen/projection/half_erp_side_by_side.bin", half, CAP_VIDEO_HEVC, "ext/hevc/base_64x64.265"), + ] { + let base_bytes = ext.get(file).ok_or_else(|| format!("missing {file}"))?; + let params = probe(cap, base_bytes).map_err(|e| format!("{file}: {e}"))?; + let outcome = match p.check_binding(cap, ¶ms) { + Ok(()) => json!("accept"), + Err(r) => json!({ "refuse": r.code() }), + }; + bindings.push(json!({ "name": n, "descriptor": desc, "base_capability": cap, "base": file, "outcome": outcome })); + } + + // The facts. + for &(file, ptr, want) in FACTS { + let v = b.vectors.iter().find(|v| v["file"] == file && v["capability"] == ext_capability(file)).ok_or_else(|| format!("no vector for {file}"))?; + let got = v["outcome"]["accept"].pointer(ptr).cloned().unwrap_or(Value::Null); + let want: Value = serde_json::from_str(want).map_err(|e| e.to_string())?; + if got != want { + return Err(format!("{file}{ptr}: the probe reads {got}, the encoder was asked for {want}")); + } + } + // Every refusal code is given by some vector or binding. + for r in Refusal::ALL { + let given = b.vectors.iter().any(|v| v["outcome"]["refuse"] == r.code()) + || bindings.iter().any(|v| v["outcome"]["refuse"] == r.code()); + if !given { + return Err(format!("no vector is refused with {}", r.code())); + } + } + let doc = json!({ + "probes": PROBES.iter().map(|(c, basis)| json!({ "capability": c, "basis": basis.label() })).collect::>(), + "refusals": Refusal::ALL.iter().map(|r| r.code()).collect::>(), + "facts": FACTS.iter().map(|(f, p, v)| json!({ "file": f, "pointer": p, "value": serde_json::from_str::(v).unwrap_or(Value::Null) })).collect::>(), + "vectors": b.vectors, + "bindings": bindings, + "fetched": fetched_expectations(), + }); + let mut out: Vec<(String, Vec)> = b.files.into_iter().collect(); + let mut text = serde_json::to_string_pretty(&doc).map_err(|e| e.to_string())?; + text.push('\n'); + out.push(("vectors.json".to_string(), text.into_bytes())); + Ok(out) +} + +/// The LCEVC streams `fetch.sh` fetches by digest (format-probes §13.3), each with what its +/// encoder was asked for. The bytes are not committed: the files' licence notice names +/// their owner. +pub fn fetched_expectations() -> Value { + json!([ + { "file": "parse_std.bin", "sha256": "0ad059dd167093ef773aa9be932afdf0cd2108656daaed18bfc7ee8041c4ee05", + "expect": { "/conformance_window": [1, 2, 3, 4], "/tile_dimensions_type": 3, "/nal_units": [1, 2] } }, + { "file": "parse_gops.bin", "sha256": "20b7d666216b20419b6e4d80b2d9b68b8c9b4a66f9ba01849895e00d7c9d2583", + "expect": { "/nal_units": [3, 2] } }, + { "file": "parse_hdr.bin", "sha256": "1492419eb19f34dd1e4dd3f50b3525a32f2bb34ad48fdeea8e4e9fc1ab54c149", + "expect": { "/nal_units": [1, 0], "/base_depth": 10 } }, + { "file": "decode_temp_on.bin", "sha256": "156568e8c5cbb0307e5c1b6dde25a8932315abfffe063140dfa6366edea4c9e4", + "expect": { "/temporal_enabled": true } }, + { "file": "decode_temp_off.bin", "sha256": "a486c0a40abdfdde11d95b51491a3b78a273a48b75548e7a86e32de361df781c", + "expect": { "/temporal_enabled": false } }, + ]) +} + +/// The byte stream inside one of those files: the files are a container of length-prefixed +/// blocks (an 8-byte magic `lcevcbin`, a `u32` version, then per block a `u16` type, a +/// `u32` size and that many bytes, of which the first 16 are two indices), and each block's +/// remaining bytes are NAL units with start codes. +pub fn unwrap_fetched(b: &[u8]) -> Result, String> { + if b.len() < 12 || &b[..8] != b"lcevcbin" { + return Err("not the container".into()); + } + let mut at = 12; + let mut out = Vec::new(); + while at < b.len() { + let h = b.get(at..at + 6).ok_or("a block header cut short")?; + let size = u32::from_be_bytes([h[2], h[3], h[4], h[5]]) as usize; + let body = b.get(at + 6..at + 6 + size).ok_or("a block cut short")?; + out.extend_from_slice(body.get(16..).ok_or("a block shorter than its indices")?); + at += 6 + size; + } + Ok(out) +} + +/// Checks a corpus: every vector's stated outcome, every binding's, every fact, and that +/// the generated files regenerate. `files` maps paths relative to the corpus directory to +/// bytes. Returns the failures. +pub fn verify(files: &BTreeMap>) -> Vec { + let mut fails = Vec::new(); + let Some(doc) = files.get("vectors.json").and_then(|t| serde_json::from_slice::(t).ok()) else { + return vec!["vectors.json does not read".into()]; + }; + for v in doc["vectors"].as_array().into_iter().flatten() { + let name = v["name"].as_str().unwrap_or("?"); + let (Some(cap), Some(file)) = (v["capability"].as_str(), v["file"].as_str()) else { + fails.push(format!("{name}: no capability or file")); + continue; + }; + let Some(bytes) = files.get(file) else { + fails.push(format!("{name}: {file} missing")); + continue; + }; + if v["sha256"].as_str() != Some(sha256(bytes).as_str()) { + fails.push(format!("{name}: {file}'s digest differs")); + } + let got = match probe(cap, bytes) { + Ok(p) => json!({ "accept": p }), + Err(r) => json!({ "refuse": r.code() }), + }; + if got != v["outcome"] { + fails.push(format!("{name}: stated {} , probe gives {}", v["outcome"], got)); + } + } + for bnd in doc["bindings"].as_array().into_iter().flatten() { + let name = bnd["name"].as_str().unwrap_or("?"); + let r = (|| -> Result { + let d = files.get(bnd["descriptor"].as_str().unwrap_or("")).ok_or("descriptor missing")?; + let cap = bnd["base_capability"].as_str().unwrap_or(""); + let base = files.get(bnd["base"].as_str().unwrap_or("")).ok_or("base missing")?; + let p = Projection::read(d).map_err(|e| e.to_string())?; + let params = probe(cap, base).map_err(|e| e.to_string())?; + Ok(match p.check_binding(cap, ¶ms) { + Ok(()) => json!("accept"), + Err(r) => json!({ "refuse": r.code() }), + }) + })(); + match r { + Ok(got) if got == bnd["outcome"] => {} + Ok(got) => fails.push(format!("binding {name}: stated {}, gives {got}", bnd["outcome"])), + Err(e) => fails.push(format!("binding {name}: {e}")), + } + } + let ext: BTreeMap> = files.iter().filter(|(k, _)| k.starts_with("ext/")).map(|(k, v)| (k.clone(), v.clone())).collect(); + match generate(&ext) { + Err(e) => fails.push(format!("the corpus does not generate: {e}")), + Ok(generated) => { + for (k, v) in &generated { + if files.get(k) != Some(v) { + fails.push(format!("{k} does not regenerate byte for byte")); + } + } + for k in files.keys().filter(|k| k.starts_with("gen/")) { + if !generated.iter().any(|(g, _)| g == k) { + fails.push(format!("{k} is not a generated file")); + } + } + } + } + fails +} + +/// Checks the fetched LCEVC streams in `dir` (fetch.sh's output) against +/// [`fetched_expectations`]. Every file must be present: a missing one is a failure. +pub fn verify_fetched(files: &BTreeMap>) -> Vec { + let mut fails = Vec::new(); + for f in fetched_expectations().as_array().into_iter().flatten() { + let name = f["file"].as_str().unwrap_or("?"); + let Some(b) = files.get(name) else { + fails.push(format!("{name}: not fetched")); + continue; + }; + if f["sha256"].as_str() != Some(sha256(b).as_str()) { + fails.push(format!("{name}: digest differs")); + continue; + } + let stream = match unwrap_fetched(b) { + Ok(s) => s, + Err(e) => { + fails.push(format!("{name}: {e}")); + continue; + } + }; + match probe(CAP_VIDEO_LCEVC, &stream) { + Err(r) => fails.push(format!("{name}: refused ({r})")), + Ok(p) => { + for (ptr, want) in f["expect"].as_object().unwrap_or(&Map::new()) { + if p.pointer(ptr) != Some(want) { + fails.push(format!("{name}{ptr}: {:?}, expected {want}", p.pointer(ptr))); + } + } + } + } + } + fails +} diff --git a/wai/wai-rs/src/format_probe/hevc.rs b/wai/wai-rs/src/format_probe/hevc.rs new file mode 100644 index 000000000..8474415ea --- /dev/null +++ b/wai/wai-rs/src/format_probe/hevc.rs @@ -0,0 +1,520 @@ +//! The H.265 probe (`wai.video.hevc`, `wai.video.mvhevc`; format-probes §4): the Annex B +//! byte stream's NAL units, the video parameter set with its multilayer extension +//! (Annex F), and the base layer's sequence parameter set. Syntax from ITU-T H.265 (V11, +//! 01/2026) §7.3.1.2, §7.3.2.1, §7.3.2.2.1, §7.3.3, §E.2.2–§E.2.3, §F.7.3.2.1 and +//! §F.7.3.2.1.1; derivations from §F.7.4.3.1.1 (F-3)–(F-6). + +use super::bits::{annex_b_units, unescape, BitReader}; +use super::Refusal; +use serde_json::{json, Value}; + +pub const NAL_VPS: u64 = 32; +pub const NAL_SPS: u64 = 33; +pub const NAL_PPS: u64 = 34; + +/// One `profile_tier_level( )`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Ptl { + /// `general_profile_idc`, or `None` when the structure carries no profile. + pub profile_idc: Option, + pub tier: Option, + /// `general_profile_compatibility_flag[ j ]`, bit `j` = flag `j`. + pub compat: u32, + pub level_idc: u64, +} + +impl Ptl { + /// Whether the structure indicates profile `idc`, by its idc or its compatibility flag. + pub fn indicates(&self, idc: u64) -> bool { + self.profile_idc == Some(idc) || (idc < 32 && self.compat & (1 << idc) != 0) + } +} + +/// `profile_tier_level( profilePresentFlag, maxNumSubLayersMinus1 )` (§7.3.3). The general +/// profile part is 88 bits whatever its branches (the syntax's own note), so it is read as +/// fields and a fixed skip. +pub fn ptl(r: &mut BitReader, profile_present: bool, max_sub_layers_minus1: u64) -> Result { + let (mut profile_idc, mut tier, mut compat) = (None, None, 0u32); + if profile_present { + let _space = r.u(2)?; + tier = Some(r.u(1)?); + profile_idc = Some(r.u(5)?); + compat = r.u(32)? as u32; + // progressive, interlaced, non-packed, frame-only (4), then 43 constraint bits, + // then general_inbld_flag or general_reserved_zero_bit. + r.skip(4 + 43 + 1)?; + } + let level_idc = r.u(8)?; + let n = max_sub_layers_minus1 as usize; + let mut present = Vec::with_capacity(n); + for _ in 0..n { + present.push((r.flag()?, r.flag()?)); + } + if n > 0 { + r.skip((8 - n) * 2)?; + } + for &(profile, level) in &present { + if profile { + r.skip(88)?; + } + if level { + r.skip(8)?; + } + } + Ok(Ptl { profile_idc, tier, compat, level_idc }) +} + +/// The common information of the previous `hrd_parameters( )` a VPS carries, which one +/// with `cprms_present_flag` equal to 0 reuses (§E.2.2). +#[derive(Default, Clone, Copy)] +struct HrdCommon { + nal: bool, + vcl: bool, + sub_pic: bool, +} + +/// `hrd_parameters( commonInfPresentFlag, maxNumSubLayersMinus1 )` (§E.2.2), read and +/// discarded. +fn hrd(r: &mut BitReader, common: bool, prev: HrdCommon, max_sub: u64) -> Result { + let mut c = prev; + if common { + c.nal = r.flag()?; + c.vcl = r.flag()?; + c.sub_pic = false; + if c.nal || c.vcl { + c.sub_pic = r.flag()?; + if c.sub_pic { + r.skip(8 + 5 + 1 + 5)?; + } + r.skip(4 + 4)?; + if c.sub_pic { + r.skip(4)?; + } + r.skip(5 + 5 + 5)?; + } + } + for _ in 0..=max_sub { + let general = r.flag()?; + let within = if general { true } else { r.flag()? }; + let mut low_delay = false; + if within { + r.ue()?; + } else { + low_delay = r.flag()?; + } + let cpb_cnt = if low_delay { 1 } else { r.ue()? + 1 }; + if cpb_cnt > 32 { + return Err(Refusal::ReservedValue); + } + for present in [c.nal, c.vcl] { + if present { + for _ in 0..cpb_cnt { + r.ue()?; + r.ue()?; + if c.sub_pic { + r.ue()?; + r.ue()?; + } + r.skip(1)?; + } + } + } + } + Ok(c) +} + +/// What the probe reads from a video parameter set. +#[derive(Debug, Clone, Default)] +pub struct Vps { + pub base_layer_internal: bool, + pub max_layers_minus1: u64, + pub max_sub_layers_minus1: u64, + pub base_ptl: Option, + /// Present when `vps_extension_flag` is 1 and `vps_max_layers_minus1` > 0. + pub ext: Option, +} + +#[derive(Debug, Clone, Default)] +pub struct VpsExt { + pub scalability_mask: u16, + /// `layer_id_in_nuh[ i ]`, for i from 0 to MaxLayersMinus1. + pub layer_ids: Vec, + /// `ViewOrderIdx[ layer_id_in_nuh[ i ] ]`. + pub view_order: Vec, + /// `AuxId[ layer_id_in_nuh[ i ] ]`. + pub aux: Vec, + pub num_views: u64, + /// `view_id_val[ i ]`, for each view order index. + pub view_ids: Vec, + /// `direct_dependency_flag[ i ][ j ]`. + pub deps: Vec>, + /// The profile_tier_level( ) structures of the extension, in order (the first, when + /// present, carries no profile). + pub ptls: Vec, +} + +/// `video_parameter_set_rbsp( )` (§F.7.3.2.1) through `vps_extension( )` (§F.7.3.2.1.1) +/// up to and including its profile_tier_level( ) structures. +pub fn vps(rbsp: &[u8]) -> Result { + let mut r = BitReader::new(rbsp); + let _id = r.u(4)?; + let base_layer_internal = r.flag()?; + let _base_layer_available = r.flag()?; + let max_layers_minus1 = r.u(6)?; + let max_sub = r.u(3)?; + if max_sub > 6 { + return Err(Refusal::ReservedValue); + } + let _nesting = r.flag()?; + if r.u(16)? != 0xffff { + return Err(Refusal::ReservedValue); + } + let base_ptl = ptl(&mut r, true, max_sub)?; + let ordering = r.flag()?; + for _ in (if ordering { 0 } else { max_sub })..=max_sub { + r.ue()?; + r.ue()?; + r.ue()?; + } + let max_layer_id = r.u(6)?; + let num_layer_sets_minus1 = r.ue()?; + if num_layer_sets_minus1 > 1023 { + return Err(Refusal::ReservedValue); + } + r.skip(num_layer_sets_minus1 as usize * (max_layer_id as usize + 1))?; + if r.flag()? { + r.skip(64)?; + if r.flag()? { + r.ue()?; + } + let num_hrd = r.ue()?; + if num_hrd > num_layer_sets_minus1 + 1 { + return Err(Refusal::ReservedValue); + } + let mut prev = HrdCommon::default(); + for i in 0..num_hrd { + r.ue()?; + let cprms = if i > 0 { r.flag()? } else { true }; + prev = hrd(&mut r, cprms, prev, max_sub)?; + } + } + let mut out = Vps { base_layer_internal, max_layers_minus1, max_sub_layers_minus1: max_sub, base_ptl: Some(base_ptl), ext: None }; + if !r.flag()? { + return Ok(out); + } + r.align(Some(1))?; + if max_layers_minus1 == 0 { + // An extension with one layer describes no further layer: nothing the probe reads. + return Ok(out); + } + out.ext = Some(vps_extension(&mut r, base_layer_internal, max_layers_minus1, max_sub, base_ptl)?); + Ok(out) +} + +fn ceil_log2(n: u64) -> u32 { + if n <= 1 { 0 } else { 64 - (n - 1).leading_zeros() } +} + +/// A structure with no profile takes the previous structure's profile, tier and +/// compatibility flags (§F.7.4.4: VpsProfileTierLevel[ k ] from VpsProfileTierLevel[ k − 1 ]). +fn inherit(mut p: Ptl, prev: &Ptl) -> Ptl { + if p.profile_idc.is_none() { + p.profile_idc = prev.profile_idc; + p.tier = prev.tier; + p.compat = prev.compat; + } + p +} + +fn vps_extension(r: &mut BitReader, base_internal: bool, max_layers_minus1: u64, max_sub: u64, base_ptl: Ptl) -> Result { + let mut ptls = Vec::new(); + if base_internal { + ptls.push(inherit(ptl(r, false, max_sub)?, &base_ptl)); + } + let splitting = r.flag()?; + let mut mask = 0u16; + for i in 0..16 { + if r.flag()? { + mask |= 1 << i; + } + } + let types = mask.count_ones() as usize; + let mut dim_len = vec![0u32; types]; + for j in 0..types.saturating_sub(usize::from(splitting)) { + dim_len[j] = r.u(3)? as u32 + 1; + } + let max_l = max_layers_minus1.min(62) as usize; + if splitting && types > 0 { + // (F-2): the last length makes the offsets reach 6 bits. + let used: u32 = dim_len[..types - 1].iter().sum(); + if used >= 6 { + return Err(Refusal::ReservedValue); + } + dim_len[types - 1] = 6 - used; + } + let id_present = r.flag()?; + let mut layer_ids = vec![0u64; max_l + 1]; + let mut dims = vec![vec![0u64; types]; max_l + 1]; + for i in 1..=max_l { + layer_ids[i] = if id_present { r.u(6)? } else { i as u64 }; + if layer_ids[i] <= layer_ids[i - 1] { + return Err(Refusal::ReservedValue); + } + if !splitting { + for j in 0..types { + dims[i][j] = r.u(dim_len[j])?; + } + } + } + if splitting { + for i in 0..=max_l { + let mut off = 0u32; + for j in 0..types { + dims[i][j] = (layer_ids[i] >> off) & ((1 << dim_len[j]) - 1); + off += dim_len[j]; + } + } + } + // (F-3): ScalabilityId, ViewOrderIdx, AuxId and NumViews. + let mut view_order = vec![0u64; max_l + 1]; + let mut aux = vec![0u64; max_l + 1]; + let mut num_views = 1u64; + for i in 0..=max_l { + let mut j = 0; + let mut sid = [0u64; 16]; + for (sm, s) in sid.iter_mut().enumerate() { + if mask & (1 << sm) != 0 { + *s = dims[i][j]; + j += 1; + } + } + view_order[i] = sid[1]; + aux[i] = sid[3]; + if i > 0 && !(0..i).any(|k| view_order[k] == view_order[i]) { + num_views += 1; + } + } + let view_id_len = r.u(4)? as u32; + let mut view_ids = Vec::new(); + if view_id_len > 0 { + for _ in 0..num_views { + view_ids.push(r.u(view_id_len)?); + } + } else { + view_ids = vec![0; num_views as usize]; + } + let mut deps = vec![vec![false; max_l + 1]; max_l + 1]; + for (i, row) in deps.iter_mut().enumerate().skip(1) { + for cell in row.iter_mut().take(i) { + *cell = r.flag()?; + } + } + // (F-4)–(F-6): the independent layers and their tree partitions. + let n = max_l + 1; + let mut dep = deps.clone(); + for i in 0..n { + for j in 0..n { + for k in 0..i { + if deps[i][k] && dep[k][j] { + dep[i][j] = true; + } + } + } + } + let mut tree_sizes = Vec::new(); + let mut in_list = [false; 64]; + for i in 0..n { + if (0..n).any(|j| deps[i][j]) { + continue; + } + let mut h = 1u64; + for j in 0..n { + if dep[j][i] { + let lid = layer_ids[j] as usize; + if !in_list[lid] { + in_list[lid] = true; + h += 1; + } + } + } + tree_sizes.push(h); + } + let independent = tree_sizes.len(); + let num_add_layer_sets = if independent > 1 { r.ue()? } else { 0 }; + if num_add_layer_sets > 1023 { + return Err(Refusal::ReservedValue); + } + for _ in 0..num_add_layer_sets { + for &size in tree_sizes.iter().skip(1) { + r.skip(ceil_log2(size + 1) as usize)?; + } + } + if r.flag()? { + r.skip(3 * n)?; + } + if r.flag()? { + for i in 0..max_l { + for j in (i + 1)..=max_l { + if deps[j][i] { + r.skip(3)?; + } + } + } + } + let _default_ref_layers_active = r.flag()?; + let num_ptl_minus1 = r.ue()?; + if num_ptl_minus1 > 63 { + return Err(Refusal::ReservedValue); + } + let first = if base_internal { 2 } else { 1 }; + for _ in first..=num_ptl_minus1 { + let present = r.flag()?; + let p = ptl(r, present, max_sub)?; + let prev = ptls.last().copied().unwrap_or(base_ptl); + ptls.push(inherit(p, &prev)); + } + Ok(VpsExt { scalability_mask: mask, layer_ids, view_order, aux, num_views, view_ids, deps, ptls }) +} + +/// What the probe reads from the base layer's sequence parameter set (§7.3.2.2.1). +#[derive(Debug, Clone, Copy)] +pub struct Sps { + pub ptl: Ptl, + pub chroma_format_idc: u64, + pub width: u64, + pub height: u64, + pub crop: [u64; 4], + pub bit_depth_luma: u64, + pub bit_depth_chroma: u64, +} + +pub fn sps(rbsp: &[u8]) -> Result { + let mut r = BitReader::new(rbsp); + let _vps_id = r.u(4)?; + let max_sub = r.u(3)?; + if max_sub > 6 { + return Err(Refusal::ReservedValue); + } + let _nesting = r.flag()?; + let p = ptl(&mut r, true, max_sub)?; + let id = r.ue()?; + if id > 15 { + return Err(Refusal::ReservedValue); + } + let chroma = r.ue()?; + if chroma > 3 { + return Err(Refusal::ReservedValue); + } + if chroma == 3 { + r.skip(1)?; + } + let width = r.ue()?; + let height = r.ue()?; + if width == 0 || height == 0 { + return Err(Refusal::ReservedValue); + } + let mut crop = [0u64; 4]; + if r.flag()? { + for c in &mut crop { + *c = r.ue()?; + } + } + let bdl = r.ue()?; + let bdc = r.ue()?; + if bdl > 8 || bdc > 8 { + return Err(Refusal::ReservedValue); + } + Ok(Sps { ptl: p, chroma_format_idc: chroma, width, height, crop, bit_depth_luma: bdl + 8, bit_depth_chroma: bdc + 8 }) +} + +/// The cropped size: the coded size less the conformance window, whose offsets count +/// chroma samples (§7.4.3.2.1, SubWidthC and SubHeightC). +pub fn cropped(chroma: u64, w: u64, h: u64, crop: [u64; 4]) -> Result<(u64, u64), Refusal> { + let (sw, sh) = match chroma { + 1 => (2, 2), + 2 => (2, 1), + _ => (1, 1), + }; + let dw = sw * (crop[0] + crop[1]); + let dh = sh * (crop[2] + crop[3]); + if dw >= w || dh >= h { + return Err(Refusal::ReservedValue); + } + Ok((w - dw, h - dh)) +} + +/// The probe. `multiview` is true for `wai.video.mvhevc`, which further requires a +/// multiview layer (§4.3). +pub fn probe(stream: &[u8], multiview: bool) -> Result { + let units = annex_b_units(stream)?; + // Whether the stream holds a VPS and a base-layer SPS anywhere: a slice before them is out + // of order if they come later, and the stream lacks them if they never do. + let has = |t: u64| units.iter().any(|u| u.bytes.len() >= 2 && u64::from((u.bytes[0] >> 1) & 0x3f) == t && (u16::from_be_bytes([u.bytes[0], u.bytes[1]]) >> 3) & 0x3f == 0); + let complete = has(NAL_VPS) && has(NAL_SPS); + let mut vps_info: Option = None; + let mut base_sps: Option = None; + let mut layers_seen = std::collections::BTreeSet::new(); + let mut vcl_units = 0u64; + for u in &units { + if u.bytes.len() < 2 { + return Err(Refusal::Truncated); + } + let h = u16::from_be_bytes([u.bytes[0], u.bytes[1]]); + if h & 0x8000 != 0 { + return Err(Refusal::ReservedValue); + } + let nut = u64::from((h >> 9) & 0x3f); + let layer = u64::from((h >> 3) & 0x3f); + let tid_plus1 = h & 7; + if tid_plus1 == 0 { + return Err(Refusal::ReservedValue); + } + let rbsp = unescape(&u.bytes[2..]); + if nut < 32 { + if base_sps.is_none() || vps_info.is_none() { + return Err(if complete { Refusal::OutOfOrder } else { Refusal::MissingStructure }); + } + vcl_units += 1; + layers_seen.insert(layer); + } else if nut == NAL_VPS && vps_info.is_none() { + vps_info = Some(vps(&rbsp)?); + } else if nut == NAL_SPS && layer == 0 && base_sps.is_none() { + base_sps = Some(sps(&rbsp)?); + } + } + let (Some(v), Some(s)) = (vps_info, base_sps) else { return Err(Refusal::MissingStructure) }; + if vcl_units == 0 { + return Err(Refusal::MissingStructure); + } + let (cw, ch) = cropped(s.chroma_format_idc, s.width, s.height, s.crop)?; + let views = v.ext.as_ref().map_or(1, |e| if e.scalability_mask & 2 != 0 { e.num_views } else { 1 }); + let mv_profile = v.ext.as_ref().is_some_and(|e| e.ptls.iter().any(|p| p.indicates(6))); + let enhancement_present = layers_seen.iter().any(|&l| l > 0); + if multiview && (views < 2 || !enhancement_present) { + return Err(Refusal::NotMultiview); + } + let mut out = json!({ + "profile_idc": s.ptl.profile_idc, + "tier": s.ptl.tier, + "level_idc": s.ptl.level_idc, + "chroma_format_idc": s.chroma_format_idc, + "bit_depth": [s.bit_depth_luma, s.bit_depth_chroma], + "coded": [s.width, s.height], + "cropped": [cw, ch], + "layers": v.max_layers_minus1.min(62) + 1, + "layers_present": layers_seen.iter().copied().collect::>(), + }); + if let Some(e) = &v.ext { + out["multilayer"] = json!({ + "scalability_mask": e.scalability_mask, + "layer_ids": e.layer_ids, + "view_order": e.view_order, + "aux": e.aux, + "views": views, + "view_ids": e.view_ids, + "profiles": e.ptls.iter().map(|p| p.profile_idc).collect::>(), + "multiview_profile": mv_profile, + }); + } + Ok(out) +} diff --git a/wai/wai-rs/src/format_probe/jxs.rs b/wai/wai-rs/src/format_probe/jxs.rs new file mode 100644 index 000000000..90516d233 --- /dev/null +++ b/wai/wai-rs/src/format_probe/jxs.rs @@ -0,0 +1,300 @@ +//! The JPEG XS probe (`wai.image.jxs`, `wai.video.jxs`; format-probes §3): the codestream's +//! markers from SOC to the first slice header, the picture header, the component table and +//! the trailing EOC, and the media-type parameters of RFC 9134 §7.1 they give. +//! +//! ISO/IEC 21122-1's text is not openly readable. The marker codes and segment layouts here +//! are the ones two independent open implementations agree on (format-probes §3.1); the +//! profile codes are ISO/IEC 21122-2:2022/Amd 1:2022 Table A.7 and the sublevel codes its +//! Table A.16, both read in the amendment's public preview. + +use super::Refusal; +use serde_json::{json, Value}; + +pub const SOC: u16 = 0xff10; +pub const EOC: u16 = 0xff11; +pub const PIH: u16 = 0xff12; +pub const CDT: u16 = 0xff13; +pub const WGT: u16 = 0xff14; +pub const COM: u16 = 0xff15; +pub const NLT: u16 = 0xff16; +pub const CWD: u16 = 0xff17; +pub const CTS: u16 = 0xff18; +pub const CRG: u16 = 0xff19; +pub const SLH: u16 = 0xff20; +pub const CAP: u16 = 0xff50; + +/// ISO/IEC 21122-2:2022/Amd 1:2022 Table A.7: `Ppih` and the profile's name as RFC 9134 +/// §7.1 writes it (white space omitted). +pub const PROFILES: &[(u16, &str)] = &[ + (0x0000, "Unrestricted"), + (0x1500, "Light422.10"), + (0x1a00, "Light444.12"), + (0x2500, "Light-Subline422.10"), + (0x3240, "Main420.12"), + (0x3540, "Main422.10"), + (0x3a40, "Main444.12"), + (0x3e40, "Main4444.12"), + (0x4240, "High420.12"), + (0x4a40, "High444.12"), + (0x4e40, "High4444.12"), + (0x6ec0, "MLS.12"), + (0x9300, "LightBayer"), + (0xb340, "MainBayer"), + (0xc340, "HighBayer"), +]; + +/// The level, `Plev`'s high byte. +pub const LEVELS: &[(u8, &str)] = &[ + (0x00, "Unrestricted"), + (0x04, "1k-1"), + (0x10, "2k-1"), + (0x20, "4k-1"), + (0x24, "4k-2"), + (0x28, "4k-3"), + (0x2c, "5k-1"), + (0x30, "8k-1"), + (0x34, "8k-2"), + (0x38, "8k-3"), + (0x40, "10k-1"), +]; + +/// The sublevel, `Plev`'s low byte (Amd 1 Table A.16). +pub const SUBLEVELS: &[(u8, &str)] = &[ + (0x00, "Unrestricted"), + (0x80, "Full"), + (0x10, "Sublev12bpp"), + (0x0c, "Sublev9bpp"), + (0x08, "Sublev6bpp"), + (0x06, "Sublev4bpp"), + (0x04, "Sublev3bpp"), + (0x03, "Sublev2bpp"), +]; + +fn be16(b: &[u8], at: usize) -> Result { + b.get(at..at + 2).map(|s| u16::from_be_bytes([s[0], s[1]])).ok_or(Refusal::Truncated) +} + +/// One component of the component table. +#[derive(Debug, Clone, Copy)] +pub struct Component { + pub depth: u8, + pub sx: u8, + pub sy: u8, +} + +/// What the probe reads from one codestream. +#[derive(Debug, Clone)] +pub struct Codestream { + pub ppih: u16, + pub plev: u16, + pub lcod: u32, + pub width: u16, + pub height: u16, + pub cw: u16, + pub hsl: u16, + pub nc: u8, + pub bw: u8, + pub fq: u8, + pub cpih: u8, + pub nlx: u8, + pub nly: u8, + pub lh: bool, + pub rl: bool, + pub qpih: u8, + pub cap_bits: Vec, + pub components: Vec, +} + +/// Reads one codestream, which is the whole of `b`: SOC first, EOC last. +pub fn codestream(b: &[u8]) -> Result { + if b.len() < 2 || be16(b, 0)? != SOC { + return Err(Refusal::NoSignature); + } + // CAP immediately after SOC. + if be16(b, 2)? != CAP { + return Err(Refusal::OutOfOrder); + } + let lcap = be16(b, 4)? as usize; + if lcap < 2 { + return Err(Refusal::LengthMismatch); + } + let cap = b.get(6..4 + lcap).ok_or(Refusal::Truncated)?; + let mut cap_bits = Vec::new(); + for (i, byte) in cap.iter().enumerate() { + for k in 0..8 { + if byte & (0x80 >> k) != 0 { + cap_bits.push((i * 8 + k) as u32); + } + } + } + let mut at = 4 + lcap; + // PIH immediately after CAP. + if be16(b, at)? != PIH { + return Err(Refusal::OutOfOrder); + } + if be16(b, at + 2)? != 26 { + return Err(Refusal::LengthMismatch); + } + let p = b.get(at + 4..at + 28).ok_or(Refusal::Truncated)?; + let lcod = u32::from_be_bytes([p[0], p[1], p[2], p[3]]); + let ppih = u16::from_be_bytes([p[4], p[5]]); + let plev = u16::from_be_bytes([p[6], p[7]]); + let width = u16::from_be_bytes([p[8], p[9]]); + let height = u16::from_be_bytes([p[10], p[11]]); + let cw = u16::from_be_bytes([p[12], p[13]]); + let hsl = u16::from_be_bytes([p[14], p[15]]); + let (nc, ng, ss, bw) = (p[16], p[17], p[18], p[19]); + let (fq, br) = (p[20] >> 4, p[20] & 15); + let (_fslc, _ppoc, cpih) = (p[21] >> 7, (p[21] >> 4) & 7, p[21] & 15); + let (nlx, nly) = (p[22] >> 4, p[22] & 15); + let (lh, rl, qpih) = (p[23] >> 7 == 1, (p[23] >> 6) & 1 == 1, (p[23] >> 4) & 3); + if width == 0 || height == 0 || hsl == 0 || !(1..=8).contains(&nc) || ng != 4 || ss != 8 || br != 4 { + return Err(Refusal::ReservedValue); + } + if ![0, 1, 3].contains(&cpih) || qpih > 1 || nlx == 0 { + return Err(Refusal::ReservedValue); + } + at += 28; + let (mut cdt, mut wgt, mut cts, mut crg) = (None, false, false, false); + loop { + let m = be16(b, at)?; + if m == SLH { + break; + } + let l = be16(b, at + 2)? as usize; + if l < 2 { + return Err(Refusal::LengthMismatch); + } + let body = b.get(at + 4..at + 2 + l).ok_or(Refusal::Truncated)?; + match m { + CDT => { + if l != 2 + 2 * nc as usize { + return Err(Refusal::LengthMismatch); + } + let comps: Vec = + body.chunks(2).map(|c| Component { depth: c[0], sx: c[1] >> 4, sy: c[1] & 15 }).collect(); + if comps.iter().any(|c| c.depth == 0 || c.sx == 0 || c.sy == 0) { + return Err(Refusal::ReservedValue); + } + cdt = Some(comps); + } + WGT => wgt = true, + CTS => cts = true, + CRG => crg = true, + COM | NLT | CWD => {} + SOC | EOC | PIH | CAP => return Err(Refusal::OutOfOrder), + _ => return Err(Refusal::ReservedValue), + } + at += 2 + l; + } + let Some(components) = cdt else { return Err(Refusal::MissingStructure) }; + if !wgt || (cpih == 3 && !(cts && crg)) { + return Err(Refusal::MissingStructure); + } + if b.len() < at + 4 || be16(b, b.len() - 2)? != EOC { + return Err(Refusal::MissingStructure); + } + if lcod != 0 && lcod as usize != b.len() { + return Err(Refusal::LengthMismatch); + } + Ok(Codestream { ppih, plev, lcod, width, height, cw, hsl, nc, bw, fq, cpih, nlx, nly, lh, rl, qpih, cap_bits, components }) +} + +fn name(table: &[(T, &'static str)], v: T) -> Option<&'static str> { + table.iter().find(|(k, _)| *k == v).map(|(_, n)| *n) +} + +/// The chroma structure the component table states: `4:4:4`, `4:2:2` or `4:2:0` for three +/// or four components whose first is full resolution and whose next two share a factor; +/// otherwise `null`. +fn structure(c: &[Component]) -> Option<&'static str> { + if c.len() < 3 || c[0].sx != 1 || c[0].sy != 1 || c[1].sx != c[2].sx || c[1].sy != c[2].sy { + return None; + } + match (c[1].sx, c[1].sy) { + (1, 1) => Some("4:4:4"), + (2, 1) => Some("4:2:2"), + (2, 2) => Some("4:2:0"), + _ => None, + } +} + +/// The parameters of one codestream, with the RFC 9134 §7.1 media-type parameters it +/// determines (format-probes §3.4). +pub fn params(c: &Codestream) -> Value { + let profile = name(PROFILES, c.ppih); + let level = name(LEVELS, (c.plev >> 8) as u8); + let sublevel = name(SUBLEVELS, (c.plev & 0xff) as u8); + let depth = c.components.iter().map(|x| x.depth).max().unwrap_or(0); + let st = structure(&c.components); + // RGB samples only where a colour transform is signalled over three full components. + let sampling = if c.cpih != 0 && st == Some("4:4:4") { Some("RGB") } else { None }; + json!({ + "profile": format!("0x{:04x}", c.ppih), + "level": format!("0x{:04x}", c.plev), + "width": c.width, + "height": c.height, + "components": c.components.iter().map(|x| json!([x.depth, x.sx, x.sy])).collect::>(), + "structure": st, + "colour_transform": c.cpih, + "decompositions": [c.nlx, c.nly], + "precinct_width": c.cw, + "slice_height": c.hsl, + "wavelet_precision": c.bw, + "fractional_bits": c.fq, + "quantizer": c.qpih, + "long_header": c.lh, + "raw_mode": c.rl, + "cbr_bytes": c.lcod, + "cap_bits": c.cap_bits, + "rtp": { + "profile": profile, + "level": level, + "sublevel": sublevel, + "depth": depth, + "width": c.width, + "height": c.height, + "sampling": sampling, + }, + }) +} + +pub fn probe_image(b: &[u8]) -> Result { + Ok(params(&codestream(b)?)) +} + +/// `wai.video.jxs`: five `u32` little-endian values `n`, `h`, `w`, `fps_num`, `fps_den`, +/// then `n` codestreams each preceded by its `u32` little-endian length (format-probes +/// §3.3). +pub fn probe_video(b: &[u8]) -> Result { + let word = |i: usize| -> Result { + b.get(4 * i..4 * i + 4).map(|s| u32::from_le_bytes([s[0], s[1], s[2], s[3]])).ok_or(Refusal::Truncated) + }; + let (n, h, w, num, den) = (word(0)?, word(1)?, word(2)?, word(3)?, word(4)?); + if n == 0 || h == 0 || w == 0 || num == 0 || den == 0 { + return Err(Refusal::FramingInvalid); + } + let mut at = 20usize; + let mut first = None; + let mut lcods = Vec::with_capacity(n as usize); + for _ in 0..n { + let len = b.get(at..at + 4).map(|s| u32::from_le_bytes([s[0], s[1], s[2], s[3]])).ok_or(Refusal::Truncated)? as usize; + let frame = b.get(at + 4..at + 4 + len).ok_or(Refusal::Truncated)?; + let c = codestream(frame)?; + if u32::from(c.width) > w || u32::from(c.height) > h { + return Err(Refusal::FramingInvalid); + } + if first.is_none() { + first = Some(params(&c)); + } + lcods.push(c.lcod); + at += 4 + len; + } + if at != b.len() { + return Err(Refusal::TrailingBytes); + } + // The bytes every frame takes, where every frame states the same non-zero Lcod: the + // constant number of bytes a frame SMPTE ST 2110-22 §4 asks of a stream (§3.5). + let constant = lcods.iter().all(|&l| l != 0 && l == lcods[0]).then_some(lcods[0]); + Ok(json!({ "frames": n, "size": [w, h], "fps": [num, den], "constant_bytes": constant, "first": first })) +} diff --git a/wai/wai-rs/src/format_probe/lcevc.rs b/wai/wai-rs/src/format_probe/lcevc.rs new file mode 100644 index 000000000..aaed3ed06 --- /dev/null +++ b/wai/wai-rs/src/format_probe/lcevc.rs @@ -0,0 +1,241 @@ +//! The LCEVC probe (`wai.video.lcevc`; format-probes §6): the byte stream's NAL units, each +//! one's process blocks, and the first sequence and global configurations. ISO/IEC 23094-2's +//! text is not openly readable; the layout is the one two independent open implementations +//! agree on, each citing its clauses (format-probes §6.1). + +use super::bits::{annex_b_units, unescape, BitReader}; +use super::Refusal; +use serde_json::{json, Value}; + +/// The sizes of `resolution_type` 1 to 50 (ISO/IEC 23094-2 Table 20, as the open decoder +/// reproduces it); 0 and 51 to 62 are not sizes, and 63 is a custom size. +pub const RESOLUTIONS: [(u16, u16); 50] = [ + (360, 200), (400, 240), (480, 320), (640, 360), (640, 480), (768, 480), (800, 600), + (852, 480), (854, 480), (856, 480), (960, 540), (960, 640), (1024, 576), (1024, 600), + (1024, 768), (1152, 864), (1280, 720), (1280, 800), (1280, 1024), (1360, 768), + (1366, 768), (1400, 1050), (1440, 900), (1600, 1200), (1680, 1050), (1920, 1080), + (1920, 1200), (2048, 1080), (2048, 1152), (2048, 1536), (2160, 1440), (2560, 1440), + (2560, 1600), (2560, 2048), (3200, 1800), (3200, 2048), (3200, 2400), (3440, 1440), + (3840, 1600), (3840, 2160), (3840, 2400), (4096, 2160), (4096, 3072), (5120, 2880), + (5120, 3200), (5120, 4096), (6400, 4096), (6400, 4800), (7680, 4320), (7680, 4800), +]; + +pub const NUT_NON_IDR: u8 = 28; +pub const NUT_IDR: u8 = 29; + +pub const BLOCK_SEQUENCE_CONFIG: u8 = 0; +pub const BLOCK_GLOBAL_CONFIG: u8 = 1; +pub const BLOCK_PICTURE_CONFIG: u8 = 2; + +/// A multi-byte value: 7 bits a byte, most significant first, while the byte's top bit is +/// set; at most ten bytes. +fn multibyte(b: &[u8], at: &mut usize) -> Result { + let mut v: u64 = 0; + for _ in 0..10 { + let x = *b.get(*at).ok_or(Refusal::Truncated)?; + *at += 1; + v = (v << 7) | u64::from(x & 0x7f); + if x & 0x80 == 0 { + return Ok(v); + } + } + Err(Refusal::ReservedValue) +} + +/// The blocks of one NAL unit's payload (after its two header bytes, emulation prevention +/// removed, the final `0x80` stop byte removed): `(type, body)`. +pub fn blocks(payload: &[u8]) -> Result, Refusal> { + let mut out = Vec::new(); + let mut at = 0; + while at < payload.len() { + let h = payload[at]; + at += 1; + let (size_type, kind) = (h >> 5, h & 0x1f); + let size = match size_type { + 0..=5 => u64::from(size_type), + 6 => return Err(Refusal::ReservedValue), + _ => multibyte(payload, &mut at)?, + } as usize; + let body = payload.get(at..at.checked_add(size).ok_or(Refusal::LengthMismatch)?).ok_or(Refusal::LengthMismatch)?; + out.push((kind, body)); + at += size; + } + Ok(out) +} + +#[derive(Debug, Clone)] +pub struct SequenceConfig { + pub profile_idc: u64, + pub level_idc: u64, + pub sublevel_idc: u64, + pub conformance_window: Option<[u64; 4]>, +} + +pub fn sequence_config(b: &[u8]) -> Result { + let mut r = BitReader::new(b); + let profile_idc = r.u(4)?; + let level_idc = r.u(4)?; + let sublevel_idc = r.u(2)?; + let window = r.flag()?; + if r.u(5)? != 0 { + return Err(Refusal::ReservedValue); + } + if profile_idc == 15 || level_idc == 15 { + // The extended profile and level: the two open readings of their byte disagree. + return Err(Refusal::OutsideProbe); + } + let mut at = r.position() / 8; + let conformance_window = if window { + let mut w = [0u64; 4]; + for x in &mut w { + *x = multibyte(b, &mut at)?; + } + Some(w) + } else { + None + }; + if at != b.len() { + return Err(Refusal::LengthMismatch); + } + Ok(SequenceConfig { profile_idc, level_idc, sublevel_idc, conformance_window }) +} + +#[derive(Debug, Clone)] +pub struct GlobalConfig { + pub resolution_type: u64, + pub transform_type: u64, + pub chroma_sampling_type: u64, + pub base_depth_type: u64, + pub enhancement_depth_type: u64, + pub temporal_enabled: bool, + pub upsample_type: u64, + pub tile_dimensions_type: u64, + pub custom_size: Option<[u64; 2]>, +} + +impl GlobalConfig { + /// The enhanced picture's size: Table 20's for a listed type, the custom size for 63. + pub fn size(&self) -> [u64; 2] { + match self.custom_size { + Some(s) => s, + None => { + let (w, h) = RESOLUTIONS[self.resolution_type as usize - 1]; + [u64::from(w), u64::from(h)] + } + } + } +} + +/// The four fixed bytes of the global configuration and the conditional fields up to the +/// custom resolution (format-probes §6.4). +pub fn global_config(b: &[u8]) -> Result { + let mut r = BitReader::new(b); + let planes_flag = r.flag()?; + let resolution_type = r.u(6)?; + let transform_type = r.u(1)?; + let chroma_sampling_type = r.u(2)?; + let base_depth_type = r.u(2)?; + let enhancement_depth_type = r.u(2)?; + let tsw_modifier = r.flag()?; + let _predicted_residual = r.flag()?; + let _temporal_tile_intra = r.flag()?; + let temporal_enabled = r.flag()?; + let upsample_type = r.u(3)?; + let l1_filter = r.flag()?; + let _scaling_l1 = r.u(2)?; + let _scaling_l2 = r.u(2)?; + let tile_dimensions_type = r.u(2)?; + let _user_data = r.u(2)?; + let _l1_depth = r.flag()?; + let _chroma_step = r.flag()?; + if planes_flag { + r.skip(8)?; + } + if tsw_modifier { + r.skip(8)?; + } + if upsample_type == 4 { + r.skip(64)?; + } + if l1_filter { + r.skip(8)?; + } + if tile_dimensions_type > 0 { + if tile_dimensions_type == 3 { + r.skip(32)?; + } + r.skip(8)?; + } + if resolution_type == 0 || (51..63).contains(&resolution_type) || upsample_type > 4 { + return Err(Refusal::ReservedValue); + } + let custom_size = if resolution_type == 63 { Some([r.u(16)?, r.u(16)?]) } else { None }; + if custom_size.is_some_and(|[w, h]| w == 0 || h == 0) { + return Err(Refusal::ReservedValue); + } + Ok(GlobalConfig { + resolution_type, transform_type, chroma_sampling_type, base_depth_type, enhancement_depth_type, + temporal_enabled, upsample_type, tile_dimensions_type, custom_size, + }) +} + +pub fn probe(stream: &[u8]) -> Result { + let units = annex_b_units(stream)?; + let (mut seq, mut glob) = (None, None); + let (mut idr, mut non_idr, mut pictures) = (0u64, 0u64, 0u64); + for (i, u) in units.iter().enumerate() { + let b = u.bytes; + if b.len() < 3 { + return Err(Refusal::Truncated); + } + // forbidden_zero_bit 0, forbidden_one_bit 1, every reserved_flag bit 1. + if b[0] & 0xc1 != 0x41 || b[1] != 0xff { + return Err(Refusal::ReservedValue); + } + let nut = (b[0] >> 1) & 0x1f; + match nut { + NUT_IDR => idr += 1, + NUT_NON_IDR => non_idr += 1, + _ => return Err(Refusal::ReservedValue), + } + if i == 0 && nut != NUT_IDR { + return Err(Refusal::OutOfOrder); + } + let rbsp = unescape(&b[2..]); + let Some((&stop, payload)) = rbsp.split_last() else { return Err(Refusal::Truncated) }; + if stop != 0x80 { + return Err(Refusal::ReservedValue); + } + for (kind, body) in blocks(payload)? { + match kind { + BLOCK_SEQUENCE_CONFIG if seq.is_none() => seq = Some(sequence_config(body)?), + BLOCK_GLOBAL_CONFIG if glob.is_none() => glob = Some(global_config(body)?), + BLOCK_PICTURE_CONFIG => { + if seq.is_none() || glob.is_none() { + return Err(Refusal::OutOfOrder); + } + pictures += 1; + } + _ => {} + } + } + } + let (Some(s), Some(g)) = (seq, glob) else { return Err(Refusal::MissingStructure) }; + Ok(json!({ + "nal_units": [idr, non_idr], + "pictures": pictures, + "profile_idc": s.profile_idc, + "level_idc": s.level_idc, + "sublevel_idc": s.sublevel_idc, + "conformance_window": s.conformance_window, + "resolution_type": g.resolution_type, + "size": g.size(), + "transform_type": g.transform_type, + "chroma_sampling_type": g.chroma_sampling_type, + "base_depth": 8 + 2 * g.base_depth_type, + "enhancement_depth": 8 + 2 * g.enhancement_depth_type, + "temporal_enabled": g.temporal_enabled, + "upsample_type": g.upsample_type, + "tile_dimensions_type": g.tile_dimensions_type, + })) +} diff --git a/wai/wai-rs/src/format_probe/mhas.rs b/wai/wai-rs/src/format_probe/mhas.rs new file mode 100644 index 000000000..e47bdd93b --- /dev/null +++ b/wai/wai-rs/src/format_probe/mhas.rs @@ -0,0 +1,164 @@ +//! The MPEG-H 3D Audio probe (`wai.audio.mpegh`; format-probes §7): the MHAS packet +//! stream, its synchronisation and configuration packets, and the start of +//! `mpegh3daConfig( )`. ISO/IEC 23008-3's text is not openly readable; the packet syntax +//! and the configuration's first fields are the ones two independent open decoders agree +//! on, and the profile-level codes of the low-complexity profile are confirmed by ATSC +//! A/342-3:2024-04 §5.2.2.1 (format-probes §7.1). + +use super::bits::{BitReader, BitWriter}; +use super::Refusal; +use serde_json::{json, Value}; + +pub const PACTYP_FILLDATA: u64 = 0; +pub const PACTYP_MPEGH3DACFG: u64 = 1; +pub const PACTYP_MPEGH3DAFRAME: u64 = 2; +pub const PACTYP_SYNC: u64 = 6; +pub const SYNC_BYTE: u8 = 0xa5; + +/// `escapedValue( nBits1, nBits2, nBits3 )`. +pub fn escaped(r: &mut BitReader, n1: u32, n2: u32, n3: u32) -> Result { + let mut v = r.u(n1)?; + if v == (1 << n1) - 1 { + let add = r.u(n2)?; + v += add; + if add == (1 << n2) - 1 { + v += r.u(n3)?; + } + } + Ok(v) +} + +/// The writer's inverse of [`escaped`] (the corpus generator's). +pub fn write_escaped(w: &mut BitWriter, v: u64, n1: u32, n2: u32, n3: u32) { + let m1 = (1u64 << n1) - 1; + let m2 = (1u64 << n2) - 1; + if v < m1 { + w.u(n1, v); + } else if v - m1 < m2 { + w.u(n1, m1); + w.u(n2, v - m1); + } else { + w.u(n1, m1); + w.u(n2, m2); + w.u(n3, v - m1 - m2); + } +} + +/// The sampling rates of `usacSamplingFrequencyIndex` 0 to 27; `None` where reserved. +pub const SAMPLING_RATES: [Option; 31] = [ + Some(96000), Some(88200), Some(64000), Some(48000), Some(44100), Some(32000), Some(24000), + Some(22050), Some(16000), Some(12000), Some(11025), Some(8000), Some(7350), None, None, + Some(57600), Some(51200), Some(40000), Some(38400), Some(34150), Some(28800), Some(25600), + Some(20000), Some(19200), Some(17075), Some(14400), Some(12800), Some(9600), None, None, None, +]; + +/// One packet: its type, label and payload. +pub struct Packet<'a> { + pub kind: u64, + pub label: u64, + pub payload: &'a [u8], +} + +/// Splits an MHAS stream into packets. Every packet header is a whole number of bytes. +pub fn packets(b: &[u8]) -> Result>, Refusal> { + let mut out = Vec::new(); + let mut at = 0usize; + while at < b.len() { + let mut r = BitReader::new(&b[at..]); + let kind = escaped(&mut r, 3, 8, 8)?; + let label = escaped(&mut r, 2, 8, 32)?; + let len = escaped(&mut r, 11, 24, 24)? as usize; + debug_assert!(r.byte_aligned()); + let start = at + r.position() / 8; + let payload = b.get(start..start.checked_add(len).ok_or(Refusal::Truncated)?).ok_or(Refusal::Truncated)?; + out.push(Packet { kind, label, payload }); + at = start + len; + } + Ok(out) +} + +/// The profile-level indication's profile and level, for the codes this probe names. +fn profile_level(pl: u64) -> (Option<&'static str>, Option) { + match pl { + 0x0b..=0x0f => (Some("low-complexity"), Some(pl - 0x0a)), + 0x10..=0x14 => (Some("baseline"), Some(pl - 0x0f)), + _ => (None, None), + } +} + +pub fn probe(b: &[u8]) -> Result { + // The signature: the first packet is a synchronisation or a configuration packet. + let first = escaped(&mut BitReader::new(b), 3, 8, 8).map_err(|_| Refusal::NoSignature)?; + if first != PACTYP_SYNC && first != PACTYP_MPEGH3DACFG { + return Err(Refusal::NoSignature); + } + let ps = packets(b)?; + let mut cfg: Option = None; + let (mut frames, mut syncs, mut configs) = (0u64, 0u64, 0u64); + for p in &ps { + match p.kind { + PACTYP_SYNC => { + if p.label != 0 || p.payload != [SYNC_BYTE] { + return Err(Refusal::ReservedValue); + } + syncs += 1; + } + PACTYP_MPEGH3DACFG => { + configs += 1; + if cfg.is_none() { + cfg = Some(config(p.payload)?); + } + } + PACTYP_MPEGH3DAFRAME => { + if cfg.is_none() { + return Err(Refusal::OutOfOrder); + } + frames += 1; + } + _ => {} + } + } + let Some(mut out) = cfg else { return Err(Refusal::MissingStructure) }; + out["packets"] = json!(ps.len()); + out["frames"] = json!(frames); + out["syncs"] = json!(syncs); + out["configs"] = json!(configs); + Ok(out) +} + +/// The start of `mpegh3daConfig( )`: the profile-level indication, the sampling rate, the +/// core frame length index, and the reference layout of `SpeakerConfig3d( )`. +pub fn config(b: &[u8]) -> Result { + let mut r = BitReader::new(b); + let pl = r.u(8)?; + let sfi = r.u(5)?; + let rate = if sfi == 0x1f { + r.u(24)? + } else { + u64::from(SAMPLING_RATES[sfi as usize].ok_or(Refusal::ReservedValue)?) + }; + if rate == 0 { + return Err(Refusal::ReservedValue); + } + let core = r.u(3)?; + let _reserved = r.u(1)?; + let _receiver_delay = r.u(1)?; + let layout_type = r.u(2)?; + let (mut cicp, mut speakers) = (None, None); + if layout_type == 0 { + cicp = Some(r.u(6)?); + } else if layout_type < 3 { + speakers = Some(escaped(&mut r, 5, 8, 16)? + 1); + } + let (profile, level) = profile_level(pl); + Ok(json!({ + "profile_level": pl, + "profile": profile, + "level": level, + "sampling_rate": rate, + "core_frame_length_index": core, + "speaker_layout_type": layout_type, + "cicp_layout": cicp, + "speakers": speakers, + })) +} diff --git a/wai/wai-rs/src/format_probe/nic.rs b/wai/wai-rs/src/format_probe/nic.rs new file mode 100644 index 000000000..59fbf3ab8 --- /dev/null +++ b/wai/wai-rs/src/format_probe/nic.rs @@ -0,0 +1,40 @@ +//! The IEEE 1857.11 parse (`wai.image.ieee1857_11`, a candidate; format-probes §10). The +//! standard's text is not openly readable. Its open reference software writes a picture +//! header with no start code, which this parse reads as that software's writer lays it +//! out: three bytes of identifiers, the picture's height and width, and two bytes of +//! picture format. The bytes carry no signature, so the parse cannot tell this format from +//! another: it reads a payload declared to be one. + +use super::Refusal; +use serde_json::{json, Value}; + +pub fn probe(b: &[u8]) -> Result { + let h = b.get(..13).ok_or(Refusal::Truncated)?; + let ids = u32::from_be_bytes([0, h[0], h[1], h[2]]); + let profile_id = ids >> 21; + let level_id = (ids >> 15) & 0x3f; + let model_id = (ids >> 12) & 7; + let parameter_set_id = (ids >> 5) & 0x7f; + let quality_id = ids & 0x1f; + let height = u32::from_be_bytes([h[3], h[4], h[5], h[6]]); + let width = u32::from_be_bytes([h[7], h[8], h[9], h[10]]); + let fmt = u16::from_be_bytes([h[11], h[12]]); + let depth_code = (fmt >> 11) & 7; + let picture_format = (fmt >> 8) & 7; + let slice_h = (fmt >> 4) & 15; + let slice_w = fmt & 15; + if profile_id > 2 || fmt >> 14 != 0 || !(1..=2).contains(&depth_code) || height == 0 || width == 0 { + return Err(Refusal::ReservedValue); + } + Ok(json!({ + "profile_id": profile_id, + "level_id": level_id, + "model_id": model_id, + "parameter_set_id": parameter_set_id, + "quality_id": quality_id, + "size": [width, height], + "bit_depth": if depth_code == 1 { 8 } else { 10 }, + "picture_format": picture_format, + "slice_size": [1u32 << (slice_w + 6), 1u32 << (slice_h + 6)], + })) +} diff --git a/wai/wai-rs/src/format_probe/projection.rs b/wai/wai-rs/src/format_probe/projection.rs new file mode 100644 index 000000000..22a923098 --- /dev/null +++ b/wai/wai-rs/src/format_probe/projection.rs @@ -0,0 +1,129 @@ +//! The projection descriptor (`wai.meta.projection`; extensions/projection.md): how the +//! pictures of one video rendition map onto the viewer's sphere or field of view, and how +//! its two views are arranged. A `WPJ1` record of 40 bytes, little-endian. + +use super::Refusal; +use serde_json::{json, Value}; + +pub const MAGIC: &[u8; 4] = b"WPJ1"; +pub const LEN: usize = 40; +/// One degree in the descriptor's angle unit, 2â�»Â¹â�¶ degrees (ITU-T H.274 §8.15). +pub const DEG: i64 = 1 << 16; + +pub const PROJECTIONS: [&str; 4] = ["rectilinear", "equirectangular", "half-equirectangular", "fisheye"]; +pub const STEREO: [&str; 4] = ["mono", "side-by-side", "top-bottom", "layers"]; + +/// A projection descriptor. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Projection { + pub projection: u8, + pub stereo: u8, + pub first_view: u8, + pub azimuth_range: u32, + pub elevation_range: u32, + pub centre_azimuth: i32, + pub centre_elevation: i32, + pub centre_tilt: i32, + pub baseline_um: u32, + pub left_layer: u8, + pub right_layer: u8, +} + +impl Projection { + pub fn to_bytes(&self) -> Vec { + let mut b = Vec::with_capacity(LEN); + b.extend_from_slice(MAGIC); + b.extend_from_slice(&[1, self.projection, self.stereo, self.first_view]); + b.extend_from_slice(&self.azimuth_range.to_le_bytes()); + b.extend_from_slice(&self.elevation_range.to_le_bytes()); + b.extend_from_slice(&self.centre_azimuth.to_le_bytes()); + b.extend_from_slice(&self.centre_elevation.to_le_bytes()); + b.extend_from_slice(&self.centre_tilt.to_le_bytes()); + b.extend_from_slice(&self.baseline_um.to_le_bytes()); + b.extend_from_slice(&[self.left_layer, self.right_layer, 0, 0, 0, 0, 0, 0]); + b + } + + /// Reads a descriptor and checks it on its own (projection.md §3, §4). + pub fn read(b: &[u8]) -> Result { + if b.len() < 4 || &b[..4] != MAGIC { + return Err(Refusal::NoSignature); + } + if b.len() != LEN { + return Err(Refusal::LengthMismatch); + } + let u32_at = |i: usize| u32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]); + let i32_at = |i: usize| i32::from_le_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]); + if b[4] != 1 || b[5] > 3 || b[6] > 3 || b[7] > 1 || b[34..].iter().any(|&x| x != 0) { + return Err(Refusal::ReservedValue); + } + let p = Projection { + projection: b[5], + stereo: b[6], + first_view: b[7], + azimuth_range: u32_at(8), + elevation_range: u32_at(12), + centre_azimuth: i32_at(16), + centre_elevation: i32_at(20), + centre_tilt: i32_at(24), + baseline_um: u32_at(28), + left_layer: b[32], + right_layer: b[33], + }; + p.check()?; + Ok(p) + } + + fn check(&self) -> Result<(), Refusal> { + let (az, el) = (i64::from(self.azimuth_range), i64::from(self.elevation_range)); + let ranges_ok = match self.projection { + 0 => (az == 0 && el == 0) || (0 < az && az < 180 * DEG && 0 < el && el < 180 * DEG), + 1 => az == 360 * DEG && el == 180 * DEG, + 2 => az == 180 * DEG && el == 180 * DEG, + _ => 0 < az && az <= 360 * DEG && 0 < el && el <= 360 * DEG, + }; + let (ca, ce, ct) = (i64::from(self.centre_azimuth), i64::from(self.centre_elevation), i64::from(self.centre_tilt)); + let centre_ok = (-180 * DEG..180 * DEG).contains(&ca) && (-90 * DEG..=90 * DEG).contains(&ce) && (-180 * DEG..180 * DEG).contains(&ct); + let views_ok = match self.stereo { + 0 => self.first_view == 0 && self.baseline_um == 0 && self.left_layer == 0 && self.right_layer == 0, + 1 | 2 => self.left_layer == 0 && self.right_layer == 0, + _ => self.left_layer != self.right_layer && self.left_layer <= 62 && self.right_layer <= 62, + }; + if ranges_ok && centre_ok && views_ok { Ok(()) } else { Err(Refusal::DescriptorInvalid) } + } + + pub fn params(&self) -> Value { + json!({ + "projection": PROJECTIONS[self.projection as usize], + "stereo": STEREO[self.stereo as usize], + "first_view": if self.first_view == 0 { "left" } else { "right" }, + "coverage": [self.azimuth_range, self.elevation_range], + "centre": [self.centre_azimuth, self.centre_elevation, self.centre_tilt], + "baseline_um": self.baseline_um, + "layers": if self.stereo == 3 { json!([self.left_layer, self.right_layer]) } else { Value::Null }, + }) + } + + /// The binding check against the rendition the descriptor accompanies, given that + /// rendition's capability and its probe's parameters (projection.md §5): views in layers + /// need a multiview base that holds both layers. + pub fn check_binding(&self, base_capability: &str, base: &Value) -> Result<(), Refusal> { + if self.stereo != 3 { + return Ok(()); + } + if base_capability != crate::codecs::CAP_VIDEO_MVHEVC { + return Err(Refusal::LayerAbsent); + } + let present: Vec = base["layers_present"].as_array().map(|a| a.iter().filter_map(Value::as_u64).collect()).unwrap_or_default(); + for l in [self.left_layer, self.right_layer] { + if !present.contains(&u64::from(l)) { + return Err(Refusal::LayerAbsent); + } + } + Ok(()) + } +} + +pub fn probe(b: &[u8]) -> Result { + Ok(Projection::read(b)?.params()) +} diff --git a/wai/wai-rs/src/format_probe/vvc.rs b/wai/wai-rs/src/format_probe/vvc.rs new file mode 100644 index 000000000..45d2561c9 --- /dev/null +++ b/wai/wai-rs/src/format_probe/vvc.rs @@ -0,0 +1,173 @@ +//! The H.266 probe (`wai.video.vvc`; format-probes §5): the Annex B byte stream's NAL +//! units, the start of the video parameter set, and the sequence parameter set through its +//! conformance window. Syntax from ITU-T H.266 (V4, 01/2026) §7.3.1.2, §7.3.2.3, §7.3.2.4, +//! §7.3.3.1 and §7.3.3.2. + +use super::bits::{annex_b_units, unescape, BitReader}; +use super::hevc::cropped; +use super::Refusal; +use serde_json::{json, Value}; + +pub const NAL_VPS: u64 = 14; +pub const NAL_SPS: u64 = 15; + +/// `general_constraints_info( )` (§7.3.3.2): its 71 fixed bits, the additional bits and +/// the zero alignment, read and discarded. +fn gci(r: &mut BitReader) -> Result<(), Refusal> { + if r.flag()? { + r.skip(71)?; + // gci_num_additional_bits: six named flags when it exceeds 5, then reserved bits, + // so as many bits as it states either way. + let n = r.u(8)? as usize; + r.skip(n)?; + } + r.align(Some(0)) +} + +#[derive(Debug, Clone)] +pub struct Ptl { + pub profile_idc: Option, + pub tier: Option, + pub level_idc: u64, + pub frame_only: bool, + pub multilayer: bool, + pub sub_profiles: Vec, +} + +/// `profile_tier_level( profileTierPresentFlag, MaxNumSubLayersMinus1 )` (§7.3.3.1). +pub fn ptl(r: &mut BitReader, present: bool, max_sub: u64) -> Result { + let (mut profile_idc, mut tier) = (None, None); + if present { + profile_idc = Some(r.u(7)?); + tier = Some(r.u(1)?); + } + let level_idc = r.u(8)?; + let frame_only = r.flag()?; + let multilayer = r.flag()?; + if present { + gci(r)?; + } + let mut level_present = Vec::new(); + for _ in 0..max_sub { + level_present.push(r.flag()?); + } + r.align(None)?; + for &p in &level_present { + if p { + r.skip(8)?; + } + } + let mut sub_profiles = Vec::new(); + if present { + let n = r.u(8)?; + for _ in 0..n { + sub_profiles.push(r.u(32)?); + } + } + Ok(Ptl { profile_idc, tier, level_idc, frame_only, multilayer, sub_profiles }) +} + +#[derive(Debug, Clone)] +pub struct Sps { + pub ptl: Option, + pub chroma_format_idc: u64, + pub ctu_size: u64, + pub width: u64, + pub height: u64, + pub crop: [u64; 4], +} + +/// `seq_parameter_set_rbsp( )` (§7.3.2.4) through the conformance window. +pub fn sps(rbsp: &[u8]) -> Result { + let mut r = BitReader::new(rbsp); + let _id = r.u(4)?; + let _vps_id = r.u(4)?; + let max_sub = r.u(3)?; + if max_sub > 6 { + return Err(Refusal::ReservedValue); + } + let chroma = r.u(2)?; + let ctu_minus5 = r.u(2)?; + if ctu_minus5 > 2 { + return Err(Refusal::ReservedValue); + } + let ptl_present = r.flag()?; + let p = if ptl_present { Some(ptl(&mut r, true, max_sub)?) } else { None }; + let _gdr = r.flag()?; + if r.flag()? { + r.skip(1)?; + } + let width = r.ue()?; + let height = r.ue()?; + if width == 0 || height == 0 { + return Err(Refusal::ReservedValue); + } + let mut crop = [0u64; 4]; + if r.flag()? { + for c in &mut crop { + *c = r.ue()?; + } + } + Ok(Sps { ptl: p, chroma_format_idc: chroma, ctu_size: 1 << (ctu_minus5 + 5), width, height, crop }) +} + +pub fn probe(stream: &[u8]) -> Result { + let units = annex_b_units(stream)?; + let has_sps = units.iter().any(|u| u.bytes.len() >= 2 && u64::from(u.bytes[1] >> 3) == NAL_SPS); + let mut s: Option = None; + let mut vps_layers: Option = None; + let mut layers_seen = std::collections::BTreeSet::new(); + let mut vcl = 0u64; + for u in &units { + if u.bytes.len() < 2 { + return Err(Refusal::Truncated); + } + let h = u16::from_be_bytes([u.bytes[0], u.bytes[1]]); + // forbidden_zero_bit and nuh_reserved_zero_bit. + if h & 0xc000 != 0 { + return Err(Refusal::ReservedValue); + } + let layer = u64::from((h >> 8) & 0x3f); + let nut = u64::from((h >> 3) & 0x1f); + if h & 7 == 0 { + return Err(Refusal::ReservedValue); + } + if layer > 55 { + return Err(Refusal::ReservedValue); + } + let rbsp = unescape(&u.bytes[2..]); + if nut <= 11 { + if s.is_none() { + return Err(if has_sps { Refusal::OutOfOrder } else { Refusal::MissingStructure }); + } + vcl += 1; + layers_seen.insert(layer); + } else if nut == NAL_VPS && vps_layers.is_none() { + let mut r = BitReader::new(&rbsp); + let _id = r.u(4)?; + vps_layers = Some(r.u(6)? + 1); + } else if nut == NAL_SPS && s.is_none() { + s = Some(sps(&rbsp)?); + } + } + let Some(s) = s else { return Err(Refusal::MissingStructure) }; + if vcl == 0 { + return Err(Refusal::MissingStructure); + } + let (cw, ch) = cropped(s.chroma_format_idc, s.width, s.height, s.crop)?; + let p = s.ptl.as_ref(); + Ok(json!({ + "profile_idc": p.and_then(|p| p.profile_idc), + "tier": p.and_then(|p| p.tier), + "level_idc": p.map(|p| p.level_idc), + "frame_only": p.map(|p| p.frame_only), + "multilayer_enabled": p.map(|p| p.multilayer), + "sub_profiles": p.map(|p| p.sub_profiles.clone()).unwrap_or_default(), + "chroma_format_idc": s.chroma_format_idc, + "ctu_size": s.ctu_size, + "coded": [s.width, s.height], + "cropped": [cw, ch], + "layers": vps_layers.unwrap_or(1), + "layers_present": layers_seen.iter().copied().collect::>(), + })) +} diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 073498658..9c2084d70 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -392,6 +392,14 @@ pub mod delivery_terms; #[cfg(feature = "telemetry")] pub mod telemetry; +// Header probes for the sink-supplied formats (extensions/format-probes.md): identify a +// payload as its capability's format and read its parameters, or refuse it with a code. Pure +// integer code over bytes, no dependency beyond serde_json and sha2; wasm32-clean. Rust-only: +// its constants must not become macros in include/wai.h. +/// cbindgen:ignore +#[cfg(feature = "format_probe")] +pub mod format_probe; + // Live events (extensions/live-events.md): `wai.live.events`, a signed, hash-chained, // timeline-bound event track. Cues, chat, reactions, polls, markers, remote control, // captions, moderation decisions and room lifecycle are one record, `WEV1`, with its diff --git a/wai/wai-rs/tests/format_probe.rs b/wai/wai-rs/tests/format_probe.rs new file mode 100644 index 000000000..df8efc699 --- /dev/null +++ b/wai/wai-rs/tests/format_probe.rs @@ -0,0 +1,80 @@ +//! Header probes (extensions/format-probes.md): the committed corpus `format-probe-conformance/` +//! regenerates byte for byte from its external vectors and holds no other generated file, +//! every vector, binding and fact gets its stated outcome, the extension's §2.4 and §12 tables +//! are the reference's probes and codes, and projection.md §4 lists only codes of §12. +#![cfg(feature = "format_probe")] + +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; +use wai::format_probe::{corpus, Refusal, PROBES}; + +fn wai_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") +} + +fn walk(root: &Path, dir: &Path, out: &mut BTreeMap>) { + for e in std::fs::read_dir(dir).expect("a corpus directory").flatten() { + let p = e.path(); + if p.is_dir() { + walk(root, &p, out); + } else { + let rel = p.strip_prefix(root).unwrap().to_string_lossy().replace('\\', "/"); + out.insert(rel, std::fs::read(&p).unwrap()); + } + } +} + +fn committed() -> BTreeMap> { + let root = wai_dir().join("format-probe-conformance"); + let mut out = BTreeMap::new(); + for sub in ["ext", "gen"] { + walk(&root, &root.join(sub), &mut out); + } + out.insert("vectors.json".into(), std::fs::read(root.join("vectors.json")).expect("vectors.json")); + out +} + +#[test] +fn the_corpus_regenerates_and_every_outcome_holds() { + let files = committed(); + assert!(files.keys().filter(|k| k.starts_with("ext/")).count() >= 9, "the external vectors"); + let fails = corpus::verify(&files); + assert!(fails.is_empty(), "{}: run `wai_format_probe gen`", fails.join("\n")); +} + +fn section(text: &str, heading: &str) -> String { + let at = text.find(&format!("\n{heading}")).unwrap_or_else(|| panic!("{heading}")); + let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); + text[at..end].to_string() +} + +/// §2.4 lists exactly the probes and bases of `PROBES`, in order; §12 exactly the codes of +/// `Refusal::ALL`, in order. +#[test] +fn the_extension_lists_the_probes_and_the_codes() { + let text = std::fs::read_to_string(wai_dir().join("extensions/format-probes.md")).expect("the extension"); + let rows: Vec<(String, String)> = section(&text, "## 2. Common rules") + .lines() + .filter(|l| l.starts_with("| `wai.")) + .map(|l| { + let cells: Vec<&str> = l.split('|').map(str::trim).collect(); + (cells[1].trim_matches('`').to_string(), cells[2].to_string()) + }) + .collect(); + let want: Vec<(String, String)> = PROBES.iter().map(|(c, b)| (c.to_string(), b.label().to_string())).collect(); + assert_eq!(rows, want, "§2.4's table and PROBES differ"); + let codes: Vec = section(&text, "## 12. Refusals") + .lines() + .filter(|l| l.starts_with("| `")) + .filter_map(|l| l.split('`').nth(1).map(String::from)) + .collect(); + let want: Vec = Refusal::ALL.iter().map(|r| r.code().to_string()).collect(); + assert_eq!(codes, want, "§12's table and Refusal::ALL differ"); + // projection.md states the codes its reader and its binding check give. + let proj = std::fs::read_to_string(wai_dir().join("extensions/projection.md")).expect("projection.md"); + let checks = section(&proj, "## 4. Checks"); + for r in [Refusal::NoSignature, Refusal::LengthMismatch, Refusal::ReservedValue, Refusal::DescriptorInvalid] { + assert!(checks.contains(&format!("`{}`", r.code())), "projection.md §4 does not state {}", r.code()); + } + assert!(section(&proj, "## 5. Binding to the rendition").contains("`layer_absent`")); +} -- 2.54.0 (Apple Git-157) From f2a44c65708f19f1df230d50e8f44e63afda8331 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:55:38 -0400 Subject: [PATCH 013/242] wai: register wai.image.jxs, wai.video.jxs, wai.video.mvhevc, wai.meta.projection and the candidate wai.image.ieee1857_11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JPEG XS (image and video, StandardDefined on ISO/IEC 21122-4's strict conformance point, which the registration requires of a sink), MV-HEVC (StandardDefined on H.265 (V11) §F.13.5.1), the WPJ1 projection descriptor (a companion, as the HDR descriptors are) and IEEE 1857.11 (a candidate: its text is not openly readable and its reference software writes no start code). SPEC §5 and §7, the registry export, the glossary links and the research registry. Co-Authored-By: Claude Opus 5.5 --- wai/REGISTRY.md | 5 ++ wai/SPEC.md | 61 ++++++++++++++---- wai/glossary/codecs.json | 43 ++++++++++++- wai/registry/capabilities.json | 70 ++++++++++++++++++++ wai/research/media-codec-registry.md | 46 ++++++++++++- wai/vocab/v2.shapes.ttl | 14 ++++ wai/vocab/v2.ttl | 61 ++++++++++++++++++ wai/wai-rs/src/codecs/glossary_check.rs | 5 ++ wai/wai-rs/src/codecs/mod.rs | 85 +++++++++++++++++++++++-- wai/wai-rs/src/codecs/registry.rs | 9 ++- 10 files changed, 379 insertions(+), 20 deletions(-) diff --git a/wai/REGISTRY.md b/wai/REGISTRY.md index 5cf04f36e..203bc2c04 100644 --- a/wai/REGISTRY.md +++ b/wai/REGISTRY.md @@ -195,11 +195,13 @@ and a role are fixed once registered. | `wai.haptic.mihs` | payload | registered | haptic | standalone | patent-pool | standard-unclassified | — | §5 Immersive audio, cinema, volumetric, haptics, HDR | | `wai.haptic.signal` | payload | registered | haptic | standalone | royalty-free | standard-defined | — | §5 Interactive, temporal & computed content | | `wai.image.avif` | payload | registered | image | standalone | royalty-free | standard-tolerance | recommended | §5 Recommended modern set | +| `wai.image.ieee1857_11` | payload | candidate | image | standalone | patent-pool | neural-float | — | §5 Neural capabilities | | `wai.image.int_refine` | refinement | registered | image | refinement | royalty-free | standard-defined | — | §5 Refinement capabilities (staged delivery) | | `wai.image.jp2` | payload | registered | image | standalone | royalty-free | standard-tolerance | — | §5 Immersive audio, cinema, volumetric, haptics, HDR | | `wai.image.jpeg` | payload | registered | image | standalone | royalty-free | standard-tolerance | recommended | §5 Recommended modern set | | `wai.image.jpegai` | payload | registered | image | standalone | royalty-free | neural-entropy-exact | — | §5 Neural capabilities | | `wai.image.jxl` | payload | registered | image | standalone | royalty-free | standard-tolerance | recommended | §5 Recommended modern set | +| `wai.image.jxs` | payload | registered | image | standalone | patent-pool | standard-defined | — | §5 Immersive audio, cinema, volumetric, haptics, HDR | | `wai.image.png` | payload | registered | image | standalone | royalty-free | standard-defined | floor | §5 Mandatory floor | | `wai.live.ad_presentation` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content | | `wai.live.ads` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content | @@ -225,6 +227,7 @@ and a role are fixed once registered. | `wai.mesh.glb` | payload | registered | volumetric | standalone | royalty-free | standard-unclassified | — | §5 Interactive, temporal & computed content | | `wai.meta.dovi` | companion | registered | metadata | companion | proprietary | standard-unclassified | — | §5 Immersive audio, cinema, volumetric, haptics, HDR | | `wai.meta.hdr10plus` | companion | registered | metadata | companion | royalty-free | standard-unclassified | — | §5 Immersive audio, cinema, volumetric, haptics, HDR | +| `wai.meta.projection` | companion | registered | metadata | companion | royalty-free | standard-unclassified | — | §5 Immersive audio, cinema, volumetric, haptics, HDR | | `wai.neural.bmshj2018` | payload | registered | image | standalone | royalty-free | neural-float | — | §5 Neural capabilities | | `wai.neural.cmic` | payload | registered | image | standalone | royalty-free | neural-float | — | §5 Neural capabilities | | `wai.neural.dac` | payload | registered | audio | standalone | royalty-free | neural-float | — | §5 Neural capabilities | @@ -276,9 +279,11 @@ and a role are fixed once registered. | `wai.video.int_motion` | payload | registered | video | standalone | royalty-free | standard-defined | — | §5 Neural capabilities | | `wai.video.int_refine` | refinement | registered | video | refinement | royalty-free | standard-defined | — | §5 Refinement capabilities (staged delivery) | | `wai.video.j2k` | payload | registered | video | standalone | royalty-free | standard-tolerance | — | §5 Immersive audio, cinema, volumetric, haptics, HDR | +| `wai.video.jxs` | payload | registered | video | standalone | patent-pool | standard-defined | — | §5 Immersive audio, cinema, volumetric, haptics, HDR | | `wai.video.keys` | record | registered | — | — | — | — | — | §5 Neural capabilities | | `wai.video.lcevc` | companion | registered | video | enhancement | patent-pool | standard-unclassified | — | §5 Broadcast and streaming codecs | | `wai.video.manifest` | record | registered | — | — | — | — | — | §5 Neural capabilities | +| `wai.video.mvhevc` | payload | registered | video | standalone | patent-pool | standard-defined | — | §5 Broadcast and streaming codecs | | `wai.video.package` | record | registered | — | — | — | — | — | §5 Neural capabilities | | `wai.video.ssai` | record | registered | — | — | — | — | — | §5 Neural capabilities | | `wai.video.vp8` | payload | registered | video | standalone | royalty-free | standard-unclassified | — | §5 Broadcast and streaming codecs | diff --git a/wai/SPEC.md b/wai/SPEC.md index 18d124311..0e27a097e 100644 --- a/wai/SPEC.md +++ b/wai/SPEC.md @@ -712,6 +712,7 @@ registration and makes no conforming envelope non-conforming (§8). | `wai.neural.mlicv2` | MLICv2 / MLICv2+ (2025), MetaFormer transform | shares the mlicpp route | research code, `neural/mlicpp.rs` | | `wai.neural.cmic` | Content-Adaptive Mamba (ICLR 2026), SSM transform + SCCTX entropy | shares the mlicpp route | arXiv 2508.02192, `neural/mlicpp.rs` | | `wai.neural.glc` | Generative Latent Coding — categorical / VQ entropy | ultra-low-bitrate image coding | research code, `neural/glc.rs` | +| `wai.image.ieee1857_11` | IEEE 1857.11-2024 neural-network-based image coding — a **candidate** (*Candidate capabilities*, below): the standard is not openly readable, and its open reference software writes a picture header with no start code, from a working draft, so no payload format is pinned | `NeuralFloat`: no integer decode is shown, and the open text that states what its decoders must reproduce asks for a cross-check by re-running a decoder, not identical output | `format_probe::nic` reads the reference software's picture header ([format-probes](extensions/format-probes.md) §10); no decoder | | `wai.semantic.clip` | a CLIP-embedding package regenerated by any conforming sink generator (the generative-semantic path, `semantic.rs`) | conforms by similarity above a declared threshold, never by bytes (`Behavioral`, tier `Perceptual`); generators are sink-supplied | `semantic.rs` | | `wai.semantic.perco2` | PerCoV2 ultra-low-bitrate generative image coding | as `wai.semantic.clip`; its model licence is non-commercial (CC BY-NC-SA), so it is licence-flagged and never a default | `semantic.rs` | @@ -1526,9 +1527,11 @@ named standard's canonical bytes. |---|---| | immersive audio | `wai.audio.iamf` (IAMF, royalty-free) · `wai.audio.ac4` (AC-4, ETSI TS 103 190, proprietary) · `wai.audio.ivas` (3GPP IVAS, SEP-pool) · `wai.audio.mpegh` (MPEG-H 3D Audio, SEP-pool) · `wai.audio.avs3` (AVS3-P3 / Audio Vivid, SEP-pool) — the last two with the broadcast codecs below | | cinema mastering | `wai.image.jp2` · `wai.video.j2k` (JPEG 2000 / DCP, OpenJPEG) | +| mezzanine and contribution | `wai.image.jxs` · `wai.video.jxs` (JPEG XS, ISO/IEC 21122; SEP-pool; *JPEG XS*, below) | | volumetric / 3D | `wai.mesh.draco` (royalty-free) · `wai.pc.gpcc` (G-PCC, ISO/IEC 23090-9) · `wai.pc.vpcc` (V-PCC, ISO/IEC 23090-5) — MPEG point clouds · `wai.splat.sog` (Gaussian-splat compression; a **candidate**, no payload format pinned — *Candidate capabilities*, below) | | haptics | `wai.haptic.mihs` (MPEG-I Haptics, ISO/IEC 23090-31; its 23090-33 ships bit-exact conformance vectors) | | HDR dynamic metadata | `wai.meta.hdr10plus` (SMPTE ST 2094-40 dynamic metadata, royalty-free) · `wai.meta.dovi` (SMPTE ST 2094-10 dynamic metadata, proprietary) — **companion descriptors** to a video payload, not standalone media | +| projection | `wai.meta.projection` (the `WPJ1` projection descriptor, [projection](extensions/projection.md), royalty-free) — a **companion descriptor** to a video payload: rectilinear, equirectangular, half-equirectangular or fisheye pictures, one view or two views packed or in layers | **Point clouds: what the two registrations name.** `wai.pc.gpcc` names G-PCC, ISO/IEC 23090-9, and `wai.pc.vpcc` names V-PCC, ISO/IEC 23090-5; each payload is @@ -1549,11 +1552,26 @@ capability string of its own (§8). Whether V-DMC, which the fourth edition of ISO/IEC 23090-5 makes a V3C application, is admitted turns on the unresolved edition. +**JPEG XS.** A `wai.image.jxs` payload is one JPEG XS codestream (ISO/IEC 21122-1), `SOC` +to `EOC`. A `wai.video.jxs` payload is five `u32` little-endian values, `n`, `h`, `w`, +`fps_num` and `fps_den`, then `n` codestreams, each preceded by its length as a `u32` +little-endian value: the framing of `wai.video.av1` (*Payload framing*, above), with a +codestream for a temporal unit. A sink that advertises either decodes at the strict +conformance point of ISO/IEC 21122-4 (Annex B.2: decoded images bitwise identical to the +reference images), not only at its relaxed point, so the class of both is +`StandardDefined`; the measurements behind it, and RFC 9134's and SMPTE ST 2110-22's +carriage of the payload, are [format-probes](extensions/format-probes.md) §3.5–§3.6. Each is +tiered `Lossy` (its role is mezzanine compression; its lossless mode is signalled in the +codestream, as for AVIF). Its essential patents are licensed through a pool, so its licence +class is `patent-pool`. + **Candidate capabilities.** A *candidate* is a registered capability string whose payload format no revision has pinned. The candidates are `wai.tensor.nnc` (general-tensor coding is in an unpublished NNC amendment; *Prior artifacts* -above) and `wai.splat.sog` (registered for a family of Gaussian-splat formats; no -specification of the format is cited). A conforming encoder's payload is the +above), `wai.splat.sog` (registered for a family of Gaussian-splat formats; no +specification of the format is cited) and `wai.image.ieee1857_11` (IEEE 1857.11-2024 +is not openly readable, and its open reference software writes a header with no start +code from a working draft; *Neural capabilities*, above). A conforming encoder's payload is the canonical byte stream of its capability (§7), and a candidate has none. So, until a revision pins a candidate's payload format: @@ -1638,6 +1656,7 @@ re-frame it. | `wai.video.av2` | AV2 (AOMedia) — AV2 Bitstream & Decoding Process Specification v1.0.0, 28 May 2026 | the length-delimited bitstream of that specification's Annex B (each OBU preceded by its `leb128` byte length) | royalty-free | | `wai.video.vvc` | VVC — ITU-T H.266 \| ISO/IEC 23090-3 | H.266 Annex B byte stream | SEP-pool | | `wai.video.hevc` | HEVC — ITU-T H.265 \| ISO/IEC 23008-2 | H.265 Annex B byte stream | SEP-pool | +| `wai.video.mvhevc` | MV-HEVC — ITU-T H.265 \| ISO/IEC 23008-2, Annexes F and G | the H.265 Annex B byte stream of a multiview bitstream: a Multiview Main or Multiview Main 10 layer over a Main or Main 10 base (*Multiview video*, below) | SEP-pool | | `wai.video.avc` | AVC — ITU-T H.264 \| ISO/IEC 14496-10 | H.264 Annex B byte stream | SEP-pool | | `wai.video.vp9` | VP9 Bitstream & Decoding Process Specification | VP9 frames in the IVF frame container (FourCC `VP90`) | royalty-free | | `wai.video.vp8` | VP8 — RFC 6386 | VP8 frames in the IVF frame container (FourCC `VP80`): a 32-byte file header, then a 12-byte header before each frame, as for `wai.video.vp9` | royalty-free | @@ -1658,10 +1677,11 @@ below. Each is tiered `Lossy` (its distribution role; the lossless modes some of them have are signalled in the bitstream, as for AVIF). No model WAI runs is involved in any of them. For replication (§7) they fall in three classes: -- **`StandardDefined`** — AVC, HEVC, VVC and VP9. Each standard's decoder +- **`StandardDefined`** — AVC, HEVC, MV-HEVC, VVC and VP9. Each standard's decoder conformance requires every sample a conforming decoder outputs to equal the - value its decoding process produces (H.264 §C.4; H.265 and H.266 §C.5.1; VP9 - §8.1), so every conforming decoder outputs the same cropped decoded pictures. + value its decoding process produces (H.264 §C.4; H.265 and H.266 §C.5.1, and for + every output layer of a multilayer bitstream H.265 §F.13.5.1; VP9 §8.1), so every + conforming decoder outputs the same cropped decoded pictures. The guarantee stops at those pictures: film-grain synthesis an SEI message signals, colour conversion and other post-decoding processing are outside it. - **`StandardTolerance`** — AV2 and the Opus packet. As for AV1, AV2's output is @@ -1681,6 +1701,22 @@ is involved in any of them. For replication (§7) they fall in three classes: The clauses behind each class, what was not read, and the cross-decoder measurements are in the research registry. +**Multiview video.** A `wai.video.mvhevc` payload is an H.265 Annex B byte stream whose VPS +extension signals multiview scalability with at least two views, and which holds a layer +above the base (ITU-T H.265 (V11) Annexes F and G). A sink that advertises it outputs every +output layer of the target output layer set. Its base layer is an HEVC bitstream, so +`wai.video.hevc` is a usable fallback (§4, step 5): a sink without multiview decode presents +the base view. The views' arrangement and the viewer geometry travel in a projection +descriptor (`wai.meta.projection`, *Immersive audio, cinema, volumetric, haptics, HDR*, +above). + +**Header probes.** [format-probes](extensions/format-probes.md) defines a header probe for +`wai.video.vvc`, `wai.video.hevc`, `wai.video.mvhevc`, `wai.video.lcevc`, `wai.audio.mpegh`, +`wai.video.avs3` and `wai.audio.avs3` (and for `wai.image.jxs`, `wai.video.jxs`, +`wai.image.ieee1857_11` and `wai.meta.projection`): it identifies a payload as the format its +capability names and reads the parameters its headers state, or refuses it with a code. A +probe decodes no sample, so it does not change any class above. + **One Opus packet.** A `wai.audio.opus.packet` payload is one Opus packet (RFC 6716 §3), its TOC byte first, with no container: the per-packet form streaming carries, where a live track sends one packet per object. A sink decodes it with @@ -1761,11 +1797,12 @@ unit stream without start code prefixes (for example with each NAL unit preceded by its length) is not a `wai.video.lcevc` payload; carrying that form would need its own capability string (§8). -**Companion-only capabilities.** `wai.video.lcevc` and the HDR dynamic-metadata -descriptors `wai.meta.hdr10plus` and `wai.meta.dovi` are **companion-only**: -their payload means nothing without the base it accompanies. An HDR descriptor -applies to a rendition whose payload is video (§7.1, rule 2), `wai.video.lcevc` -to one rendition of one of its base codecs (above). Every other registered +**Companion-only capabilities.** `wai.video.lcevc`, the HDR dynamic-metadata +descriptors `wai.meta.hdr10plus` and `wai.meta.dovi` and the projection descriptor +`wai.meta.projection` are **companion-only**: their payload means nothing without the base +it accompanies. An HDR descriptor or a projection descriptor applies to a rendition whose +payload is video (§7.1, rule 2), `wai.video.lcevc` to one rendition of one of its base codecs +(above). Every other registered capability is **standalone media** and is never carried as a companion (§7.1). A companion-only capability MUST NOT be a v1.0 `capability` (a `WAI1` carries one payload, so @@ -2206,6 +2243,8 @@ cited are the ones read; the evidence, and what was not read, is in | | `wai.prior.set` | its bytes exactly: a listing (§3.1) is its own output | | | `wai.video.avc`, `wai.video.hevc`, `wai.video.vvc` | every sample of every cropped decoded picture a conforming decoder outputs equals the value the specified decoding process produces (ITU-T H.264 (V16, 06/2026) §C.4; H.265 (V11, 01/2026) §C.5.1; H.266 (V4, 01/2026) §C.5.1); processing an SEI message only signals, such as film grain synthesis, is outside that decoding process | | | `wai.video.vp9` | output frames "identical in all respects", in the same order, to those of the specified decoding process (VP9 Bitstream & Decoding Process Specification v0.6 §8.1) | +| | `wai.video.mvhevc` | every sample of every cropped decoded picture of every output layer equals the value the specified decoding process produces (ITU-T H.265 (V11, 01/2026) §F.13.5.1) | +| | `wai.image.jxs`, `wai.video.jxs` | decoded images bitwise identical to the reference images, at the strict conformance point (ISO/IEC 21122-4:2022 §6.3 and Annex B.2, read in its public preview), which the registration requires of a sink; its relaxed point, held to PSNR bounds, is not enough ([format-probes](extensions/format-probes.md) §3.6) | | `StandardTolerance` | `wai.audio.opus`, `wai.audio.opus.packet` | output within `opus_compare`'s thresholds of the reference decoder, with the same final range-decoder state (RFC 6716 §6) | | | `wai.image.jpeg` | decoding "with appropriate accuracy", the accuracy of dequantisation and IDCT specified in Part 2 (ITU-T T.81 §7, A.3.3); Part 2 (ITU-T T.83) was not read, so the bound is not stated here | | | `wai.image.jxl` | decoded samples within per-test peak-error and RMSE thresholds of reference images (ISO/IEC 18181-3 §4.1–§4.3) | @@ -2214,7 +2253,7 @@ cited are the ones read; the evidence, and what was not read, is in | | `wai.video.av2` | as AV1: identical output unless film grain is signalled, then one of the same two options (AV2 v1.0.0 §7.1) | | | `wai.audio.ivas` | output bit-exact to the test sequences, or, for an implementation based on the floating-point reference code whose output is not, a non-bit-exact procedure: TS 26.444's for mono operation, a per-frame loudness-difference corridor for stereo and immersive operation (3GPP TS 26.252 V19.2.0 §7.1, §7.2.1) | | | `wai.audio.iamf` | its substreams include Opus (above), and renderers are out of its scope | -| `StandardUnclassified` | `wai.video.vp8`, `wai.video.evc`, `wai.video.evc.baseline`, `wai.video.avs3`, `wai.video.lcevc`, `wai.audio.mpegh`, `wai.audio.avs3`, `wai.audio.ac4`, `wai.mesh.draco`, `wai.pc.gpcc`, `wai.pc.vpcc`, `wai.splat.sog`, `wai.haptic.mihs`, `wai.meta.hdr10plus`, `wai.meta.dovi`, `wai.prior.nnc`, `wai.tensor.nnc` | not checked in this revision (all sink-supplied; `wai.splat.sog` and `wai.tensor.nnc` are candidates, with no payload format pinned — §5) | +| `StandardUnclassified` | `wai.video.vp8`, `wai.video.evc`, `wai.video.evc.baseline`, `wai.video.avs3`, `wai.video.lcevc`, `wai.audio.mpegh`, `wai.audio.avs3`, `wai.audio.ac4`, `wai.mesh.draco`, `wai.pc.gpcc`, `wai.pc.vpcc`, `wai.splat.sog`, `wai.haptic.mihs`, `wai.meta.hdr10plus`, `wai.meta.dovi`, `wai.meta.projection`, `wai.prior.nnc`, `wai.tensor.nnc` | not checked in this revision (all sink-supplied; `wai.splat.sog` and `wai.tensor.nnc` are candidates, with no payload format pinned — §5) | | | `wai.audio.aac` | not checked in this revision; three decoders on one host gave three different PCM streams from one ADTS stream, so it is not placed where byte identity would be promised | | | `wai.mesh.glb`, `wai.splat.spz` | the payload bytes pass through intact; what a sink decodes from them is not classified here (a glTF binary may carry JPEG images in its buffer and compressed geometry through extensions, and glTF 2.0 specifies an asset format, not a decoder conformance over what is rendered; SPZ stores quantised attributes) | diff --git a/wai/glossary/codecs.json b/wai/glossary/codecs.json index 45261294b..18f787072 100644 --- a/wai/glossary/codecs.json +++ b/wai/glossary/codecs.json @@ -204,7 +204,14 @@ "era": "classical", "status": "niche", "lineage": "standards", - "desc": "ISO/IEC 21122. Low-complexity, low-latency intra-frame coding for professional video over IP and other uses where latency matters more than compression ratio. Its RTP payload format is RFC 9134." + "desc": "ISO/IEC 21122. Low-complexity, low-latency intra-frame coding for professional video over IP and other uses where latency matters more than compression ratio. Its RTP payload format is RFC 9134.", + "wai": [ + { + "capability": "wai.image.jxs", + "licence": "patent-pool", + "replicate": "standard-defined" + } + ] }, { "name": "JPEG XT", @@ -708,6 +715,23 @@ } ] }, + { + "name": "MV-HEVC", + "year": 2014, + "body": "ITU-T and ISO/IEC JTC 1/SC 29 (MPEG)", + "modality": "video", + "era": "classical", + "status": "niche", + "lineage": "standards", + "desc": "The multiview extension of HEVC, ITU-T H.265 | ISO/IEC 23008-2 Annexes F and G: two or more views coded as layers of one bitstream, each view predicted from the views below it, the base layer an ordinary HEVC bitstream. Its profiles are Multiview Main and Multiview Main 10.", + "wai": [ + { + "capability": "wai.video.mvhevc", + "licence": "patent-pool", + "replicate": "standard-defined" + } + ] + }, { "name": "VVC / H.266", "year": 2020, @@ -1371,6 +1395,23 @@ } ] }, + { + "name": "JPEG XS video (RFC 9134)", + "year": 2021, + "body": "ISO/IEC JTC 1/SC 29 (JPEG)", + "modality": "video", + "era": "classical", + "status": "niche", + "lineage": "standards", + "desc": "A sequence of JPEG XS codestreams, one a frame, as RFC 9134 carries them in RTP and SMPTE ST 2110-22 carries constant-bit-rate compressed video for live production over IP.", + "wai": [ + { + "capability": "wai.video.jxs", + "licence": "patent-pool", + "replicate": "standard-defined" + } + ] + }, { "name": "VC-5 (CineForm)", "year": 2003, diff --git a/wai/registry/capabilities.json b/wai/registry/capabilities.json index a16da3ba0..de2c469c3 100644 --- a/wai/registry/capabilities.json +++ b/wai/registry/capabilities.json @@ -576,6 +576,20 @@ "edition": null, "registered": "2026-06-12" }, + { + "capability": "wai.image.ieee1857_11", + "kind": "payload", + "status": "candidate", + "media": "image", + "role": "standalone", + "tier": "lossy", + "licence": "patent-pool", + "replicate": "neural-float", + "sets": [], + "spec_section": "§5 Neural capabilities", + "edition": "IEEE 1857.11-2024", + "registered": "2026-10-22" + }, { "capability": "wai.image.int_refine", "kind": "refinement", @@ -670,6 +684,20 @@ "edition": null, "registered": "2026-06-12" }, + { + "capability": "wai.image.jxs", + "kind": "payload", + "status": "registered", + "media": "image", + "role": "standalone", + "tier": "lossy", + "licence": "patent-pool", + "replicate": "standard-defined", + "sets": [], + "spec_section": "§5 Immersive audio, cinema, volumetric, haptics, HDR", + "edition": null, + "registered": "2026-10-22" + }, { "capability": "wai.image.png", "kind": "payload", @@ -1022,6 +1050,20 @@ "edition": null, "registered": "2026-06-17" }, + { + "capability": "wai.meta.projection", + "kind": "companion", + "status": "registered", + "media": "metadata", + "role": "companion", + "tier": "lossless", + "licence": "royalty-free", + "replicate": "standard-unclassified", + "sets": [], + "spec_section": "§5 Immersive audio, cinema, volumetric, haptics, HDR", + "edition": null, + "registered": "2026-10-22" + }, { "capability": "wai.neural.bmshj2018", "kind": "payload", @@ -1835,6 +1877,20 @@ "edition": null, "registered": "2026-06-17" }, + { + "capability": "wai.video.jxs", + "kind": "payload", + "status": "registered", + "media": "video", + "role": "standalone", + "tier": "lossy", + "licence": "patent-pool", + "replicate": "standard-defined", + "sets": [], + "spec_section": "§5 Immersive audio, cinema, volumetric, haptics, HDR", + "edition": null, + "registered": "2026-10-22" + }, { "capability": "wai.video.keys", "kind": "record", @@ -1877,6 +1933,20 @@ "edition": null, "registered": "2026-09-03" }, + { + "capability": "wai.video.mvhevc", + "kind": "payload", + "status": "registered", + "media": "video", + "role": "standalone", + "tier": "lossy", + "licence": "patent-pool", + "replicate": "standard-defined", + "sets": [], + "spec_section": "§5 Broadcast and streaming codecs", + "edition": null, + "registered": "2026-10-22" + }, { "capability": "wai.video.package", "kind": "record", diff --git a/wai/research/media-codec-registry.md b/wai/research/media-codec-registry.md index 30a35f3cf..80248f8e2 100644 --- a/wai/research/media-codec-registry.md +++ b/wai/research/media-codec-registry.md @@ -148,8 +148,15 @@ alongside EVS) — **not** royalty-free. |---|---|---|---|---| | `wai.image.jp2` | JPEG 2000 still (ISO/IEC 15444-1; DCP via SMPTE ST 429 / MXF) | RF core³ | OpenJPEG (BSD-2) | Lossless | | `wai.video.j2k` | Intra-frame JPEG 2000 video (each frame an independent codestream) | RF core³ | OpenJPEG | Lossless | +| `wai.image.jxs` | JPEG XS (ISO/IEC 21122), one codestream | **POOL**¹â�¶ | an open encoder and decoder (BSD-2-Clause-Patent); a second open decoder (MIT) | Lossy | +| `wai.video.jxs` | JPEG XS codestreams, one a frame, in `wai.video.av1`'s framing (RTP: RFC 9134; SMPTE ST 2110-22) | **POOL**¹â�¶ | as above | Lossy | -The theatrical interchange path: a DCP is JPEG 2000 in MXF. The proprietary +The theatrical interchange path: a DCP is JPEG 2000 in MXF. JPEG XS is the +low-latency contribution path of live production over IP (registered 2026-10-22); +its decoders conform at a strict point (bitwise identical output) or a relaxed one +(PSNR bounds), ISO/IEC 21122-4:2022 §6.3 and Annex B.2, and WAI's registration +requires the strict one ([format-probes](../extensions/format-probes.md) §3.6, where the +cross-decoder measurement is). The proprietary production mezzanine formats are out of scope for a royalty-free floor (a licence-flagged slot could be added if a workflow needs one). @@ -266,6 +273,31 @@ characteristics already in-band in the video bitstream and need only a manifest color-descriptor field, not a separate capability. **This is the one place to resolve in SPEC before an integration partner's engineers ask.** +## Projection descriptor (a companion descriptor, registered 2026-10-22) + +| capability | standard | royalty | tier | +|---|---|---|---| +| `wai.meta.projection` | WAI's `WPJ1` record ([projection](../extensions/projection.md)): projection kinds of ISO/IEC 23090-2 as ITU-T H.274 §8.15's SEI messages state them, packing arrangements of ITU-T H.273 §8.4 | **RF** (WAI-native) | Lossless | + +A companion descriptor as the HDR descriptors are: it says how a video rendition's +pictures map onto what a viewer sees and how its two views are arranged, and is +applied by a renderer after decoding. ISO/IEC 23090-2 is not openly readable and was +not read. + +## Neural image coding standards + +| capability | standard | royalty | status | +|---|---|---|---| +| `wai.image.ieee1857_11` | IEEE 1857.11-2024, neural-network-based image coding | **POOL**, provisional¹â�· | **candidate**: no payload format pinned | + +IEEE 1857.11-2024 (published 2024-12-20) is not openly readable. Its open reference +software (BSD-2-Clause, copyright of the working group) writes a picture header with no +start code, from a working draft, and marks the code that would write one as not yet +written; [format-probes](../extensions/format-probes.md) §10 reads that header. The +open call for proposals asked for a reproducibility check by re-running a proponent's +decoder, not for identical output across machines, and the software's quantised layers +fall back to float arithmetic in places, so the registry class is `NeuralFloat`. + ## Decoder conformance — what each named standard fixes SPEC §7 classes every standard codec — the floor and modern menu, the broadcast @@ -285,6 +317,8 @@ a standard whose conformance text was not read stays `StandardUnclassified`. | `wai.text.zstd`, `wai.text.xz` | `StandardDefined` | lossless byte formats | not re-read | | `wai.video.avc` | `StandardDefined` | §C.4: test bitstreams go to the HRD and the decoder under test; "for each picture output by the HRD, the values of all samples that are output by the DUT for the corresponding picture shall be equal to the values of the samples output by the HRD". Film grain characteristics are an SEI message, outside the decoding process | Rec. ITU-T H.264 (V16) (06/2026), §C.4 | | `wai.video.hevc`, `wai.video.vvc` | `StandardDefined` | §C.5.1: "for each such cropped decoded picture output by the DUT, the values of all samples that are output shall be equal to the values of the samples produced by the specified decoding process". Film grain characteristics (and, in H.266, other post-processing hints) are SEI messages, outside the decoding process | Rec. ITU-T H.265 (V11) (01/2026) and H.266 (V4) (01/2026), §C.5.1 | +| `wai.video.mvhevc` | `StandardDefined` | §F.13.5.1: as §C.5.1, for every output layer: "for each such cropped decoded picture output by the DUT, the values of all samples that are output shall be equal to the values of the samples produced by the specified decoding process" | Rec. ITU-T H.265 (V11) (01/2026), §F.13.5.1 | +| `wai.image.jxs`, `wai.video.jxs` | `StandardDefined` | two conformance points: "a strict conformance point that requires decoders to reconstruct codestreams into image data that is bitwise identical to the reference data, and a relaxed conformance point" (§6.3); the strict point when the PSNR difference is infinity for every codestream (Annex B.2). The registration requires the strict point of a sink, so its class rests on that clause and that requirement | ISO/IEC 21122-4:2022 public preview (foreword, §6.3, Annex B.2); Annex C's PSNR bounds are not in the preview | | `wai.video.vp9` | `StandardDefined` | §8.1: "Decoders shall produce output frames that are identical in all respects and have the same output order as those produced by the decoding process specified herein" | VP9 Bitstream & Decoding Process Specification v0.6 (31 March 2016), §8.1 | | `wai.audio.opus`, `wai.audio.opus.packet` | `StandardTolerance` | §6: conformance is "defined through the reference implementation"; a decoder's output "MUST also be within the thresholds specified by the opus_compare.c tool" against the reference on the test vectors, with "the same final range decoder state". §6.1: quality 0 is the passing threshold, calibrated to additive white noise at 48 dB SNR; 100 means identical to the reference. §1.1: "Various operations in the codec require bit-exact fixed-point behavior"; §4.2.7.9: the rest of SILK frame reconstruction "does not need to be bit-exact" | RFC 6716 §1.1, §4.2.7.9, §6–§6.1 | | `wai.image.jpeg` | `StandardTolerance` | §7: a decoder shall "with appropriate accuracy, convert to reconstructed image data"; A.3.3 NOTE: the IDCT equations "contain terms which cannot be represented with perfect accuracy by any real implementation", and the accuracy of dequantisation + IDCT is specified in Part 2; §4.1: colour space "is outside the scope of this Specification". The lossless process (Annex H) is exact, but the capability's role is the DCT processes | ITU-T T.81 (1992), full text. Part 2 (ITU-T T.83 \| ISO/IEC 10918-2) not read: the download failed | @@ -367,6 +401,7 @@ distribution chain already holds; all sink-supplied, none a default. | `wai.video.av2` | AV2 (AOMedia), spec v1.0.0, 28 May 2026 | AV2 Annex B length-delimited bitstream (`leb128` size before each OBU) | **RF**â�¶ | AVM v1.0.0 | Lossy | | `wai.video.vvc` | ITU-T H.266 \| ISO/IEC 23090-3 | H.266 Annex B byte stream | **POOL**â�· | VTM | Lossy | | `wai.video.hevc` | ITU-T H.265 \| ISO/IEC 23008-2 | H.265 Annex B byte stream | **POOL**â�· | HM | Lossy | +| `wai.video.mvhevc` | ITU-T H.265 \| ISO/IEC 23008-2, Annexes F and G (registered 2026-10-22) | H.265 Annex B byte stream of a multiview bitstream | **POOL**â�· | HTM (BSD-3-Clause) | Lossy | | `wai.video.avc` | ITU-T H.264 \| ISO/IEC 14496-10 | H.264 Annex B byte stream | **POOL**â�· | JM | Lossy | | `wai.video.vp9` | VP9 Bitstream & Decoding Process Specification | IVF frame container, FourCC `VP90` | **RF**â�¸ | libvpx | Lossy | | `wai.video.vp8` | RFC 6386 | IVF frame container, FourCC `VP80` | **RF**¹â�µ | libvpx | Lossy | @@ -559,6 +594,15 @@ third-party pool asserts VP8 patents, as one does VP9's (â�¸), was not checked. `wai.audio.opus.packet`: Opus, classified royalty-free as `wai.audio.opus` is; the packet form adds no coding tool, only leaves out the Ogg container. +¹â�¶ **POOL, conservatively** (registered 2026-10-22). A licensing pool for JPEG XS +essential patents, on RAND terms, is reported by a secondary summary; its terms were not +read. The open encoder's licence grants a patent licence only for its contributors' own +claims. Classified `patent-pool`, never royalty-free, until a primary source says otherwise. + +¹â�· **Provisional** (registered 2026-10-22). The reference software's licence notes that +patent rights may apply; no licensing statement for the standard was read. A candidate's +class is provisional in any case (SPEC §5). + ## Implementation & demonstration status WAI's job is **carriage + dispatch**; decoders are sink-supplied (even the diff --git a/wai/vocab/v2.shapes.ttl b/wai/vocab/v2.shapes.ttl index 858902707..dca15d691 100644 --- a/wai/vocab/v2.shapes.ttl +++ b/wai/vocab/v2.shapes.ttl @@ -43,11 +43,13 @@ wai:ManifestShape a sh:NodeShape ; wai:wai.image.jpeg wai:wai.image.jpegai wai:wai.image.jxl + wai:wai.image.jxs wai:wai.image.png wai:wai.mesh.draco wai:wai.mesh.glb wai:wai.meta.dovi wai:wai.meta.hdr10plus + wai:wai.meta.projection wai:wai.neural.bmshj2018 wai:wai.neural.cmic wai:wai.neural.dac @@ -93,7 +95,9 @@ wai:ManifestShape a sh:NodeShape ; wai:wai.video.int_motion wai:wai.video.int_refine wai:wai.video.j2k + wai:wai.video.jxs wai:wai.video.lcevc + wai:wai.video.mvhevc wai:wai.video.vp8 wai:wai.video.vp9 wai:wai.video.vvc @@ -128,6 +132,7 @@ wai:ManifestShape a sh:NodeShape ; wai:wai.image.jpeg wai:wai.image.jpegai wai:wai.image.jxl + wai:wai.image.jxs wai:wai.image.png wai:wai.mesh.draco wai:wai.mesh.glb @@ -174,6 +179,8 @@ wai:ManifestShape a sh:NodeShape ; wai:wai.video.int_hyper wai:wai.video.int_motion wai:wai.video.j2k + wai:wai.video.jxs + wai:wai.video.mvhevc wai:wai.video.vp8 wai:wai.video.vp9 wai:wai.video.vvc @@ -223,11 +230,13 @@ wai:ComponentShape a sh:NodeShape ; wai:wai.image.jpeg wai:wai.image.jpegai wai:wai.image.jxl + wai:wai.image.jxs wai:wai.image.png wai:wai.mesh.draco wai:wai.mesh.glb wai:wai.meta.dovi wai:wai.meta.hdr10plus + wai:wai.meta.projection wai:wai.neural.bmshj2018 wai:wai.neural.cmic wai:wai.neural.dac @@ -273,7 +282,9 @@ wai:ComponentShape a sh:NodeShape ; wai:wai.video.int_motion wai:wai.video.int_refine wai:wai.video.j2k + wai:wai.video.jxs wai:wai.video.lcevc + wai:wai.video.mvhevc wai:wai.video.vp8 wai:wai.video.vp9 wai:wai.video.vvc @@ -308,6 +319,7 @@ wai:ComponentShape a sh:NodeShape ; wai:wai.image.jpeg wai:wai.image.jpegai wai:wai.image.jxl + wai:wai.image.jxs wai:wai.image.png wai:wai.mesh.draco wai:wai.mesh.glb @@ -354,6 +366,8 @@ wai:ComponentShape a sh:NodeShape ; wai:wai.video.int_hyper wai:wai.video.int_motion wai:wai.video.j2k + wai:wai.video.jxs + wai:wai.video.mvhevc wai:wai.video.vp8 wai:wai.video.vp9 wai:wai.video.vvc diff --git a/wai/vocab/v2.ttl b/wai/vocab/v2.ttl index 30762872b..18e0f3cbe 100644 --- a/wai/vocab/v2.ttl +++ b/wai/vocab/v2.ttl @@ -503,6 +503,19 @@ wai:wai.image.avif a wai:Capability ; wai:specSection "§5 Recommended modern set" ; wai:registeredOn "2026-06-12"^^xsd:date . +wai:wai.image.ieee1857_11 a wai:Capability ; + rdfs:label "wai.image.ieee1857_11" ; + wai:kind wai:kind-payload ; + wai:status wai:status-candidate ; + wai:media wai:media-image ; + wai:role wai:role-standalone ; + wai:tier wai:tier-lossy ; + wai:licence wai:licence-patent-pool ; + wai:replicateClass wai:replicate-neural-float ; + wai:edition "IEEE 1857.11-2024" ; + wai:specSection "§5 Neural capabilities" ; + wai:registeredOn "2026-10-22"^^xsd:date . + wai:wai.image.int_refine a wai:Capability ; rdfs:label "wai.image.int_refine" ; wai:kind wai:kind-refinement ; @@ -564,6 +577,18 @@ wai:wai.image.jxl a wai:Capability ; wai:specSection "§5 Recommended modern set" ; wai:registeredOn "2026-06-12"^^xsd:date . +wai:wai.image.jxs a wai:Capability ; + rdfs:label "wai.image.jxs" ; + wai:kind wai:kind-payload ; + wai:status wai:status-registered ; + wai:media wai:media-image ; + wai:role wai:role-standalone ; + wai:tier wai:tier-lossy ; + wai:licence wai:licence-patent-pool ; + wai:replicateClass wai:replicate-standard-defined ; + wai:specSection "§5 Immersive audio, cinema, volumetric, haptics, HDR" ; + wai:registeredOn "2026-10-22"^^xsd:date . + wai:wai.image.png a wai:Capability ; rdfs:label "wai.image.png" ; wai:kind wai:kind-payload ; @@ -764,6 +789,18 @@ wai:wai.meta.hdr10plus a wai:Capability ; wai:specSection "§5 Immersive audio, cinema, volumetric, haptics, HDR" ; wai:registeredOn "2026-06-17"^^xsd:date . +wai:wai.meta.projection a wai:Capability ; + rdfs:label "wai.meta.projection" ; + wai:kind wai:kind-companion ; + wai:status wai:status-registered ; + wai:media wai:media-metadata ; + wai:role wai:role-companion ; + wai:tier wai:tier-lossless ; + wai:licence wai:licence-royalty-free ; + wai:replicateClass wai:replicate-standard-unclassified ; + wai:specSection "§5 Immersive audio, cinema, volumetric, haptics, HDR" ; + wai:registeredOn "2026-10-22"^^xsd:date . + wai:wai.neural.bmshj2018 a wai:Capability ; rdfs:label "wai.neural.bmshj2018" ; wai:kind wai:kind-payload ; @@ -1359,6 +1396,18 @@ wai:wai.video.j2k a wai:Capability ; wai:specSection "§5 Immersive audio, cinema, volumetric, haptics, HDR" ; wai:registeredOn "2026-06-17"^^xsd:date . +wai:wai.video.jxs a wai:Capability ; + rdfs:label "wai.video.jxs" ; + wai:kind wai:kind-payload ; + wai:status wai:status-registered ; + wai:media wai:media-video ; + wai:role wai:role-standalone ; + wai:tier wai:tier-lossy ; + wai:licence wai:licence-patent-pool ; + wai:replicateClass wai:replicate-standard-defined ; + wai:specSection "§5 Immersive audio, cinema, volumetric, haptics, HDR" ; + wai:registeredOn "2026-10-22"^^xsd:date . + wai:wai.video.keys a wai:Capability ; rdfs:label "wai.video.keys" ; wai:kind wai:kind-record ; @@ -1385,6 +1434,18 @@ wai:wai.video.manifest a wai:Capability ; wai:specSection "§5 Neural capabilities" ; wai:registeredOn "2026-09-03"^^xsd:date . +wai:wai.video.mvhevc a wai:Capability ; + rdfs:label "wai.video.mvhevc" ; + wai:kind wai:kind-payload ; + wai:status wai:status-registered ; + wai:media wai:media-video ; + wai:role wai:role-standalone ; + wai:tier wai:tier-lossy ; + wai:licence wai:licence-patent-pool ; + wai:replicateClass wai:replicate-standard-defined ; + wai:specSection "§5 Broadcast and streaming codecs" ; + wai:registeredOn "2026-10-22"^^xsd:date . + wai:wai.video.package a wai:Capability ; rdfs:label "wai.video.package" ; wai:kind wai:kind-record ; diff --git a/wai/wai-rs/src/codecs/glossary_check.rs b/wai/wai-rs/src/codecs/glossary_check.rs index ed6b91a66..868179606 100644 --- a/wai/wai-rs/src/codecs/glossary_check.rs +++ b/wai/wai-rs/src/codecs/glossary_check.rs @@ -37,6 +37,7 @@ const GLOSSARY_LINKS: &[(&str, &str)] = &[ ("JPEG AI", CAP_IMAGE_JPEGAI), ("H.264 / AVC", CAP_VIDEO_AVC), ("HEVC / H.265", CAP_VIDEO_HEVC), + ("MV-HEVC", CAP_VIDEO_MVHEVC), ("VVC / H.266", CAP_VIDEO_VVC), ("LCEVC", CAP_VIDEO_LCEVC), ("EVC", CAP_VIDEO_EVC), @@ -48,6 +49,8 @@ const GLOSSARY_LINKS: &[(&str, &str)] = &[ ("AV1", CAP_VIDEO_AV1_LOSSLESS), ("AV2", CAP_VIDEO_AV2), ("JPEG 2000 (DCI cinema)", CAP_VIDEO_J2K), + ("JPEG XS", CAP_IMAGE_JXS), + ("JPEG XS video (RFC 9134)", CAP_VIDEO_JXS), ("DCVC-FM", CAP_NEURAL_DCVC_FM), ("AAC", CAP_AUDIO_AAC), ("HE-AAC", CAP_AUDIO_AAC), @@ -124,6 +127,8 @@ const GLOSSARY_NO_ENTRY: &[(&str, &str)] = &[ (CAP_PRIOR_SET, WAI_NATIVE), (CAP_TENSOR_NNC, "a candidate with no payload format pinned; neural-network coding codes tensors, not media"), (CAP_SPLAT_SOG, "a candidate with no payload format pinned"), + (CAP_IMAGE_IEEE1857_11, "a candidate with no payload format pinned"), + (CAP_META_PROJECTION, WAI_NATIVE), ]; /// Pairs SPEC §5 excludes from a capability: such an entry must not link it. diff --git a/wai/wai-rs/src/codecs/mod.rs b/wai/wai-rs/src/codecs/mod.rs index 52cd6ba1c..1e774e824 100644 --- a/wai/wai-rs/src/codecs/mod.rs +++ b/wai/wai-rs/src/codecs/mod.rs @@ -41,6 +41,12 @@ pub const CAP_IMAGE_JPEG: &str = "wai.image.jpeg"; // most-supported lossy pub const CAP_IMAGE_AVIF: &str = "wai.image.avif"; // modern lossy (AV1-based) pub const CAP_IMAGE_JXL: &str = "wai.image.jxl"; // JPEG-XL (lossless + lossy) pub const CAP_IMAGE_JPEGAI: &str = "wai.image.jpegai"; // JPEG AI (ITU-T T.840.1 / ISO/IEC 6048-1:2025) standardized neural image codec. Deterministic decoder in-crate for one configuration (256×256 4:4:4, model_id 0 / tools_0, β = 0, high-operation-point synthesis; jpegai_decode::check_codestream lists the rest): codestream→RGB via jpegai_decode::decode (integer entropy and PTQ networks; dequant, post-filters and colour conversion in fixed-order IEEE-754 float; max|Δ| ≤ 2 vs the reference in its test). Its scale path and entropy decodes run through the integer operator set (jpegai_ops, integer-operators §13.6.1). Without the integer weight bundle, or in a non-default floating-point environment, the float ORT decode runs; a refusal of the exact decode is reported, not replaced by it; codestreams outside the configuration are refused. Model weights sink-supplied. +// IEEE 1857.11-2024, neural-network-based image coding — a CANDIDATE (registered 2026-10-22): +// the standard's text is not openly readable, and its open reference software writes a +// picture header with no start code from a working draft, so no payload format is pinned and +// an encoder MUST NOT emit it (SPEC §5, Candidate capabilities). format_probe::nic reads that +// header as the software's writer lays it out (format-probes §10). Float inference: NeuralFloat. +pub const CAP_IMAGE_IEEE1857_11: &str = "wai.image.ieee1857_11"; // Audio pub const CAP_AUDIO_OPUS: &str = "wai.audio.opus"; // modern lossy pub const CAP_AUDIO_FLAC: &str = "wai.audio.flac"; // lossless @@ -396,6 +402,17 @@ pub const CAP_IMAGE_JP2: &str = "wai.image.jp2"; // the DCP / archival-mezzanine video path. Same OpenJPEG reference. pub const CAP_VIDEO_J2K: &str = "wai.video.j2k"; +// JPEG XS (ISO/IEC 21122) — the low-latency intra-frame mezzanine and contribution codec +// (RTP RFC 9134; SMPTE ST 2110-22). PATENT POOL: essential patents are licensed through a +// pool, so classified conservatively. Image payload: one JPEG XS codestream, SOC to EOC. +// Video payload: five u32 LE values (n, h, w, fps_num, fps_den), then n codestreams each +// preceded by its u32 LE length — wai.video.av1's framing. A sink that advertises either +// decodes at ISO/IEC 21122-4's strict conformance point (Annex B.2: decoded images bitwise +// identical to the reference), so the class is StandardDefined. Header probe: +// format_probe::jxs (format-probes §3). +pub const CAP_IMAGE_JXS: &str = "wai.image.jxs"; +pub const CAP_VIDEO_JXS: &str = "wai.video.jxs"; + // --------------------------------------------------------------------------- // Volumetric / 3D geometry compression. Complements the asset capabilities // (wai.mesh.glb, wai.splat.spz, wai.splat.4d) with the standard *compression* @@ -453,6 +470,13 @@ pub const CAP_META_HDR10PLUS: &str = "wai.meta.hdr10plus"; // licence-flagged slot. The capability string is a functional identifier. pub const CAP_META_DOVI: &str = "wai.meta.dovi"; +// The projection descriptor (extensions/projection.md) — ROYALTY-FREE, WAI-native: a 40-byte +// WPJ1 record that says how a video rendition's pictures map onto the viewer's sphere or field +// of view (rectilinear, equirectangular, half-equirectangular, fisheye; projection semantics +// of ISO/IEC 23090-2) and how its two views are arranged (frame-packed per ITU-T H.273, or +// in layers of a multiview bitstream). A COMPANION descriptor, as the HDR descriptors are. +pub const CAP_META_PROJECTION: &str = "wai.meta.projection"; + // --------------------------------------------------------------------------- // Broadcast and streaming video / audio codecs: the elementary bitstreams that // terrestrial and satellite broadcast, streaming and device playback carry, so @@ -482,6 +506,14 @@ pub const CAP_VIDEO_VVC: &str = "wai.video.vvc"; // Annex B byte stream. pub const CAP_VIDEO_HEVC: &str = "wai.video.hevc"; +// MV-HEVC (ITU-T H.265 | ISO/IEC 23008-2 Annexes F and G) — PATENT POOL, as HEVC. Payload: +// the H.265 Annex B byte stream of a multiview bitstream (a Multiview Main or Multiview Main +// 10 layer over a Main or Main 10 base). A sink that advertises it outputs every output layer; +// its base layer is a wai.video.hevc payload, so wai.video.hevc is a usable fallback. H.265 +// (V11) §F.13.5.1 requires every output sample of every output layer to equal the decoding +// process's: StandardDefined. Header probe: format_probe::hevc (format-probes §4). +pub const CAP_VIDEO_MVHEVC: &str = "wai.video.mvhevc"; + // AVC / H.264 (ITU-T H.264 | ISO/IEC 14496-10) — PATENT POOL. Payload: the // Annex B byte stream. pub const CAP_VIDEO_AVC: &str = "wai.video.avc"; @@ -602,6 +634,8 @@ pub fn known_capabilities() -> &'static [&'static str] { CAP_NEURAL_ELIC, CAP_NEURAL_MLICPP, CAP_NEURAL_MLICV2, CAP_NEURAL_CMIC, CAP_NEURAL_VIDEO_BMSHJ2018, CAP_NEURAL_GLC, CAP_NEURAL_DCVC_RT, CAP_NEURAL_DCVC_FM, + // IEEE 1857.11 neural image coding (a candidate: no payload format pinned) + CAP_IMAGE_IEEE1857_11, // perceptual / generative-semantic CAP_SEMANTIC_CLIP, CAP_SEMANTIC_PERCO2, // learned integer-exact audio codec (byte-exact PCM) + splat codec (byte-exact attrs) @@ -624,15 +658,20 @@ pub fn known_capabilities() -> &'static [&'static str] { CAP_AUDIO_IAMF, CAP_AUDIO_AC4, CAP_AUDIO_IVAS, // cinema mastering (JPEG 2000 / DCP) CAP_IMAGE_JP2, CAP_VIDEO_J2K, + // mezzanine and contribution (JPEG XS) + CAP_IMAGE_JXS, CAP_VIDEO_JXS, // volumetric / 3D geometry compression CAP_MESH_DRACO, CAP_PC_GPCC, CAP_PC_VPCC, CAP_SPLAT_SOG, // standardized haptics CAP_HAPTIC_MIHS, // HDR dynamic-metadata descriptors (companion to a video payload) CAP_META_HDR10PLUS, CAP_META_DOVI, + // the projection descriptor (companion to a video payload) + CAP_META_PROJECTION, // broadcast and streaming video (sink-supplied; none is a default) CAP_VIDEO_AV2, CAP_VIDEO_VVC, CAP_VIDEO_HEVC, CAP_VIDEO_AVC, CAP_VIDEO_VP9, CAP_VIDEO_VP8, CAP_VIDEO_EVC, CAP_VIDEO_EVC_BASELINE, CAP_VIDEO_AVS3, + CAP_VIDEO_MVHEVC, // enhancement layer (companion-only, bound to one base video rendition) CAP_VIDEO_LCEVC, // broadcast and streaming audio (sink-supplied; none is a default) @@ -675,7 +714,7 @@ pub fn is_known_capability(cap: &str) -> bool { /// capability is a layer; and a record or a derivation names no payload format, so /// it is no `WAI2` component in any role. pub fn is_companion_only(cap: &str) -> bool { - matches!(cap, CAP_META_HDR10PLUS | CAP_META_DOVI | CAP_VIDEO_LCEVC) + matches!(cap, CAP_META_HDR10PLUS | CAP_META_DOVI | CAP_META_PROJECTION | CAP_VIDEO_LCEVC) } /// Whether a registered capability is an **enhancement layer**: a companion @@ -767,7 +806,7 @@ pub fn companion_applies_to(companion: &str, base: &str) -> bool { return false; } match companion { - CAP_META_HDR10PLUS | CAP_META_DOVI => !is_known_capability(base) || is_video_class(base), + CAP_META_HDR10PLUS | CAP_META_DOVI | CAP_META_PROJECTION => !is_known_capability(base) || is_video_class(base), c if is_enhancement_layer(c) => enhancement_bases(c).contains(&base), _ => false, } @@ -814,6 +853,8 @@ pub fn capability_tier(cap: &str) -> Option { // HDR dynamic metadata reproduces exactly (it IS the descriptor bytes). | CAP_IMAGE_JP2 | CAP_VIDEO_J2K | CAP_MESH_DRACO | CAP_AUDIO_IAMF | CAP_META_HDR10PLUS | CAP_META_DOVI + // The projection descriptor reproduces exactly: it IS its 40 bytes. + | CAP_META_PROJECTION // A parameter-set listing is its own output, byte for byte. | CAP_PRIOR_SET => Lossless, // Interactive worlds: replay-equivalence is EXACT state @@ -867,6 +908,10 @@ CAP_WORLD_SCENE | CAP_WORLD_REPLAY | CAP_WORLD_LIVE | CAP_WORLD_MODEL | CAP_MESH | CAP_VIDEO_AV2 | CAP_VIDEO_VVC | CAP_VIDEO_HEVC | CAP_VIDEO_AVC | CAP_VIDEO_VP9 | CAP_VIDEO_VP8 | CAP_VIDEO_EVC | CAP_VIDEO_EVC_BASELINE | CAP_VIDEO_AVS3 | CAP_VIDEO_LCEVC | CAP_AUDIO_MPEGH | CAP_AUDIO_AVS3 | CAP_AUDIO_AAC | CAP_AUDIO_OPUS_PACKET + | CAP_VIDEO_MVHEVC + // JPEG XS: mezzanine compression, visually lossless by design; its lossless mode is + // signalled in the codestream, as for AVIF. IEEE 1857.11: a lossy neural image codec. + | CAP_IMAGE_JXS | CAP_VIDEO_JXS | CAP_IMAGE_IEEE1857_11 // A residual refinement quantises its residual by its step q: lossy against // its target, whatever its base; its decode is exact given the stage below. | CAP_IMAGE_INT_REFINE | CAP_VIDEO_INT_REFINE | CAP_AUDIO_INT_REFINE | CAP_SPLAT_INT_REFINE => Lossy, @@ -925,6 +970,9 @@ pub fn capability_license(cap: &str) -> Option { // licensing of whatever format is pinned is not established. // Classified conservatively and provisionally (SPEC §5, Licence classes). CAP_SPLAT_SOG => PatentPool, + // IEEE 1857.11: a candidate whose payload format is not pinned, and whose open + // reference software states that patent rights may apply. Provisional, conservative. + CAP_IMAGE_IEEE1857_11 => PatentPool, // Single-licensor proprietary: AC-4 and ST 2094-10 dynamic metadata. CAP_AUDIO_AC4 | CAP_META_DOVI => Proprietary, // Patent-pool / SEP royalties: IVAS (a voice-codec pool, with EVS); MPEG @@ -942,6 +990,9 @@ pub fn capability_license(cap: &str) -> Option { // and AAC (pool programmes). CAP_VIDEO_VVC | CAP_VIDEO_HEVC | CAP_VIDEO_AVC | CAP_VIDEO_EVC | CAP_VIDEO_LCEVC | CAP_VIDEO_AVS3 | CAP_AUDIO_MPEGH | CAP_AUDIO_AVS3 | CAP_AUDIO_AAC => PatentPool, + // MV-HEVC, as HEVC (October 2026); JPEG XS (a RAND pool licenses its essential + // patents; the open encoder's grant covers only its contributors' own claims). + CAP_VIDEO_MVHEVC | CAP_IMAGE_JXS | CAP_VIDEO_JXS => PatentPool, // Non-commercial model licence (PerCoV2 is CC-BY-NC-SA). CAP_SEMANTIC_PERCO2 => NonCommercial, // Royalty-free — listed explicitly, never a fall-through. @@ -981,6 +1032,8 @@ pub fn capability_license(cap: &str) -> Option { // devices (only the logo is licensed). | CAP_IMAGE_JP2 | CAP_VIDEO_J2K | CAP_AUDIO_IAMF | CAP_MESH_DRACO | CAP_META_HDR10PLUS + // The projection descriptor: WAI-native, its format defined by projection.md. + | CAP_META_PROJECTION // AV2 under the AOMedia Patent License 1.0; VP9 and VP8 under their BSD // source + patent grant; EVC Baseline royalty-free by design (caveat // recorded); the Opus packet form, Opus as wai.audio.opus is. @@ -1290,6 +1343,10 @@ pub fn capability_replicate_class(cap: &str) -> Option { | CAP_NEURAL_ELIC | CAP_NEURAL_MLICPP | CAP_NEURAL_MLICV2 | CAP_NEURAL_CMIC | CAP_NEURAL_VIDEO_BMSHJ2018 | CAP_NEURAL_GLC | CAP_NEURAL_DCVC_RT | CAP_NEURAL_DCVC_FM => NeuralFloat, + // IEEE 1857.11: no integer decode is shown, and the open text that states what its + // decoders must reproduce (its call for proposals) asks for a cross-check by + // re-running a decoder, not for identical output across machines. + CAP_IMAGE_IEEE1857_11 => NeuralFloat, // Deterministic integer generator: generated content, byte-identical on // every machine, pinned to (model, prompt, seed, steps) — receiptable. CAP_GENERATE_SPECTRAL => GenerativeDeterministic, @@ -1307,6 +1364,11 @@ pub fn capability_replicate_class(cap: &str) -> Option { // §C.5.1, the VP9 specification §8.1. It covers the cropped decoded // pictures, not processing an SEI message only signals (film grain). | CAP_VIDEO_AVC | CAP_VIDEO_HEVC | CAP_VIDEO_VVC | CAP_VIDEO_VP9 + // MV-HEVC: H.265 (V11) §F.13.5.1, every output sample of every output layer. + | CAP_VIDEO_MVHEVC + // JPEG XS: a sink that advertises it decodes at ISO/IEC 21122-4's strict conformance + // point (Annex B.2, decoded images bitwise identical to the reference images). + | CAP_IMAGE_JXS | CAP_VIDEO_JXS // WAI-native capabilities, by the extension that defines each one's // `*-equivalence` over integer / fixed-point state. The wai.audio.scene // mixdown is exact given its sources' decoded PCM; wai.world.model's @@ -1343,6 +1405,9 @@ pub fn capability_replicate_class(cap: &str) -> Option { | CAP_AUDIO_MPEGH | CAP_AUDIO_AVS3 | CAP_AUDIO_AAC | CAP_AUDIO_AC4 | CAP_MESH_DRACO | CAP_PC_GPCC | CAP_PC_VPCC | CAP_SPLAT_SOG | CAP_HAPTIC_MIHS | CAP_META_HDR10PLUS | CAP_META_DOVI + // The projection descriptor: what it governs is a rendered view, which no text + // fixes; the descriptor's own bytes pass through intact. + | CAP_META_PROJECTION | CAP_MESH_GLB | CAP_SPLAT_SPZ => StandardUnclassified, // Neural-network coding (ISO/IEC 15938-17). Whether every conforming NNC // decoder reconstructs the same parameter values is not established @@ -1577,6 +1642,7 @@ const CLASSICAL: &[&str] = &[ CAP_MESH_GLB, CAP_MESH_DRACO, CAP_PC_GPCC, CAP_PC_VPCC, CAP_SPLAT_SPZ, CAP_HAPTIC_MIHS, CAP_META_HDR10PLUS, CAP_META_DOVI, + CAP_IMAGE_JXS, CAP_VIDEO_JXS, CAP_VIDEO_MVHEVC, ]; /// Whether `cap` decodes a classical codec ([`CLASSICAL`]). @@ -1913,6 +1979,8 @@ mod tests { (CAP_VIDEO_HEVC, LicenseClass::PatentPool, ReplicateClass::StandardDefined), // H.265 pools + bilateral (CAP_VIDEO_AVC, LicenseClass::PatentPool, ReplicateClass::StandardDefined), // H.264 pool (CAP_VIDEO_VP9, LicenseClass::RoyaltyFree, ReplicateClass::StandardDefined), // BSD + patent grant + // MV-HEVC: H.265 (V11) §F.13.5.1, every output layer. + (CAP_VIDEO_MVHEVC, LicenseClass::PatentPool, ReplicateClass::StandardDefined), // as HEVC // AV2 v1.0.0 §7.1: identical output unless film grain is signalled, then a // perceptual criterion — as AV1. (CAP_VIDEO_AV2, LicenseClass::RoyaltyFree, ReplicateClass::StandardTolerance), // AOMedia Patent License 1.0 @@ -1977,6 +2045,9 @@ mod tests { CAP_PRIOR_NNC, CAP_TENSOR_NNC, // A candidate whose format is not pinned: provisional, conservative. CAP_SPLAT_SOG, + // Registered 2026-10-22: MV-HEVC as HEVC; JPEG XS (a RAND pool); IEEE 1857.11, + // a candidate. + CAP_VIDEO_MVHEVC, CAP_IMAGE_JXS, CAP_VIDEO_JXS, CAP_IMAGE_IEEE1857_11, ]; want.sort_unstable(); assert_eq!(non_rf, want); @@ -2025,7 +2096,7 @@ mod tests { #[test] fn companion_only_and_enhancement_layers() { // Companion-only: HDR dynamic metadata and the LCEVC enhancement layer. - for &c in &[CAP_META_HDR10PLUS, CAP_META_DOVI, CAP_VIDEO_LCEVC] { + for &c in &[CAP_META_HDR10PLUS, CAP_META_DOVI, CAP_META_PROJECTION, CAP_VIDEO_LCEVC] { assert!(is_known_capability(c)); assert!(is_companion_only(c), "{c} is companion-only"); } @@ -2117,7 +2188,8 @@ mod tests { // HDR dynamic metadata was registered as a companion to a video payload: // it applies to every video-class capability and to nothing else. - for &hdr in &[CAP_META_HDR10PLUS, CAP_META_DOVI] { + // The projection descriptor, registered the same way, applies to the same class. + for &hdr in &[CAP_META_HDR10PLUS, CAP_META_DOVI, CAP_META_PROJECTION] { for &base in known_capabilities() { let want = video.contains(&base); assert_eq!(companion_applies_to(hdr, base), want, "{hdr} on {base}"); @@ -2345,7 +2417,7 @@ mod tests { CAP_VIDEO_VP8, CAP_VIDEO_EVC, CAP_VIDEO_EVC_BASELINE, CAP_VIDEO_AVS3, CAP_VIDEO_LCEVC, CAP_AUDIO_MPEGH, CAP_AUDIO_AVS3, CAP_AUDIO_AAC, CAP_AUDIO_AC4, CAP_MESH_DRACO, CAP_PC_GPCC, CAP_PC_VPCC, - CAP_SPLAT_SOG, CAP_HAPTIC_MIHS, CAP_META_HDR10PLUS, CAP_META_DOVI, + CAP_SPLAT_SOG, CAP_HAPTIC_MIHS, CAP_META_HDR10PLUS, CAP_META_DOVI, CAP_META_PROJECTION, CAP_MESH_GLB, CAP_SPLAT_SPZ, // NNC: reconstruction equality across decoders not established. CAP_PRIOR_NNC, CAP_TENSOR_NNC])); @@ -2443,6 +2515,9 @@ mod tests { (CAP_VIDEO_HEVC, "ITU-T H.265 (V11, 01/2026) §C.5.1: every output sample equals the specified decoding process's"), (CAP_VIDEO_VVC, "ITU-T H.266 (V4, 01/2026) §C.5.1: every output sample equals the specified decoding process's"), (CAP_VIDEO_VP9, "VP9 Bitstream & Decoding Process Specification v0.6 §8.1: output frames identical in all respects"), + (CAP_VIDEO_MVHEVC, "ITU-T H.265 (V11, 01/2026) §F.13.5.1: every output sample of every output layer equals the specified decoding process's"), + (CAP_IMAGE_JXS, "ISO/IEC 21122-4:2022 §6.3 and Annex B.2 (public preview): the strict conformance point, decoded images bitwise identical to the reference, which the registration requires of a sink"), + (CAP_VIDEO_JXS, "ISO/IEC 21122-4:2022 §6.3 and Annex B.2 (public preview): the strict conformance point, decoded images bitwise identical to the reference, which the registration requires of a sink"), (CAP_VIDEO_INT_MOTION, "int_video.rs: pure-integer decode, frame-state-equivalence"), (CAP_WORLD_SCENE, "extensions/interactive-worlds.md: replay-equivalence"), (CAP_WORLD_REPLAY, "extensions/interactive-worlds.md: replay-equivalence"), diff --git a/wai/wai-rs/src/codecs/registry.rs b/wai/wai-rs/src/codecs/registry.rs index 9ed0a7939..adcdcc22a 100644 --- a/wai/wai-rs/src/codecs/registry.rs +++ b/wai/wai-rs/src/codecs/registry.rs @@ -194,7 +194,7 @@ pub fn capability_role(cap: &str) -> Option<&'static str> { /// The registered capabilities whose payload format no revision has pinned /// (SPEC §5, *Candidate capabilities*). -const CANDIDATES: &[&str] = &[CAP_TENSOR_NNC, CAP_SPLAT_SOG]; +const CANDIDATES: &[&str] = &[CAP_TENSOR_NNC, CAP_SPLAT_SOG, CAP_IMAGE_IEEE1857_11]; // ---- Records ----------------------------------------------------------------- @@ -271,6 +271,7 @@ const ROWS: &[(&str, &str, Option<&str>, &str)] = &[ (CAP_NEURAL_INT_HYPER, S_NEURAL, None, "2026-06-24"), (CAP_NEURAL_INT_MLICPP, S_NEURAL, None, "2026-09-30"), (CAP_IMAGE_JPEGAI, S_NEURAL, Some("ISO/IEC 6048-1:2025"), "2026-06-13"), + (CAP_IMAGE_IEEE1857_11, S_NEURAL, Some("IEEE 1857.11-2024"), "2026-10-22"), (CAP_GENERATE_SPECTRAL, S_NEURAL, None, "2026-06-25"), (CAP_NEURAL_ELIC, S_NEURAL, None, "2026-06-13"), (CAP_NEURAL_MLICPP, S_NEURAL, None, "2026-06-13"), @@ -354,6 +355,9 @@ const ROWS: &[(&str, &str, Option<&str>, &str)] = &[ (CAP_HAPTIC_MIHS, S_IMMERSIVE, Some("ISO/IEC 23090-31:2025"), "2026-06-17"), (CAP_META_HDR10PLUS, S_IMMERSIVE, None, "2026-06-17"), (CAP_META_DOVI, S_IMMERSIVE, None, "2026-06-17"), + (CAP_IMAGE_JXS, S_IMMERSIVE, None, "2026-10-22"), + (CAP_VIDEO_JXS, S_IMMERSIVE, None, "2026-10-22"), + (CAP_META_PROJECTION, S_IMMERSIVE, None, "2026-10-22"), (CAP_VIDEO_AV2, S_BROADCAST, Some("AV2 Bitstream & Decoding Process Specification v1.0.0 (2026-05-28)"), "2026-09-28"), (CAP_VIDEO_VVC, S_BROADCAST, None, "2026-09-28"), (CAP_VIDEO_HEVC, S_BROADCAST, None, "2026-09-28"), @@ -368,6 +372,7 @@ const ROWS: &[(&str, &str, Option<&str>, &str)] = &[ (CAP_AUDIO_AAC, S_BROADCAST, None, "2026-09-28"), (CAP_VIDEO_VP8, S_BROADCAST, None, "2026-10-18"), (CAP_AUDIO_OPUS_PACKET, S_BROADCAST, None, "2026-10-18"), + (CAP_VIDEO_MVHEVC, S_BROADCAST, None, "2026-10-22"), (CAP_IMAGE_INT_REFINE, S_REFINEMENT, None, "2026-10-01"), (CAP_VIDEO_INT_REFINE, S_REFINEMENT, None, "2026-10-01"), (CAP_AUDIO_INT_REFINE, S_REFINEMENT, None, "2026-10-01"), @@ -1610,7 +1615,7 @@ mod tests { fn candidates_are_the_registrations_with_no_pinned_payload() { let mut c: Vec<&str> = known_capabilities().iter().copied().filter(|c| is_candidate(c)).collect(); c.sort_unstable(); - assert_eq!(c, vec![CAP_SPLAT_SOG, CAP_TENSOR_NNC]); + assert_eq!(c, vec![CAP_IMAGE_IEEE1857_11, CAP_SPLAT_SOG, CAP_TENSOR_NNC]); for &cap in &c { assert!(!mandatory_floor().contains(&cap) && !recommended_set().contains(&cap)); assert!(!sink_capabilities().contains(&cap)); -- 2.54.0 (Apple Git-157) From 7d61c2f8f1d2fa4fd049e0548c703df5b3ee8ce9 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:55:38 -0400 Subject: [PATCH 014/242] wai: extensions format-probes and projection; the corpus format-probe-conformance with its second implementation External vectors written by permissively licensed encoders at pinned revisions from synthetic pictures (tools/make_external.sh, provenance.json), generated vectors and negative controls, the facts each encoder was asked for, the LCEVC streams fetch.sh fetches by digest, and format_probe_verify.py, written from the two texts. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/format-probes.md | 510 +++++ wai/extensions/projection.md | 131 ++ .../ext/avs3/main_128x64.avs3 | Bin 0 -> 3061 bytes .../ext/hevc/base_64x64.265 | Bin 0 -> 1358 bytes .../ext/jxs-video/main420_8_64x48_3f.jxsv | Bin 0 -> 3488 bytes .../ext/jxs/lossless_444_8_48x32.jxs | Bin 0 -> 4261 bytes .../ext/jxs/main422_10_64x64.jxs | Bin 0 -> 1536 bytes .../ext/jxs/rgb_rct_8_64x64.jxs | Bin 0 -> 2048 bytes .../ext/mvhevc/2view_64x64.265 | Bin 0 -> 1507 bytes .../ext/vvc/main10_100x58.266 | Bin 0 -> 1083 bytes .../ext/vvc/main10_64x64.266 | Bin 0 -> 1407 bytes wai/format-probe-conformance/fetch.sh | 23 + .../format_probe_verify.py | 1210 ++++++++++ .../gen/avs3-audio/ambisonic_order_2.bin | Bin 0 -> 31 bytes .../gen/avs3-audio/bed_and_objects.bin | Bin 0 -> 33 bytes .../gen/avs3-audio/channels_5_1.bin | Bin 0 -> 31 bytes .../gen/avs3-audio/lossless.bin | Bin 0 -> 31 bytes .../gen/avs3-audio/no_sync.bin | Bin 0 -> 31 bytes .../gen/avs3-audio/profile_3.bin | Bin 0 -> 31 bytes .../gen/avs3-audio/rate_index_9.bin | Bin 0 -> 31 bytes .../gen/avs3-audio/resolution_3.bin | Bin 0 -> 31 bytes .../gen/avs3-audio/truncated.bin | Bin 0 -> 3 bytes .../gen/avs3/frame_rate_zero.bin | Bin 0 -> 29 bytes .../gen/avs3/library_stream.bin | Bin 0 -> 3061 bytes .../gen/avs3/main10_1080p_emulated.bin | Bin 0 -> 29 bytes .../gen/avs3/marker_cleared.bin | Bin 0 -> 3061 bytes .../gen/avs3/no_sequence_header.bin | Bin 0 -> 2949 bytes .../gen/avs3/no_start_code.bin | Bin 0 -> 3058 bytes .../gen/avs3/picture_first.bin | Bin 0 -> 3061 bytes .../gen/avs3/truncated_header.bin | Bin 0 -> 2959 bytes .../gen/ieee1857_11/base_8bit.bin | Bin 0 -> 18 bytes .../gen/ieee1857_11/depth_code_0.bin | Bin 0 -> 18 bytes .../gen/ieee1857_11/main_10bit.bin | Bin 0 -> 18 bytes .../gen/ieee1857_11/profile_3.bin | Bin 0 -> 18 bytes .../gen/ieee1857_11/top_bits.bin | Bin 0 -> 18 bytes .../gen/ieee1857_11/truncated.bin | Bin 0 -> 12 bytes .../gen/jxs-video/four_frames_stated.bin | Bin 0 -> 3488 bytes .../gen/jxs-video/frame_cut.bin | Bin 0 -> 3481 bytes .../gen/jxs-video/trailing_byte.bin | Bin 0 -> 3489 bytes .../gen/jxs-video/width_too_small.bin | Bin 0 -> 3488 bytes .../gen/jxs-video/zero_frames.bin | Bin 0 -> 3488 bytes .../gen/jxs/cap_missing.bin | Bin 0 -> 1536 bytes .../gen/jxs/cdt_length.bin | Bin 0 -> 1536 bytes .../gen/jxs/cpih_2.bin | Bin 0 -> 1536 bytes .../gen/jxs/lcod_off_by_one.bin | Bin 0 -> 1536 bytes .../gen/jxs/lpih_25.bin | Bin 0 -> 1536 bytes wai/format-probe-conformance/gen/jxs/ng_3.bin | Bin 0 -> 1536 bytes .../gen/jxs/no_eoc.bin | Bin 0 -> 1536 bytes .../gen/jxs/no_soc.bin | Bin 0 -> 1534 bytes .../gen/jxs/truncated.bin | Bin 0 -> 28 bytes .../gen/jxs/unknown_marker.bin | Bin 0 -> 1540 bytes .../gen/lcevc/block_overrun.bin | Bin 0 -> 10 bytes .../gen/lcevc/extended_profile.bin | Bin 0 -> 33 bytes .../gen/lcevc/main_1080p.bin | Bin 0 -> 80 bytes .../gen/lcevc/nal_type_27.bin | Bin 0 -> 56 bytes .../gen/lcevc/no_global_config.bin | Bin 0 -> 13 bytes .../gen/lcevc/no_start_code.bin | Bin 0 -> 52 bytes .../gen/lcevc/non_idr_first.bin | Bin 0 -> 15 bytes .../gen/lcevc/picture_before_global.bin | Bin 0 -> 19 bytes .../gen/lcevc/reserved_flag.bin | Bin 0 -> 56 bytes .../gen/lcevc/resolution_type_0.bin | Bin 0 -> 32 bytes .../gen/lcevc/resolution_type_55.bin | Bin 0 -> 32 bytes .../gen/lcevc/sequence_reserved_bits.bin | Bin 0 -> 32 bytes .../gen/lcevc/size_type_6.bin | Bin 0 -> 10 bytes .../gen/lcevc/stop_byte_0x81.bin | Bin 0 -> 15 bytes .../gen/lcevc/upsample_type_6.bin | Bin 0 -> 32 bytes .../gen/lcevc/window_custom_tiles.bin | Bin 0 -> 80 bytes .../mhas/baseline2_escaped_rate_speakers.bin | Bin 0 -> 2125 bytes .../gen/mhas/frame_before_config.bin | Bin 0 -> 14 bytes .../gen/mhas/frame_first.bin | Bin 0 -> 18 bytes .../gen/mhas/lc3_48k_5_1.bin | Bin 0 -> 31 bytes .../gen/mhas/no_config.bin | Bin 0 -> 9 bytes .../gen/mhas/packet_overrun.bin | Bin 0 -> 22 bytes .../gen/mhas/rate_index_13.bin | Bin 0 -> 17 bytes .../gen/mhas/sync_byte.bin | Bin 0 -> 17 bytes .../gen/mvhevc/forbidden_bit.bin | Bin 0 -> 1509 bytes .../gen/mvhevc/no_sps.bin | Bin 0 -> 1425 bytes .../gen/mvhevc/no_start_code.bin | Bin 0 -> 1503 bytes .../gen/mvhevc/slice_first.bin | Bin 0 -> 1509 bytes .../gen/mvhevc/sps_truncated.bin | Bin 0 -> 1468 bytes .../gen/mvhevc/tid_zero.bin | Bin 0 -> 1509 bytes .../gen/mvhevc/vps_reserved.bin | Bin 0 -> 1509 bytes .../gen/other/png_has_no_probe.bin | 2 + .../gen/projection/centre_180.bin | Bin 0 -> 40 bytes .../gen/projection/erp_359.bin | Bin 0 -> 40 bytes .../gen/projection/erp_mono.bin | Bin 0 -> 40 bytes .../gen/projection/fisheye_top_bottom.bin | Bin 0 -> 40 bytes .../gen/projection/half_erp_side_by_side.bin | Bin 0 -> 40 bytes .../gen/projection/layers_equal.bin | Bin 0 -> 40 bytes .../gen/projection/length_39.bin | Bin 0 -> 39 bytes .../gen/projection/magic.bin | Bin 0 -> 40 bytes .../gen/projection/mono_baseline.bin | Bin 0 -> 40 bytes .../gen/projection/projection_4.bin | Bin 0 -> 40 bytes .../gen/projection/rectilinear_layers.bin | Bin 0 -> 40 bytes .../gen/projection/rectilinear_layers_0_2.bin | Bin 0 -> 40 bytes .../gen/projection/rectilinear_unstated.bin | Bin 0 -> 40 bytes .../gen/projection/reserved_byte.bin | Bin 0 -> 40 bytes .../gen/projection/version_2.bin | Bin 0 -> 40 bytes .../gen/vvc/ctu_size_reserved.bin | Bin 0 -> 1408 bytes .../gen/vvc/no_sps.bin | Bin 0 -> 1167 bytes .../gen/vvc/reserved_zero_bit.bin | Bin 0 -> 1408 bytes .../gen/vvc/slice_first.bin | Bin 0 -> 1408 bytes .../gen/vvc/tid_zero.bin | Bin 0 -> 1408 bytes wai/format-probe-conformance/provenance.json | 231 ++ wai/format-probe-conformance/tools/frame.py | 66 + .../tools/make_external.sh | 179 ++ .../tools/provenance.py | 50 + wai/format-probe-conformance/tools/yuv.py | 42 + wai/format-probe-conformance/vectors.json | 1981 +++++++++++++++++ 109 files changed, 4425 insertions(+) create mode 100644 wai/extensions/format-probes.md create mode 100644 wai/extensions/projection.md create mode 100644 wai/format-probe-conformance/ext/avs3/main_128x64.avs3 create mode 100644 wai/format-probe-conformance/ext/hevc/base_64x64.265 create mode 100644 wai/format-probe-conformance/ext/jxs-video/main420_8_64x48_3f.jxsv create mode 100644 wai/format-probe-conformance/ext/jxs/lossless_444_8_48x32.jxs create mode 100644 wai/format-probe-conformance/ext/jxs/main422_10_64x64.jxs create mode 100644 wai/format-probe-conformance/ext/jxs/rgb_rct_8_64x64.jxs create mode 100644 wai/format-probe-conformance/ext/mvhevc/2view_64x64.265 create mode 100644 wai/format-probe-conformance/ext/vvc/main10_100x58.266 create mode 100644 wai/format-probe-conformance/ext/vvc/main10_64x64.266 create mode 100644 wai/format-probe-conformance/fetch.sh create mode 100644 wai/format-probe-conformance/format_probe_verify.py create mode 100644 wai/format-probe-conformance/gen/avs3-audio/ambisonic_order_2.bin create mode 100644 wai/format-probe-conformance/gen/avs3-audio/bed_and_objects.bin create mode 100644 wai/format-probe-conformance/gen/avs3-audio/channels_5_1.bin create mode 100644 wai/format-probe-conformance/gen/avs3-audio/lossless.bin create mode 100644 wai/format-probe-conformance/gen/avs3-audio/no_sync.bin create mode 100644 wai/format-probe-conformance/gen/avs3-audio/profile_3.bin create mode 100644 wai/format-probe-conformance/gen/avs3-audio/rate_index_9.bin create mode 100644 wai/format-probe-conformance/gen/avs3-audio/resolution_3.bin create mode 100644 wai/format-probe-conformance/gen/avs3-audio/truncated.bin create mode 100644 wai/format-probe-conformance/gen/avs3/frame_rate_zero.bin create mode 100644 wai/format-probe-conformance/gen/avs3/library_stream.bin create mode 100644 wai/format-probe-conformance/gen/avs3/main10_1080p_emulated.bin create mode 100644 wai/format-probe-conformance/gen/avs3/marker_cleared.bin create mode 100644 wai/format-probe-conformance/gen/avs3/no_sequence_header.bin create mode 100644 wai/format-probe-conformance/gen/avs3/no_start_code.bin create mode 100644 wai/format-probe-conformance/gen/avs3/picture_first.bin create mode 100644 wai/format-probe-conformance/gen/avs3/truncated_header.bin create mode 100644 wai/format-probe-conformance/gen/ieee1857_11/base_8bit.bin create mode 100644 wai/format-probe-conformance/gen/ieee1857_11/depth_code_0.bin create mode 100644 wai/format-probe-conformance/gen/ieee1857_11/main_10bit.bin create mode 100644 wai/format-probe-conformance/gen/ieee1857_11/profile_3.bin create mode 100644 wai/format-probe-conformance/gen/ieee1857_11/top_bits.bin create mode 100644 wai/format-probe-conformance/gen/ieee1857_11/truncated.bin create mode 100644 wai/format-probe-conformance/gen/jxs-video/four_frames_stated.bin create mode 100644 wai/format-probe-conformance/gen/jxs-video/frame_cut.bin create mode 100644 wai/format-probe-conformance/gen/jxs-video/trailing_byte.bin create mode 100644 wai/format-probe-conformance/gen/jxs-video/width_too_small.bin create mode 100644 wai/format-probe-conformance/gen/jxs-video/zero_frames.bin create mode 100644 wai/format-probe-conformance/gen/jxs/cap_missing.bin create mode 100644 wai/format-probe-conformance/gen/jxs/cdt_length.bin create mode 100644 wai/format-probe-conformance/gen/jxs/cpih_2.bin create mode 100644 wai/format-probe-conformance/gen/jxs/lcod_off_by_one.bin create mode 100644 wai/format-probe-conformance/gen/jxs/lpih_25.bin create mode 100644 wai/format-probe-conformance/gen/jxs/ng_3.bin create mode 100644 wai/format-probe-conformance/gen/jxs/no_eoc.bin create mode 100644 wai/format-probe-conformance/gen/jxs/no_soc.bin create mode 100644 wai/format-probe-conformance/gen/jxs/truncated.bin create mode 100644 wai/format-probe-conformance/gen/jxs/unknown_marker.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/block_overrun.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/extended_profile.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/main_1080p.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/nal_type_27.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/no_global_config.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/no_start_code.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/non_idr_first.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/picture_before_global.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/reserved_flag.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/resolution_type_0.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/resolution_type_55.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/sequence_reserved_bits.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/size_type_6.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/stop_byte_0x81.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/upsample_type_6.bin create mode 100644 wai/format-probe-conformance/gen/lcevc/window_custom_tiles.bin create mode 100644 wai/format-probe-conformance/gen/mhas/baseline2_escaped_rate_speakers.bin create mode 100644 wai/format-probe-conformance/gen/mhas/frame_before_config.bin create mode 100644 wai/format-probe-conformance/gen/mhas/frame_first.bin create mode 100644 wai/format-probe-conformance/gen/mhas/lc3_48k_5_1.bin create mode 100644 wai/format-probe-conformance/gen/mhas/no_config.bin create mode 100644 wai/format-probe-conformance/gen/mhas/packet_overrun.bin create mode 100644 wai/format-probe-conformance/gen/mhas/rate_index_13.bin create mode 100644 wai/format-probe-conformance/gen/mhas/sync_byte.bin create mode 100644 wai/format-probe-conformance/gen/mvhevc/forbidden_bit.bin create mode 100644 wai/format-probe-conformance/gen/mvhevc/no_sps.bin create mode 100644 wai/format-probe-conformance/gen/mvhevc/no_start_code.bin create mode 100644 wai/format-probe-conformance/gen/mvhevc/slice_first.bin create mode 100644 wai/format-probe-conformance/gen/mvhevc/sps_truncated.bin create mode 100644 wai/format-probe-conformance/gen/mvhevc/tid_zero.bin create mode 100644 wai/format-probe-conformance/gen/mvhevc/vps_reserved.bin create mode 100644 wai/format-probe-conformance/gen/other/png_has_no_probe.bin create mode 100644 wai/format-probe-conformance/gen/projection/centre_180.bin create mode 100644 wai/format-probe-conformance/gen/projection/erp_359.bin create mode 100644 wai/format-probe-conformance/gen/projection/erp_mono.bin create mode 100644 wai/format-probe-conformance/gen/projection/fisheye_top_bottom.bin create mode 100644 wai/format-probe-conformance/gen/projection/half_erp_side_by_side.bin create mode 100644 wai/format-probe-conformance/gen/projection/layers_equal.bin create mode 100644 wai/format-probe-conformance/gen/projection/length_39.bin create mode 100644 wai/format-probe-conformance/gen/projection/magic.bin create mode 100644 wai/format-probe-conformance/gen/projection/mono_baseline.bin create mode 100644 wai/format-probe-conformance/gen/projection/projection_4.bin create mode 100644 wai/format-probe-conformance/gen/projection/rectilinear_layers.bin create mode 100644 wai/format-probe-conformance/gen/projection/rectilinear_layers_0_2.bin create mode 100644 wai/format-probe-conformance/gen/projection/rectilinear_unstated.bin create mode 100644 wai/format-probe-conformance/gen/projection/reserved_byte.bin create mode 100644 wai/format-probe-conformance/gen/projection/version_2.bin create mode 100644 wai/format-probe-conformance/gen/vvc/ctu_size_reserved.bin create mode 100644 wai/format-probe-conformance/gen/vvc/no_sps.bin create mode 100644 wai/format-probe-conformance/gen/vvc/reserved_zero_bit.bin create mode 100644 wai/format-probe-conformance/gen/vvc/slice_first.bin create mode 100644 wai/format-probe-conformance/gen/vvc/tid_zero.bin create mode 100644 wai/format-probe-conformance/provenance.json create mode 100644 wai/format-probe-conformance/tools/frame.py create mode 100644 wai/format-probe-conformance/tools/make_external.sh create mode 100644 wai/format-probe-conformance/tools/provenance.py create mode 100644 wai/format-probe-conformance/tools/yuv.py create mode 100644 wai/format-probe-conformance/vectors.json diff --git a/wai/extensions/format-probes.md b/wai/extensions/format-probes.md new file mode 100644 index 000000000..c5b994c07 --- /dev/null +++ b/wai/extensions/format-probes.md @@ -0,0 +1,510 @@ +# WAI Extension: Format Probes + +> Status: Draft. A **header probe** for each sink-supplied format the registry names whose +> payload WAI does not decode itself: it identifies a payload as the format its capability +> names and reads the parameters the format's headers state, or refuses it with one of the +> codes of §12. No probe decodes a picture or a sound. Probes are defined for JPEG XS +> (`wai.image.jxs`, `wai.video.jxs`), H.265 and MV-HEVC (`wai.video.hevc`, +> `wai.video.mvhevc`), H.266 (`wai.video.vvc`), LCEVC (`wai.video.lcevc`), MPEG-H 3D Audio +> (`wai.audio.mpegh`), AVS3 video and audio (`wai.video.avs3`, `wai.audio.avs3`), the picture +> header of IEEE 1857.11 (`wai.image.ieee1857_11`, a candidate) and the projection descriptor +> (`wai.meta.projection`, [projection](projection.md)). §3.5 maps a JPEG XS payload onto the +> media-type parameters of RFC 9134 and the constant-rate carriage of SMPTE ST 2110-22. The +> reference implementation is the `format_probe` module of `wai-rs` (feature +> `format_probe`) and its tool `wai_format_probe`; the corpus is +> [`format-probe-conformance/`](../format-probe-conformance/), and its second implementation +> is `format-probe-conformance/format_probe_verify.py`. +> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. +> +> Texts read for this revision: ITU-T H.265 (V11, 01/2026) and ITU-T H.266 (V4, 01/2026), +> whole; ITU-T H.273 and ITU-T H.274 (V4, 01/2026) §8.15; RFC 9134; SMPTE ST 2110-22:2022; the +> public previews of ISO/IEC 21122-2:2022/Amd 1:2022 (Tables A.7 and A.16) and ISO/IEC +> 21122-4:2022 (§6.3, Annex B.2); ATSC A/342-3:2024-04 §5.2; ETSI TS 101 154 V2.10.1 §5.16.2 and +> §6.10.2. Not openly readable, and so not read: ISO/IEC 21122-1, ISO/IEC 23094-2, ISO/IEC +> 23008-3, T/AI 109.2, T/AI 109.3 and IEEE 1857.11-2024. For those formats §3 to §10 say where +> each layout was read instead. + +## 1. Scope + +§1.1 A probe reads a payload's headers. It identifies the format by the bytes the format +begins with, where it has such bytes (§2.4), reads the parameters its headers state, and checks +the structure the probe reads: the order of the format's required structures, the lengths they +declare, and the values the format forbids or reserves among the fields read. It reads no +coded sample data: a payload a probe accepts is not shown to decode, and a probe that accepts a +payload says nothing of the pictures or sounds in it. + +§1.2 What a probe gives is used to describe a payload without decoding it: in a catalogue, in a +manifest a packager writes, in the `codecs` string or media-type parameters a delivery binding +states (§3.5), and to refuse a payload that does not hold the format its capability names before +any decoder sees it. + +§1.3 An encoder **MUST NOT** emit, under a capability whose probe has basis `standard` or `wai` +(§2.3), a payload that probe refuses; it **SHOULD NOT** emit one that a probe of basis +`implementation` refuses. A sink **MAY** probe a payload before it dispatches it (SPEC §4); a +sink that probes and gets a refusal does not dispatch the payload under that capability, and +may still take dispatch step 5 with a fallback. A probe's refusal is not a statement that the +payload does not conform to its format's standard: a probe of basis `implementation` reads the +layout its sources give, and §12's `outside_probe` names payloads it cannot read at all. + +§1.4 This extension adds no capability. It completes the registrations of `wai.video.vvc`, +`wai.video.lcevc`, `wai.audio.mpegh`, `wai.video.avs3` and `wai.audio.avs3`, which were +registered with no probe, and gives the registrations of `wai.image.jxs`, `wai.video.jxs`, +`wai.video.mvhevc`, `wai.image.ieee1857_11` and `wai.meta.projection` (SPEC §5) theirs. + +## 2. Common rules + +§2.1 A probe's result is a JSON object whose members each section names; a member the payload +does not state is `null`. Integers are JSON numbers, flags JSON booleans. The probe reports the +first refusal its reading meets, in the order each section reads. + +§2.2 A bit reader reads most significant bit first. `u(n)` is an `n`-bit unsigned integer; +`ue(v)` is the Exp-Golomb code of ITU-T H.265 §9.2, and one with more than 31 leading zero bits +is `reserved_value`. Reading past the payload's end is `truncated`. + +§2.3 Every probe has a **basis**: `standard`, where its format's own text is openly readable and +was read; `implementation`, where it is not and the layout was read from open implementations, +each section naming which agree; `wai`, for a format this specification defines. + +§2.4 The probes, in order: + +| capability | basis | section | +|---|---|---| +| `wai.image.jxs` | implementation | §3 | +| `wai.video.jxs` | implementation | §3.3 | +| `wai.video.hevc` | standard | §4 | +| `wai.video.mvhevc` | standard | §4 | +| `wai.video.vvc` | standard | §5 | +| `wai.video.lcevc` | implementation | §6 | +| `wai.audio.mpegh` | implementation | §7 | +| `wai.video.avs3` | implementation | §8 | +| `wai.audio.avs3` | implementation | §9 | +| `wai.image.ieee1857_11` | implementation | §10 | +| `wai.meta.projection` | wai | §11 | + +Any other capability has no probe: `no_probe`. + +§2.5 **Annex B byte streams.** A payload of H.265, H.266 or LCEVC is a byte stream: after any +zero bytes, it begins with the start code prefix `0x000001`, and otherwise is `no_signature` +(one zero byte before the prefix is not enough: at least two). A NAL unit is the bytes after a +prefix up to the next prefix, less the zero bytes that end it. Its payload is read with every +emulation-prevention byte removed: the `0x03` of every `0x000003` (ITU-T H.265 §7.4.2). + +## 3. JPEG XS + +§3.1 **Sources.** ISO/IEC 21122-1 is not openly readable. The marker codes, the segment +layouts and the order of §3.2 are those two independent open implementations of the codestream +agree on, a decoder and encoder under BSD-2-Clause-Patent and a decoder under MIT, at the +revisions `format-probe-conformance/tools/make_external.sh` pins. The profile codes are ISO/IEC +21122-2:2022/Amd 1:2022 Table A.7, and the sublevel codes its Table A.16, read in the +amendment's public preview. Level codes are named only where the two open readings agree +(§3.4). + +§3.2 **The codestream.** A `wai.image.jxs` payload is one codestream. A probe reads, in order: + +1. `SOC`, `0xFF10`, as the first two bytes; otherwise `no_signature`. +2. `CAP`, `0xFF50`, immediately after, and otherwise `out_of_order`; its length `Lcap`, a + big-endian `u16` of at least 2 (else `length_mismatch`), counts itself and `Lcap − 2` bytes of + capability bits, read most significant bit first: bit `i` set is reported in `cap_bits`. +3. `PIH`, `0xFF12`, immediately after `CAP`, else `out_of_order`, with `Lpih` 26, else + `length_mismatch`. Its body, big-endian: `Lcod` 32 bits, `Ppih` 16, `Plev` 16, `Wf` 16, `Hf` + 16, `Cw` 16, `Hsl` 16, `Nc` 8, `Ng` 8, `Ss` 8, `Bw` 8, `Fq` 4, `Br` 4, `Fslc` 1, `Ppoc` 3, + `Cpih` 4, `Nlx` 4, `Nly` 4, `Lh` 1, `Rl` 1, `Qpih` 2, `Fs` 2, `Rm` 2. It is `reserved_value` + unless `Wf`, `Hf` and `Hsl` are at least 1, `Nc` is 1 to 8, `Ng` is 4, `Ss` is 8, `Br` is 4, + `Cpih` is 0, 1 or 3, and `Qpih` is 0 or 1. +4. Segments up to the first slice header `SLH`, `0xFF20`, each a marker, a big-endian `u16` + length of at least 2 (else `length_mismatch`) and that many bytes less two: `CDT` `0xFF13`, + `WGT` `0xFF14`, `COM` `0xFF15`, `NLT` `0xFF16`, `CWD` `0xFF17`, `CTS` `0xFF18`, `CRG` + `0xFF19`, in any order. `CDT`'s length is `2 + 2·Nc`, else `length_mismatch`; it holds, per + component, its precision `B` (8 bits) and its sampling factors `Sx` and `Sy` (4 bits each), + none of which is 0 (else `reserved_value`). `SOC`, `EOC`, `PIH` or `CAP` among these segments is + `out_of_order`; any other marker is `reserved_value`. +5. `CDT` and `WGT` are required before `SLH`, and with `Cpih` 3 also `CTS` and `CRG`: + `missing_structure`. The payload ends with `EOC`, `0xFF11`: otherwise `missing_structure`. +6. `Lcod` is 0 (a variable-rate codestream) or the codestream's length in bytes, `SOC` to `EOC`; + otherwise `length_mismatch`. + +The probe does not read the slices. + +§3.3 **Video.** A `wai.video.jxs` payload is five `u32` little-endian values, `n`, `h`, `w`, +`fps_num` and `fps_den`, then `n` codestreams, each preceded by its length as a `u32` +little-endian value: the framing of `wai.video.av1` (SPEC §5), with a codestream for a temporal +unit. Any of the five being 0 is `framing_invalid`; a length past the payload's end is +`truncated`; each codestream is probed by §3.2, and one whose `Wf` exceeds `w` or whose `Hf` +exceeds `h` is `framing_invalid`; bytes after the `n`-th codestream are `trailing_bytes`. + +§3.4 **Parameters.** For one codestream: `profile` and `level` (`Ppih` and `Plev` as four +lowercase hexadecimal digits after `0x`), `width` and `height` (`Wf`, `Hf`), `components` (per +component `[B, Sx, Sy]`), `structure`, `colour_transform` (`Cpih`), `decompositions` +(`[Nlx, Nly]`), `precinct_width` (`Cw`), `slice_height` (`Hsl`), `wavelet_precision` (`Bw`), +`fractional_bits` (`Fq`), `quantizer` (`Qpih`), `long_header` (`Lh`), `raw_mode` (`Rl`), +`cbr_bytes` (`Lcod`), `cap_bits`, and `rtp` (§3.5). `structure` is `"4:4:4"`, `"4:2:2"` or +`"4:2:0"` when there are at least three components, the first has `Sx` = `Sy` = 1 and the second +and third have equal factors of (1, 1), (2, 1) or (2, 2) respectively; otherwise `null`. + +For a video: `frames` (`n`), `size` (`[w, h]`), `fps` (`[fps_num, fps_den]`), +`constant_bytes` (the common `Lcod` when every codestream states the same non-zero `Lcod`, +otherwise `null`) and `first`, the first codestream's parameters. + +The profile names (white space omitted, as RFC 9134 §7.1 writes them), by `Ppih`: + +| `Ppih` | profile | `Ppih` | profile | +|---|---|---|---| +| `0x0000` | Unrestricted | `0x4240` | High420.12 | +| `0x1500` | Light422.10 | `0x4A40` | High444.12 | +| `0x1A00` | Light444.12 | `0x4E40` | High4444.12 | +| `0x2500` | Light-Subline422.10 | `0x6EC0` | MLS.12 | +| `0x3240` | Main420.12 | `0x9300` | LightBayer | +| `0x3540` | Main422.10 | `0xB340` | MainBayer | +| `0x3A40` | Main444.12 | `0xC340` | HighBayer | +| `0x3E40` | Main4444.12 | | | + +The level, by `Plev`'s high byte: `0x00` Unrestricted, `0x10` 2k-1, `0x20` 4k-1, `0x24` 4k-2, +`0x28` 4k-3, `0x30` 8k-1, `0x34` 8k-2, `0x38` 8k-3, `0x40` 10k-1. The high bytes `0x04` and +`0x2C` are named 1k-1 and 5k-1 by one open implementation and refused as reserved by the other; +the probe names them too, and §3.6 records the disagreement. The sublevel, by `Plev`'s low byte: +`0x00` Unrestricted, `0x80` Full, `0x10` Sublev12bpp, `0x0C` Sublev9bpp, `0x08` Sublev6bpp, +`0x06` Sublev4bpp, `0x04` Sublev3bpp, `0x03` Sublev2bpp. A code in none of these lists is +reported with a `null` name and is not refused: a later edition may define it. + +§3.5 **RTP and SMPTE ST 2110-22.** RFC 9134 carries a JPEG XS codestream in RTP; SMPTE ST +2110-22:2022 carries constant-bit-rate compressed video over RTP and names no codec. A sender of +a `wai.video.jxs` payload over RFC 9134: + +- states the media type `video/jxsv` with the parameters of `rtp` (RFC 9134 §7.1): `profile`, + `level` and `sublevel` by the names of §3.4 (omitted where `null`), `depth` (the largest + component precision), `width` and `height`, and `sampling` where the codestream determines it: + `RGB` where `structure` is `4:4:4` under a colour transform (`Cpih` 1 or 3), whose decoder + outputs R, G and B; otherwise `rtp.sampling` is `null` and the sender states the sampling, + since a codestream does not say whether its components are Y′C′BC′R, constant-luminance + Y′C′BC′R or ICTCP (RFC 9134 §7.1 lists all three); +- sends each codestream as one picture segment of codestream mode (`K` = 0, RFC 9134 §4.1) at + the frame rate `fps`, the RTP timestamp at 90 kHz (RFC 9134 §4.2), or each slice in slice mode + (`K` = 1); +- for SMPTE ST 2110-22 (§4 there: a constant number of bytes, and of RTP packets, per frame), + sends only a payload whose `constant_bytes` is not `null`; the codestream's `Lcod` is the + frame's byte count. + +A receiver rebuilds the payload by framing each received codestream as §3.3 does, `fps` from +the session's `exactframerate`. WAI defines no transport (moq-streaming-format §0); this +mapping says which of a payload's fields fill which of RFC 9134's. + +§3.6 **Exactness.** A sink that advertises `wai.image.jxs` or `wai.video.jxs` decodes at the +strict conformance point of ISO/IEC 21122-4 (Annex B.2: decoded images bitwise identical to the +reference images), not only at its relaxed point (PSNR bounds), so every conforming sink outputs +the same samples; the registry class is `standard-defined` on that basis. What was measured +(`format-probe-conformance/provenance.json`): the open decoder's C path and SIMD path give +byte-identical output on every JPEG XS vector of the corpus, on arm64; the second, independent +decoder gives the same bytes on every vector whose profile it implements, five of six; it does +not implement MLS.12 (it refuses `Ppih` `0x6EC0`), and on a copy of that vector with `Ppih` and +`Plev` set to 0 its output differs, while the first decoder's output equals the vector's source +picture byte for byte, as a lossless codestream's must. No x86 decode was measured. + +## 4. H.265 and MV-HEVC + +§4.1 **Sources.** ITU-T H.265 (V11, 01/2026): the NAL unit header (§7.3.1.2), the video +parameter set and its extension (§F.7.3.2.1, §F.7.3.2.1.1, derivations (F-2) to (F-6)), the +sequence parameter set (§7.3.2.2.1), profile_tier_level (§7.3.3, inference §F.7.4.4) and the HRD +parameters (§E.2.2, §E.2.3). + +§4.2 **NAL units.** Each NAL unit (§2.5) is at least 2 bytes (else `truncated`); its header is +`forbidden_zero_bit` 1, `nal_unit_type` 6, `nuh_layer_id` 6, `nuh_temporal_id_plus1` 3 bits. +`forbidden_zero_bit` 1 or `nuh_temporal_id_plus1` 0 is `reserved_value`. Types 0 to 31 are VCL. +The probe reads the first VPS (type 32) and the first SPS (type 33) with `nuh_layer_id` 0. A VCL +NAL unit before both is `out_of_order` if both occur later in the payload and `missing_structure` +if either never does; a payload with no VCL NAL unit is `missing_structure`. + +§4.3 **The VPS.** In order: `vps_video_parameter_set_id` u(4), `vps_base_layer_internal_flag`, +`vps_base_layer_available_flag`, `vps_max_layers_minus1` u(6), `vps_max_sub_layers_minus1` u(3) +(above 6 is `reserved_value`), `vps_temporal_id_nesting_flag`, `vps_reserved_0xffff_16bits` (any +other value is `reserved_value`), profile_tier_level(1, `vps_max_sub_layers_minus1`), the +sub-layer ordering information, `vps_max_layer_id` u(6), `vps_num_layer_sets_minus1` ue(v) +(above 1023 is `reserved_value`), the layer-id flags, the timing information with its HRD +parameters (`vps_num_hrd_parameters` above `vps_num_layer_sets_minus1` + 1 is `reserved_value`; +`cpb_cnt_minus1` above 31 is `reserved_value`), and `vps_extension_flag`. When the flag is 1 and +`vps_max_layers_minus1` is above 0, the probe reads the alignment bits (each 1, else +`reserved_value`) and `vps_extension( )` up to and including its profile_tier_level( ) +structures: `splitting_flag`, the sixteen `scalability_mask_flag`s, the dimension lengths, the +layer ids (each above the one before, else `reserved_value`), the dimension ids, `view_id_len` +and the view ids, the direct dependency flags, `num_add_layer_sets` and its +`highest_layer_idx_plus1` fields, the sub-layer and inter-layer temporal fields, +`default_ref_layers_active_flag`, `vps_num_profile_tier_level_minus1` (above 63 is +`reserved_value`) and the structures it counts. A profile_tier_level( ) is read as §7.3.3 lays it +out, its general profile part as 88 bits whatever its branches (the syntax's own note). A +structure with no profile takes its profile, tier and compatibility flags from the structure +before it (§F.7.4.4). + +§4.4 **The SPS** of the base layer: `sps_video_parameter_set_id` u(4), `sps_max_sub_layers_minus1` +u(3) (above 6 is `reserved_value`), `sps_temporal_id_nesting_flag`, profile_tier_level(1, ·), +`sps_seq_parameter_set_id` ue(v) (above 15 is `reserved_value`), `chroma_format_idc` ue(v) (above +3 is `reserved_value`), `separate_colour_plane_flag` when it is 3, `pic_width_in_luma_samples` +and `pic_height_in_luma_samples` ue(v) (0 is `reserved_value`), the conformance window, and +`bit_depth_luma_minus8` and `bit_depth_chroma_minus8` ue(v) (above 8 is `reserved_value`). The +cropped size is the coded size less the window, whose offsets count SubWidthC and SubHeightC +samples (2 and 2 at 4:2:0, 2 and 1 at 4:2:2, 1 and 1 otherwise); a window as large as the +picture is `reserved_value`. + +§4.5 **MV-HEVC.** A `wai.video.mvhevc` payload is also refused `not_multiview` unless the VPS +extension's scalability mask has bit 1 (multiview, Table F.1), `NumViews` (F-3) is at least 2, and +a VCL NAL unit with `nuh_layer_id` above 0 is present. + +§4.6 **Parameters.** `profile_idc`, `tier`, `level_idc` (the base SPS's), `chroma_format_idc`, +`bit_depth` (`[luma, chroma]`), `coded` and `cropped` (`[width, height]`), `layers` +(`Min(62, vps_max_layers_minus1)` + 1), `layers_present` (the `nuh_layer_id`s of the VCL NAL units, +ascending), and, when the VPS extension was read, `multilayer`: `scalability_mask` (bit `i` for +`scalability_mask_flag[ i ]`), `layer_ids`, `view_order` and `aux` (`ViewOrderIdx` and `AuxId` per +layer), `views` (`NumViews` when the multiview bit is set, else 1), `view_ids` (`view_id_val`, or +zeros when `view_id_len` is 0), `profiles` (`general_profile_idc` of each extension structure, +after inference) and `multiview_profile` (whether any of them indicates profile 6, Multiview Main +or Multiview Main 10, by its idc or its compatibility flag). + +## 5. H.266 + +§5.1 **Sources.** ITU-T H.266 (V4, 01/2026) §7.3.1.2, §7.3.2.3, §7.3.2.4, §7.3.3.1 and §7.3.3.2. + +§5.2 **NAL units.** The header is `forbidden_zero_bit` 1, `nuh_reserved_zero_bit` 1, +`nuh_layer_id` 6, `nal_unit_type` 5, `nuh_temporal_id_plus1` 3 bits. Either of the first two set, +`nuh_temporal_id_plus1` 0 or `nuh_layer_id` above 55 is `reserved_value`. Types 0 to 11 are VCL. +The probe reads the first SPS (type 15) and the first VPS (type 14), of which it reads +`vps_max_layers_minus1` (the second field, after a 4-bit id). A VCL NAL unit before the SPS is +`out_of_order` if an SPS occurs later and `missing_structure` if none does; no VCL NAL unit is +`missing_structure`. + +§5.3 **The SPS** to its conformance window: `sps_seq_parameter_set_id` u(4), +`sps_video_parameter_set_id` u(4), `sps_max_sublayers_minus1` u(3) (above 6 is `reserved_value`), +`sps_chroma_format_idc` u(2), `sps_log2_ctu_size_minus5` u(2) (above 2 is `reserved_value`), +`sps_ptl_dpb_hrd_params_present_flag` and, when it is 1, profile_tier_level(1, +`sps_max_sublayers_minus1`), `sps_gdr_enabled_flag`, `sps_ref_pic_resampling_enabled_flag` and +the flag it conditions, `sps_pic_width_max_in_luma_samples` and +`sps_pic_height_max_in_luma_samples` ue(v) (0 is `reserved_value`), and the conformance window. +profile_tier_level( ) is read as §7.3.3.1 lays it out; general_constraints_info( ) is +`gci_present_flag`, then, when it is 1, 71 bits of constraints, `gci_num_additional_bits` u(8) +and that many bits, then zero bits to the byte boundary (a one among them is `reserved_value`). +The cropped size is computed as in §4.4. + +§5.4 **Parameters.** `profile_idc`, `tier`, `level_idc`, `frame_only` +(`ptl_frame_only_constraint_flag`) and `multilayer_enabled` (`ptl_multilayer_enabled_flag`), all +`null` when the SPS carries no profile_tier_level( ); `sub_profiles` (`general_sub_profile_idc`s); +`chroma_format_idc`; `ctu_size` (`1 << (sps_log2_ctu_size_minus5 + 5)`); `coded`; `cropped`; +`layers` (`vps_max_layers_minus1` + 1, or 1 without a VPS); `layers_present`. + +## 6. LCEVC + +§6.1 **Sources.** ISO/IEC 23094-2 is not openly readable. The NAL unit header, the block +structure and the two configurations are those an open decoder (BSD-3-Clause-Clear) and an open +syntax reading under LGPL-2.1 agree on, each citing the standard's tables; the resolution table +is the standard's Table 20 as the decoder reproduces it. The two readings disagree on the byte +that extends a profile or level of 15; the probe does not read it (`outside_probe`). + +§6.2 **NAL units.** A `wai.video.lcevc` payload is the byte stream of SPEC §5 (§2.5). Each NAL +unit is at least 3 bytes (else `truncated`). Its first byte is `forbidden_zero_bit` (0), +`forbidden_one_bit` (1), `nal_unit_type` (5 bits) and the first bit of `reserved_flag` (1); its +second byte is the other eight bits of `reserved_flag`, all 1. Any other value of those bits is +`reserved_value`. `nal_unit_type` is 29 (IDR) or 28 (non-IDR), else `reserved_value`; the first NAL +unit is IDR, else `out_of_order`. The payload, emulation prevention removed, ends with the byte +`0x80`, else `reserved_value`. + +§6.3 **Blocks.** The payload before that byte is a sequence of blocks: a header byte, its high +three bits `payload_size_type` and its low five `payload_type`, then the body. A size type of 0 to +5 is the body's length; 6 is `reserved_value`; 7 is followed by the length as a multi-byte value: +seven bits a byte, most significant first, each byte but the last with its top bit set, at most +ten bytes (else `reserved_value`). A body past the payload's end is `length_mismatch`. Types: 0 +sequence configuration, 1 global configuration, 2 picture configuration, 3 and 4 encoded data, +5 additional information, 6 filler; the probe reads the first 0 and the first 1, counts the 2s, +and passes over the rest. A picture configuration before both configurations is `out_of_order`; +a payload without both is `missing_structure`. + +§6.4 **The configurations.** The sequence configuration: `profile_idc` 4, `level_idc` 4, +`sublevel_idc` 2, `conformance_window_flag` 1, five reserved bits that are 0 (else +`reserved_value`); a profile or level of 15 is `outside_probe`; with the flag, four multi-byte +window offsets (left, right, top, bottom); bytes left in the body are `length_mismatch`. The +global configuration: `processed_planes_type_flag` 1, `resolution_type` 6, `transform_type` 1, +`chroma_sampling_type` 2, `base_depth_type` 2, `enhancement_depth_type` 2, +`temporal_step_width_modifier_signalled_flag` 1, `predicted_residual_mode_flag` 1, +`temporal_tile_intra_signalling_enabled_flag` 1, `temporal_enabled_flag` 1, `upsample_type` 3, +`level1_filtering_signalled_flag` 1, `scaling_mode_level1` 2, `scaling_mode_level2` 2, +`tile_dimensions_type` 2, `user_data_enabled` 2, `level1_depth_flag` 1, `chroma_step_width_flag` +1; then, each where its condition holds: a byte of planes type (the planes flag), a byte of step +width modifier (its flag), four 16-bit kernel coefficients (`upsample_type` 4), a byte of two +filter coefficients (the filtering flag), for `tile_dimensions_type` above 0 the custom tile +width and height as 16-bit values when it is 3 and then a byte of tile compression, and, for +`resolution_type` 63, the custom width and height as 16-bit values. `resolution_type` 0 or 51 to +62, an `upsample_type` above 4, or a custom width or height of 0 is `reserved_value`. + +§6.5 **Parameters.** `nal_units` (`[IDR, non-IDR]` counts), `pictures` (picture configurations), +`profile_idc`, `level_idc`, `sublevel_idc`, `conformance_window` (the four offsets as signalled, +or `null`), `resolution_type`, `size` (Table 20's for types 1 to 50, the custom size for 63), +`transform_type`, `chroma_sampling_type`, `base_depth` and `enhancement_depth` (`8 + 2·type`), +`temporal_enabled`, `upsample_type`, `tile_dimensions_type`. + +Table 20, types 1 to 50: 360×200, 400×240, 480×320, 640×360, 640×480, 768×480, 800×600, +852×480, 854×480, 856×480, 960×540, 960×640, 1024×576, 1024×600, 1024×768, 1152×864, +1280×720, 1280×800, 1280×1024, 1360×768, 1366×768, 1400×1050, 1440×900, 1600×1200, 1680×1050, +1920×1080, 1920×1200, 2048×1080, 2048×1152, 2048×1536, 2160×1440, 2560×1440, 2560×1600, +2560×2048, 3200×1800, 3200×2048, 3200×2400, 3440×1440, 3840×1600, 3840×2160, 3840×2400, +4096×2160, 4096×3072, 5120×2880, 5120×3200, 5120×4096, 6400×4096, 6400×4800, 7680×4320, +7680×4800. + +## 7. MPEG-H 3D Audio + +§7.1 **Sources.** ISO/IEC 23008-3 is not openly readable. The MHAS packet syntax, the packet +types and the first fields of the configuration are those two independent open decoders agree +on; the low-complexity profile-level codes 0x0B to 0x0D are confirmed by ATSC A/342-3:2024-04 +§5.2.2.1. Neither open decoder is under a licence this corpus can use, so the corpus's MHAS +vectors are written by its generator (§13.2). + +§7.2 **Packets.** A `wai.audio.mpegh` payload is an MHAS stream: packets, each a header of +`MHASPacketType` = escapedValue(3, 8, 8), `MHASPacketLabel` = escapedValue(2, 8, 32) and +`MHASPacketLength` = escapedValue(11, 24, 24), then that many bytes. escapedValue(n1, n2, n3) +reads `v` = u(n1); when `v` is 2^n1 − 1 it reads `a` = u(n2) and adds it; when `a` is +2^n2 − 1 it reads u(n3) and adds that too. Every header is a whole number of bytes. The first +packet is a synchronisation packet (type 6) or a configuration packet (type 1), else +`no_signature`; a packet past the end is `truncated`. A synchronisation packet has label 0 and the +one byte `0xA5`, else `reserved_value`. A frame packet (type 2) before the first configuration is +`out_of_order`; a payload with no configuration is `missing_structure`. Other types are passed +over. + +§7.3 **The configuration.** The first configuration packet's payload: the profile-level +indication u(8); `usacSamplingFrequencyIndex` u(5), its rate by the table below, or, for 31, a +rate u(24) (0 is `reserved_value`); `coreSbrFrameLengthIndex` u(3); one reserved bit; +`receiverDelayCompensation` u(1); `speakerLayoutType` u(2); then, for type 0, +`CICPspeakerLayoutIdx` u(6), and for types 1 and 2 the speaker count, escapedValue(5, 8, 16) + 1. +Rates by index 0 to 30: 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, +11025, 8000, 7350, reserved, reserved, 57600, 51200, 40000, 38400, 34150, 28800, 25600, 20000, +19200, 17075, 14400, 12800, 9600, reserved, reserved, reserved; a reserved index is +`reserved_value`. + +§7.4 **Parameters.** `profile_level`; `profile` and `level`: `low-complexity` 1 to 5 for 0x0B to +0x0F, `baseline` 1 to 5 for 0x10 to 0x14, else `null`; `sampling_rate`; `core_frame_length_index`; +`speaker_layout_type`; `cicp_layout`; `speakers`; and the counts `packets`, `frames`, `syncs` and +`configs`. + +## 8. AVS3 video + +§8.1 **Sources.** T/AI 109.2 is not openly readable. The sequence header and the pseudo-start-code +prevention are read as an open decoder (BSD-3-Clause) reads them, with the start codes it and an +open syntax reading under LGPL-2.1 agree on. ETSI TS 101 154 V2.10.1 §5.16.2 requires the library +flags to be 0 and confirms the profile codes 0x20, 0x22 and 0x32. + +§8.2 **Units.** A `wai.video.avs3` payload begins, after any zero bytes (at least two), with the +prefix `0x000001`, else `no_signature`. A unit is the byte after a prefix (its start code) and the +bytes up to the next prefix. A unit's bytes are read with the prevention bits removed: in each +byte `0x02` that follows two zero bytes of the unit, its two low bits. Start codes: `0xB0` +sequence header, `0xB1` sequence end, `0xB3` intra picture, `0xB6` inter picture. A payload with no +sequence header is `missing_structure`; one whose first unit is not one is `out_of_order`; one +with no intra picture is `missing_structure`. + +§8.3 **The sequence header.** `profile_id` u(8), `level_id` u(8), `progressive_sequence` u(1), +`field_coded_sequence` u(1), `library_stream_flag` u(1) (1 is `outside_probe`), +`library_picture_enable_flag` u(1) (1 is `outside_probe`: a flag follows whose place the open +texts do not establish), `marker_bit`, `horizontal_size` u(14), `marker_bit`, `vertical_size` u(14), +`chroma_format` u(2), `sample_precision` u(3), `encoding_precision` u(3) for profiles 0x22 and +0x32 only, `marker_bit`, `aspect_ratio` u(4), `frame_rate_code` u(4), `marker_bit`, +`bit_rate_lower` u(18), `marker_bit`, `bit_rate_upper` u(12), `low_delay` u(1), +`temporal_id_enable_flag` u(1), `marker_bit`, `bbv_buffer_size` u(18), `marker_bit`, +`max_dpb_size_minus1` u(4). A `marker_bit` of 0 is `reserved_value`, and so are a size, chroma +format or sample precision of 0 and a frame rate code of 0 or above 13. + +§8.4 **Parameters.** `profile_id`, `level_id`, `progressive`, `field_coded`, `size`, +`chroma_format`, `sample_precision`, `encoding_precision` (or `null`), `aspect_ratio`, +`frame_rate_code`, `bit_rate` (`bit_rate_upper` · 2^18 + `bit_rate_lower`), `low_delay`, +`temporal_id_enabled`, `bbv_buffer_size`, `max_dpb_size`, `pictures` (`[intra, inter]` counts) and +`sequence_end` (whether a sequence end unit is present). + +## 9. AVS3 audio + +§9.1 **Sources.** T/AI 109.3 is not openly readable. The frame header is read as an open +demultiplexer under LGPL-2.1 reads it; ETSI TS 101 154 V2.10.1 §6.10.2 constrains the values a +broadcast stream uses. The demultiplexer reads only the lossy codec and refuses ancillary data, +and the probe does the same (`outside_probe`). It also skips two 8-bit fields it does not name, +and so does the probe. The header states no frame length, so the probe reads the first frame's +header only. + +§9.2 **The header.** A sync word of 12 bits, `0xFFF` (else `no_signature`); `audio_codec_id` u(4) +(1, lossless, is `outside_probe`; anything but 2 is `reserved_value`); `anc_data` u(1) (1 is +`outside_probe`); `nn_type` u(3) (above 1 is `reserved_value`); `coding_profile` u(3) (above 2 is +`reserved_value`); `sampling_frequency_index` u(4), rates 192000, 96000, 48000, 44100, 32000, +24000, 22050, 16000, 8000 (above 8 is `reserved_value`); 8 bits; then for profile 0 +`channel_number_index` u(7); for profile 1 `soundbed_type` u(2): for 0 the object count less one +u(7) and the per-object bitrate index u(4), for 1 `channel_number_index` u(7), the bitrate index +u(4), the object count less one u(7) and the per-object bitrate index u(4), and above 1 +`reserved_value`; for profile 2 the ambisonic order less one u(4) (an order outside 1 to 3 is +`reserved_value`); then `resolution` u(2) (above 2 is `reserved_value`), the bitrate index u(4) +unless the profile is 1, and 8 bits. + +§9.3 **Parameters.** `codec_id`, `nn_type`, `coding_profile`, `sampling_rate`, and as the profile +gives them `channel_number_index`, `soundbed_type`, `objects`, `bitrate_index`, +`bitrate_index_per_object`, `ambisonic_order`; and `resolution_bits` (8 · (`resolution` + 1)). + +## 10. IEEE 1857.11 + +§10.1 `wai.image.ieee1857_11` is a candidate (SPEC §5): IEEE 1857.11-2024 is not openly readable, +and the open reference software (BSD-2-Clause) writes a picture header with no start code, from a +working draft, marking the code that would write one as not yet written. So no payload format is +pinned. This parse reads that header as the software's writer lays it out, at the revision the +corpus README names. Its reader masks `level_id` differently from its writer; the parse follows +the writer. With no signature, the parse cannot tell this format from another; it reads a payload +declared to be one, and never refuses `no_signature`. + +§10.2 **The header**, big-endian: 3 bytes holding `profile_id` (3 bits), `level_id` (6), +`model_id` (3), `parameter_set_id` (7) and `quality_id` (5); the picture's height and width as +32-bit values; 2 bytes holding 2 unused bits (0), `output_bit_depth` (3), `picture_format` (3), +`log2_slice_size_h_minus6` (4) and `log2_slice_size_w_minus6` (4). Fewer than 13 bytes is +`truncated`. A `profile_id` above 2, an unused bit set, an `output_bit_depth` other than 1 (8 +bits) or 2 (10 bits), or a height or width of 0 is `reserved_value`. + +§10.3 **Parameters.** `profile_id`, `level_id`, `model_id`, `parameter_set_id`, `quality_id`, +`size` (`[width, height]`), `bit_depth`, `picture_format`, `slice_size` +(`[2^(w+6), 2^(h+6)]`). + +## 11. The projection descriptor + +§11.1 The probe of `wai.meta.projection` reads the descriptor as [projection](projection.md) §3 +lays it out and checks it as its §4 does; its parameters are those of projection.md §3. The +check of a descriptor against the rendition it accompanies, which needs that rendition's probe, +is projection.md §5's. + +## 12. Refusals + +| code | meaning | +|---|---| +| `no_signature` | the payload does not begin with the format's signature | +| `truncated` | the bytes end inside a structure the probe reads | +| `out_of_order` | a structure is where the format does not allow it | +| `reserved_value` | a field holds a value the format forbids or reserves | +| `length_mismatch` | a length a structure declares disagrees with the bytes | +| `missing_structure` | a structure the format requires is absent | +| `not_multiview` | a `wai.video.mvhevc` payload with no second view | +| `framing_invalid` | the framing of a `wai.video.jxs` payload is wrong | +| `trailing_bytes` | bytes follow the end of the payload's last structure | +| `descriptor_invalid` | a projection descriptor's fields disagree with each other | +| `layer_absent` | a projection descriptor names a layer its rendition does not hold | +| `outside_probe` | the payload uses a feature whose syntax the probe's sources do not establish | +| `no_probe` | no probe is defined for the capability | + +## 13. Conformance + +§13.1 [`format-probe-conformance/`](../format-probe-conformance/) holds `vectors.json`: every +vector's capability, file, SHA-256, origin and outcome (its parameters, or its refusal), the +projection descriptors' bindings with theirs (projection.md §5), and the facts of §13.2. + +§13.2 **External vectors** (`ext/`) were written by permissively licensed encoders at pinned +revisions from synthetic pictures, and each was decoded again; `tools/make_external.sh` writes +them and `provenance.json`, which records every command and decode. **Facts** are what each +encoder was asked for (a size, a profile, a view count): the probe must read them, which ties the +probe to the encoder and not to itself. **Generated vectors** (`gen/`) are written by +`wai_format_probe gen` from fixed values and from the external vectors: the formats no +permissively licensed encoder writes (LCEVC, MHAS, AVS3 audio, the IEEE 1857.11 header, the +projection descriptor) and the negative controls, each refused with a code of §12, every code +given. + +§13.3 **Fetched vectors.** `fetch.sh` fetches five LCEVC streams an open decoder's tests use, +written by an LCEVC encoder, and checks each SHA-256; the corpus names them and does not hold +them, because their licence notice names their owner. Each is a container of blocks (an 8-byte +magic `lcevcbin`, a `u32` version, then per block a `u16` type, a `u32` size and that many bytes, +the first 16 of which are two indices) whose remaining bytes, concatenated, are the byte stream. +`vectors.json` states, for each, what its encoder was asked for. + +§13.4 `wai_format_probe verify` and `wai-rs/tests/format_probe.rs` require every outcome, binding +and fact, and the generated files byte for byte; with `--fetched` the verifier requires every +fetched stream. `format_probe_verify.py`, written from this text and projection.md and sharing no +code with wai-rs, does the same, and requires every mutated copy of the corpus to fail. diff --git a/wai/extensions/projection.md b/wai/extensions/projection.md new file mode 100644 index 000000000..4b6c79b68 --- /dev/null +++ b/wai/extensions/projection.md @@ -0,0 +1,131 @@ +# WAI Extension: Projection Descriptor + +> Status: Draft. Registers `wai.meta.projection`, a **companion descriptor** to a video +> rendition (SPEC §5, §7.1), as the HDR dynamic-metadata descriptors are: a 40-byte `WPJ1` +> record that says how the rendition's pictures map onto what a viewer sees (rectilinear, +> equirectangular, half-equirectangular or fisheye) and how its two views are arranged (one +> view, two views packed side by side or top and bottom, or two views in two layers of a +> multiview bitstream such as `wai.video.mvhevc`). The projection kinds follow ISO/IEC 23090-2 +> (OMAF); the angle unit and ranges follow ITU-T H.274 (V4, 01/2026) §8.15; the packing +> arrangements are ITU-T H.273 (V4, 07/2024) §8.4's. The reference implementation is +> `format_probe::projection` in `wai-rs` (feature `format_probe`); its vectors are in +> [`format-probe-conformance/`](../format-probe-conformance/), with its second +> implementation, `format_probe_verify.py`. +> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. +> +> ISO/IEC 23090-2 is not openly readable and was not read. Its projection kinds are used here as +> the H.274 SEI messages that carry them in a bitstream state them (the equirectangular +> projection SEI message, §8.15.2, and the sphere rotation and region-wise packing messages, +> §8.15.4 and §8.15.5), and no clause of ISO/IEC 23090-2 is cited. + +## 1. Scope + +§1.1 A projection descriptor describes the pictures of one video rendition: the geometry a +renderer maps them onto and the arrangement of their views. It changes no sample and is applied +by the renderer, after decoding. A rendition without one is a rectilinear picture of one view. + +§1.2 The descriptor is WAI's own record, so a rendition's projection is stated the same way +whatever the codec, and also where the codec's own signalling is absent: an AV1 payload, a +JPEG XS payload or an MV-HEVC payload whose bitstream carries no projection SEI message. Where a +bitstream carries such a message and the descriptor disagrees with it, the descriptor is what +WAI states, and a renderer **SHOULD** report the disagreement. + +## 2. Terms + +§2.1 Angles are in units of 2â�»Â¹â�¶ degrees, as H.274 §8.15 states them: one degree is 65 536. +Azimuth increases eastward (to the viewer's left, seen from inside the sphere), elevation +northward, as for H.274's equirectangular projection (§8.15.2.2). + +§2.2 **Projections.** + +| code | name | the picture is | +|---|---|---| +| 0 | `rectilinear` | a flat picture with no sphere projection; its coverage, where stated, is its field of view | +| 1 | `equirectangular` | the equirectangular projection of the whole sphere, 360° of azimuth and 180° of elevation, as H.274 §8.15.2 maps it | +| 2 | `half-equirectangular` | the equirectangular projection of the front hemisphere: 180° of azimuth and 180° of elevation, centred on the coverage centre | +| 3 | `fisheye` | a circular fisheye image whose field of view is its coverage | + +§2.3 **Stereo arrangements.** + +| code | name | the two views are | +|---|---|---| +| 0 | `mono` | one view | +| 1 | `side-by-side` | the left and right halves of each picture, H.273 `VideoFramePackingType` 3 | +| 2 | `top-bottom` | the top and bottom halves of each picture, `VideoFramePackingType` 4 | +| 3 | `layers` | two layers of a multiview bitstream, each a view, named by their `nuh_layer_id` | + +## 3. The record + +§3.1 A `wai.meta.projection` payload is 40 bytes, multi-byte integers little-endian: + +| offset | field | type | meaning | +|---|---|---|---| +| 0 | `magic` | 4 B | `WPJ1` | +| 4 | `version` | u8 | 1 | +| 5 | `projection` | u8 | §2.2 | +| 6 | `stereo` | u8 | §2.3 | +| 7 | `first_view` | u8 | 0: the left eye's view is the left half, the top half or `left_layer`'s; 1: the right eye's is | +| 8 | `azimuth_range` | u32 | coverage in azimuth, 2â�»Â¹â�¶ degrees | +| 12 | `elevation_range` | u32 | coverage in elevation | +| 16 | `centre_azimuth` | i32 | the coverage's centre | +| 20 | `centre_elevation` | i32 | | +| 24 | `centre_tilt` | i32 | rotation about the centre's axis | +| 28 | `baseline_um` | u32 | the distance between the two cameras, micrometres; 0 where not stated | +| 32 | `left_layer` | u8 | with `stereo` 3, the left eye's layer; otherwise 0 | +| 33 | `right_layer` | u8 | with `stereo` 3, the right eye's layer; otherwise 0 | +| 34 | reserved | 6 B | zero | + +§3.2 Its parameters: `projection` and `stereo` (the names of §2), `first_view` (`left` or +`right`), `coverage` (`[azimuth_range, elevation_range]`), `centre` (`[azimuth, elevation, +tilt]`), `baseline_um`, and `layers` (`[left_layer, right_layer]` with `stereo` 3, else `null`). + +## 4. Checks + +§4.1 A reader refuses, in this order: + +1. fewer than 4 bytes or a magic other than `WPJ1`: `no_signature`; +2. a length other than 40: `length_mismatch`; +3. a `version` other than 1, a `projection` or `stereo` above 3, a `first_view` above 1, or a + reserved byte that is not zero: `reserved_value`; +4. fields that disagree, `descriptor_invalid`: + - coverage: for `equirectangular`, exactly 360° by 180°; for `half-equirectangular`, exactly + 180° by 180°; for `fisheye`, each range above 0 and at most 360°; for `rectilinear`, both 0 + (field of view not stated) or each above 0 and below 180°; + - centre: azimuth and tilt from −180° up to but not including 180°, elevation from −90° to 90° + (H.274 §8.15's ranges); + - views: `mono` with a `first_view`, `baseline_um`, `left_layer` or `right_layer` that is not + 0; `side-by-side` or `top-bottom` with a layer that is not 0; `layers` with the two layers + equal or either above 62. + +§4.2 The codes are those of [format-probes](format-probes.md) §12. + +## 5. Binding to the rendition + +§5.1 A projection descriptor is a `WAI2` companion (SPEC §7.1) whose `companion_of` names a +capability of the video media class, as an HDR descriptor's does. It is never a `WAI1` +`capability` or `fallback` and never a selectable rendition. + +§5.2 A descriptor with `stereo` 3 applies only to a rendition of `wai.video.mvhevc` whose probe +(format-probes §4) reports both `left_layer` and `right_layer` among its `layers_present`; +otherwise `layer_absent`, and a sink **MUST NOT** apply it. A sink that presents a rendition with +such a descriptor presents the view of each layer to the eye the descriptor names. + +§5.3 With `side-by-side` or `top-bottom`, each view is half the decoded picture's width or height; +a sink that presents one eye only presents the view `first_view` names as the left eye's when it +needs one view. + +## 6. Mapping + +§6.1 Writing a descriptor from a bitstream's own signalling: an H.274 equirectangular +projection SEI message gives `equirectangular` (or `half-equirectangular` where a region-wise +packing message packs only the front hemisphere); a sphere rotation message gives the centre; a +frame packing arrangement of type 3 or 4 gives `side-by-side` or `top-bottom`; an MV-HEVC +bitstream's two views (format-probes §4.6, `view_ids`) give `layers`. Writing the other way, a +descriptor gives the same messages. Fisheye has no H.274 message; its parameters beyond the +coverage (the image circle's centre and radius) are not in this revision of the record. + +## 7. Conformance + +§7.1 [`format-probe-conformance/`](../format-probe-conformance/) holds descriptors of every +projection and arrangement, descriptors refused with each code of §4, and bindings of descriptors +to an MV-HEVC rendition and an H.265 rendition with their outcomes (format-probes §13). diff --git a/wai/format-probe-conformance/ext/avs3/main_128x64.avs3 b/wai/format-probe-conformance/ext/avs3/main_128x64.avs3 new file mode 100644 index 0000000000000000000000000000000000000000..16f760d4feeb039f5fda7fceace663d0ce6310b9 GIT binary patch literal 3061 zcmV%~9;Cn$9NIX(q5Gxgk<;3yXdvP>?wisVKIGAS5sGIyyBX+t{(*FN7k1%pHv`ypPg$+U6tUG@J!iab z(0G}>HZbQgBN)w5qU#TRy6M*L_qcq0dioOR8jilb*?Pv!%%Z4;dAW7TVDZ@(E1=&< z`?l4*%=;;OYbXkD0W=O5liZ00RF~pZZA0x4%nGc|vpuspqY}~~?g=js-&ew{LZKLq zdFCnFnNa{}_rY%20*wKHwRCYdNJnYu-Av$a9BONr)8>PH5I90Uh=KpkLB13*440ww zPIfwB8xog|zq>9P|Z@;topP=lp3FOSXxy*mHE^Tx! zAlDxG&5igXArfg__C@gP*+ih~;aBc_5+)9A*nsXR*D64JW4~svtT=MNsyr*Rf;m%u}&*k?zlVQ7!=fk>W40e)w1nm~w5%Dp7q&v>M+jG<#x za~_GdT}3V<9^d7RII?ZI$!@RPqk~VaWU>SjU&cG8_x#p5JQ+pDvRlC9G77c2R`E-* zZ~ScFn@CLPW&YB2DHnS*Ta^9vQ;eq=DWK{{>`br)<*z&4ePy4lg$bdQ{Y2V0Ik<`7 zhHPe#I`iTCvv5te2C$4&MgzWS$k(Ofh;Dya&gnMO^GuRd@jBf@UAWPEbN>C;) z3XVRoPQ;p$uXEF~&(7&y{kyHyz%SwY-Jl770*&MWry)zdjn^4I z!Fg&DYlIkbZj0AU0wvj}*T>a76VENt{}tr|Ano6j2dhNRlpX5>9Vk%mV?Vkt7{a>y z4(mKbi(XWPcGE6i)2tOmWo=KmtJ- zh(y?&KIJEMnr##gop+uu&^pYUqGvELo()3@TcP{OGd1dHYp_kIEn`NUxYyCK(Z`|m z1cOWdac7p_=ld&@RhF;8uyFU@O#s2Y-h|7hrr^U8!{cH&2aM#CmCV?#aOLS*mOhFOUQE|o1M ziU*moJ$x;jHJ1ZOOFRV8@?b`|3gbLIuV%^)vZH5iE+FP)^3KKYm&~RR5#B`z;a?oC zB2ZUJ%x>>iEzf#fXi{X7s#O+VAJ}`M(sVpvZptR1aNB-J?*nQeSmeGV9}3&$ovba< zQgng#r*EB)-$2=()#}+*Ag4cy7x1ARfB*mi zj{pDxw*UYC|G+TNkdxT~!?o$QuD8$t00991FmhB8&efJ^@U)MP!VvqTDr;R=S@iTq zVm`>e3V$%IpD5vlnq2+5w4ao*JeeQ#6`h7Z$iHDPBG;dR?3=`kf4wr2>c7Od>|6Mch9+(VO4i3>FI({F(HJ!vJI+ zqC%|U@(wcDOv={M4eh-ovEqZQG5;;Q3Nq*#S>C{VixR7XYnrL@XZEWNk*hE%2)@Aao^kkUsqq)uA6NxmL?UgKD85bGTJFG7UkPy*P-rlhkH6Ea zZtZ7S1_bdiBMTEu>UZa@qN{7f-RU8iHU{dplMm}YSmLJ?UmK$prU{ScLO~_Rcb7mo z_Pt^I<@mT~&f5NQu=cd-FqtM}rpK6%1cPG8800a``OY|9_FU$4n-{L%q?=wn3lONl zWp~O;c_szl08D;r5SZAuixkO^A@^JW2!~dtZpL5o3o@V<+nytjVfRmRsV7H&qZ#b9t>ksBF~((?nheh zHFw^EL6c(;tBxtHpiUW?i|$?DA3o7JOywr)cA>2u<*uI6Wz39`$HhQTg!&6;??WU? zcEM#L literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/ext/hevc/base_64x64.265 b/wai/format-probe-conformance/ext/hevc/base_64x64.265 new file mode 100644 index 0000000000000000000000000000000000000000..8823e4a7ce14dfe10c205ab91d481fd11599a69f GIT binary patch literal 1358 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 literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/ext/jxs/main422_10_64x64.jxs b/wai/format-probe-conformance/ext/jxs/main422_10_64x64.jxs new file mode 100644 index 0000000000000000000000000000000000000000..13d94200e29c45555743e96b10d79097c02e1cf3 GIT binary patch literal 1536 zcmb7EF>ljA6nP9gBhjJ^urMIe$?6|5@9u0GAW<7f^1Zx!_xZc;eRoAv9D)Z}tV0Xn7CaK32ZDY8 zp6l_(2nM341NUQp$KNR$@Yr2*LpN}0&TYqcHl3!^V!#-4Se-T4I=jd2vJHsg4n%~@ zVFg@*U4{Fw1=~eJIeG>`MDLRiSvH0c68L%faoh(~%2TfuS8N{`EuDz2E{44P=h!ic z%dDN7^Oj)~AUXCmyhl9nt;e}K0Ea+P?)IyIUk60y2fJHXW16(GYpMcIiKLZhC45_~j8#ZjB^YID|d>0i0o z`I*>gOih0lkcAT4J}m@Zo*zn&E080JHvu9iMj(WUQ`%VsLqR8}{7x^>uG;$}@|UgW z(y;Cdv@&2^jtA8m8uRO7>*Fht&yJ!#q*CD&%cVvL)Na24oUJjSNym^~)k1u22`Ncq zRdKzMuCt#s8k2=5;xA3_NDTrtcSYyDy(?1w1{ZUonSAlA3>yFm*_c3$ U#@P@JDv4sZtQIex4_(B+0dTt)2LJ#7 literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/ext/jxs/rgb_rct_8_64x64.jxs b/wai/format-probe-conformance/ext/jxs/rgb_rct_8_64x64.jxs new file mode 100644 index 0000000000000000000000000000000000000000..643b8c8a98293c487095cbd4558d5c651a9d1b3b GIT binary patch literal 2048 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 B^@{)i literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/ext/vvc/main10_64x64.266 b/wai/format-probe-conformance/ext/vvc/main10_64x64.266 new file mode 100644 index 0000000000000000000000000000000000000000..ab90aa4f9b371a831caa633463a71e4394c1f34a GIT binary patch literal 1407 zcmV-_1%Ubh00031c>t>d5P$#xfPw@Or~%FY>j+Jij1VB408BtZ1Q3CQAqx=*Ss@Eh zh3NHaOUWjrvP4>lyhNIakx3CINQpHWVj&A8A!-o3Ats>-6p)0OAqjOPAt53m2^2&j zCL$1*Q4oZRG7ys)2urBQLV*Q@Ab`X|21tWa0W$;;L16$Ou>gRC2m}Q}1Ofv{K!8A4 zfdGJrAP^Qo0DzEyK*$IPgaieUfPj$!13-Y11P%xofI`R+A`l>12oNMdgFymFAmIi; zi$Vz@4k4i~M=Ass5CRJj2naxcKvW<=AVV-laX`Sg z2hczO00031i3h<5001>a`l>FDc@=R~00031Im86hz+nmCi)4j}N(Tc+)2-#@%G{iu zSa;MW0($HOGF}wL+x!AjT$$CSfRNWO`Tg(g^#U2N7f%4~n+Pt>JYOxf&E}rg{I6LF zDw~X)0&uVvOuj}wOWwR?7eY2GyM2t7$YRt$RhwGvS_T6dicAlMXX8JCVGk#+UmXA1 zyC8#AXU6^0Fa_FCstABds?`Io1$^aK1bU=i-S{JCUQ z7jK(uSgYBv4j|P93`mJHXMl;q7-0R`SsWiHWYo%SYf2P(IIN$I{*>L&0$6hraU~j( zUz;?fTOFI(z(C=+Jj=%bm795A+TwFRq*XwAO#IYaEw#~>{j=M5clhQMRx4|B^t#ix^H0a|H3^;l zNmm%~9Tr-gGf}&_G1n;4L4I=gzcgNL{W>|V+bT0D#aIfTB_KGqx@2sL z(R|N%1m0*&*n>a4!zK@mKI>Te#xCD4zW>3WMBUnVN0%cQ;8v_HxF?kMqbRGZW`41o z1l?0tFoBEAWn( z5v2-fIW=L7+`BV2?6;0rZqusV;_}IzYc!3Bq4q3xdJ4M?=Qk%Od4#O{Rk#2E0096N zlmXF70wIGu`o~i}A;Qm}zkONgFJd33RcKSM8UAt4T$y&aKg2L(A&#GYL|!?!ZL(f9 zngUM9ugrmTIv^B?-xn0j)L*AjI*&t`m%6C@;J!y% z_%{B-d{VDcsnE{g0000101lK1(Q5)>gFN|-I$Cvfve}gnAS;a3}x( literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/fetch.sh b/wai/format-probe-conformance/fetch.sh new file mode 100644 index 000000000..dee559d31 --- /dev/null +++ b/wai/format-probe-conformance/fetch.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Fetches the LCEVC streams the corpus checks without holding them (format-probes §13.3), +# into , and checks each file's SHA-256. They are the unit-test streams of an open LCEVC +# decoder (BSD-3-Clause-Clear), written by an LCEVC encoder from a short test sequence; their +# licence notice names their owner, so the corpus names them by digest and holds no copy. +# +# bash fetch.sh +set -euo pipefail +dir=${1:?usage: fetch.sh } +mkdir -p "$dir" +base=https://raw.githubusercontent.com/v-novaltd/LCEVCdec/741fb7c869cf61012f7dcebfcd9e56981198c78b/src/enhancement/test/assets # allow-name: repository URL +while read -r sum name; do + curl -fsS --retry 3 -o "$dir/$name" "$base/$name" + got=$( (sha256sum "$dir/$name" 2>/dev/null || shasum -a 256 "$dir/$name") | cut -d' ' -f1) + [ "$got" = "$sum" ] || { echo "$name: SHA-256 $got, expected $sum" >&2; exit 1; } +done <<'EOF' +0ad059dd167093ef773aa9be932afdf0cd2108656daaed18bfc7ee8041c4ee05 parse_std.bin +20b7d666216b20419b6e4d80b2d9b68b8c9b4a66f9ba01849895e00d7c9d2583 parse_gops.bin +1492419eb19f34dd1e4dd3f50b3525a32f2bb34ad48fdeea8e4e9fc1ab54c149 parse_hdr.bin +156568e8c5cbb0307e5c1b6dde25a8932315abfffe063140dfa6366edea4c9e4 decode_temp_on.bin +a486c0a40abdfdde11d95b51491a3b78a273a48b75548e7a86e32de361df781c decode_temp_off.bin +EOF +echo "fetched 5 files into $dir" diff --git a/wai/format-probe-conformance/format_probe_verify.py b/wai/format-probe-conformance/format_probe_verify.py new file mode 100644 index 000000000..fe50caa17 --- /dev/null +++ b/wai/format-probe-conformance/format_probe_verify.py @@ -0,0 +1,1210 @@ +"""The second implementation of the format probes: written from extensions/format-probes.md +and extensions/projection.md, sharing no code with wai-rs. + + python3 -I format_probe_verify.py [--fetched ] + +It probes every vector of /vectors.json and requires the outcome stated there +(the parameters, or the refusal), every binding's outcome, every fact (what an encoder was +asked for), that every file under ext/ and gen/ is a vector's, that vectors.json's probe and +refusal lists are the extension's §2.4 and §12 tables, and that every refusal is given. With +--fetched it requires every fetched LCEVC stream (fetch.sh) and what its encoder was asked for. +Then it mutates copies of the corpus and requires each copy to fail. + +Exit status 0 only if everything verifies and every mutated copy fails. Python 3.8 or later; +standard library only. +""" + +import copy +import hashlib +import json +import os +import re +import struct +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +EXT = os.path.join(HERE, "..", "extensions") + + +class Refuse(Exception): + def __init__(self, code): + Exception.__init__(self, code) + self.code = code + + +# ---- bits (§2.2) ------------------------------------------------------------------- + +class Bits: + def __init__(self, data): + self.d = bytes(data) + self.p = 0 + + def left(self): + return len(self.d) * 8 - self.p + + def u(self, n): + if self.left() < n: + raise Refuse("truncated") + v = 0 + for _ in range(n): + v = (v << 1) | ((self.d[self.p >> 3] >> (7 - (self.p & 7))) & 1) + self.p += 1 + return v + + def flag(self): + return self.u(1) == 1 + + def skip(self, n): + if self.left() < n: + raise Refuse("truncated") + self.p += n + + def ue(self): + zeros = 0 + while self.u(1) == 0: + zeros += 1 + if zeros > 31: + raise Refuse("reserved_value") + return (1 << zeros) - 1 + self.u(zeros) if zeros else 0 + + def aligned(self): + return self.p % 8 == 0 + + def align(self, bit): + while not self.aligned(): + b = self.u(1) + if bit is not None and b != bit: + raise Refuse("reserved_value") + + +def be16(b, at): + if at + 2 > len(b): + raise Refuse("truncated") + return (b[at] << 8) | b[at + 1] + + +# ---- Annex B (§2.5) ---------------------------------------------------------------- + +def nal_units(b): + i = 0 + while i < len(b) and b[i] == 0: + i += 1 + if i < 2 or i >= len(b) or b[i] != 1: + raise Refuse("no_signature") + starts, j = [], 0 + while j + 2 < len(b): + if b[j] == 0 and b[j + 1] == 0 and b[j + 2] == 1: + starts.append(j + 3) + j += 3 + else: + j += 1 + out = [] + for k, s in enumerate(starts): + e = starts[k + 1] - 3 if k + 1 < len(starts) else len(b) + while e > s and b[e - 1] == 0: + e -= 1 + out.append(b[s:e]) + return out + + +def unescape(n): + out, zeros = bytearray(), 0 + for x in n: + if zeros >= 2 and x == 3: + zeros = 0 + continue + zeros = zeros + 1 if x == 0 else 0 + out.append(x) + return bytes(out) + + +def crop(chroma, w, h, c): + sw, sh = {1: (2, 2), 2: (2, 1)}.get(chroma, (1, 1)) + dw, dh = sw * (c[0] + c[1]), sh * (c[2] + c[3]) + if dw >= w or dh >= h: + raise Refuse("reserved_value") + return [w - dw, h - dh] + + +# ---- JPEG XS (§3) ------------------------------------------------------------------ + +def spec_text(name): + return open(os.path.join(EXT, name), encoding="utf-8").read() + + +def section(text, heading): + at = text.index("\n" + heading) + nxt = text.find("\n## ", at + 1) + return text[at:nxt if nxt >= 0 else len(text)] + + +FP = spec_text("format-probes.md") +PROFILES = {} +for row in re.findall(r"^\|(.*)\|$", section(FP, "## 3. JPEG XS"), re.M): + cells = [c.strip() for c in row.split("|")] + for k in range(0, len(cells) - 1, 2): + m = re.fullmatch(r"`0x([0-9A-F]{4})`", cells[k]) + if m and cells[k + 1]: + PROFILES[int(m.group(1), 16)] = cells[k + 1] +LEVELS = {0x00: "Unrestricted", 0x04: "1k-1", 0x10: "2k-1", 0x20: "4k-1", 0x24: "4k-2", 0x28: "4k-3", + 0x2C: "5k-1", 0x30: "8k-1", 0x34: "8k-2", 0x38: "8k-3", 0x40: "10k-1"} +SUBLEVELS = {0x00: "Unrestricted", 0x80: "Full", 0x10: "Sublev12bpp", 0x0C: "Sublev9bpp", 0x08: "Sublev6bpp", + 0x06: "Sublev4bpp", 0x04: "Sublev3bpp", 0x03: "Sublev2bpp"} + + +def jxs_codestream(b): + if len(b) < 2 or be16(b, 0) != 0xFF10: + raise Refuse("no_signature") + if be16(b, 2) != 0xFF50: + raise Refuse("out_of_order") + lcap = be16(b, 4) + if lcap < 2: + raise Refuse("length_mismatch") + if 4 + lcap > len(b): + raise Refuse("truncated") + cap_bits = [i * 8 + k for i, x in enumerate(b[6:4 + lcap]) for k in range(8) if x & (0x80 >> k)] + at = 4 + lcap + if be16(b, at) != 0xFF12: + raise Refuse("out_of_order") + if be16(b, at + 2) != 26: + raise Refuse("length_mismatch") + if at + 28 > len(b): + raise Refuse("truncated") + p = b[at + 4:at + 28] + lcod, ppih, plev, wf, hf, cw, hsl = struct.unpack(">IHHHHHH", p[:16]) + nc, ng, ss, bw = p[16], p[17], p[18], p[19] + fq, br = p[20] >> 4, p[20] & 15 + cpih = p[21] & 15 + nlx, nly = p[22] >> 4, p[22] & 15 + lh, rl, qpih = p[23] >> 7, (p[23] >> 6) & 1, (p[23] >> 4) & 3 + if wf == 0 or hf == 0 or hsl == 0 or not 1 <= nc <= 8 or ng != 4 or ss != 8 or br != 4: + raise Refuse("reserved_value") + if cpih not in (0, 1, 3) or qpih > 1: + raise Refuse("reserved_value") + at += 28 + cdt, wgt, cts, crg = None, False, False, False + while True: + m = be16(b, at) + if m == 0xFF20: + break + ln = be16(b, at + 2) + if ln < 2: + raise Refuse("length_mismatch") + if at + 2 + ln > len(b): + raise Refuse("truncated") + body = b[at + 4:at + 2 + ln] + if m == 0xFF13: + if ln != 2 + 2 * nc: + raise Refuse("length_mismatch") + comps = [[body[2 * i], body[2 * i + 1] >> 4, body[2 * i + 1] & 15] for i in range(nc)] + if any(0 in c for c in comps): + raise Refuse("reserved_value") + cdt = comps + elif m == 0xFF14: + wgt = True + elif m == 0xFF18: + cts = True + elif m == 0xFF19: + crg = True + elif m in (0xFF15, 0xFF16, 0xFF17): + pass + elif m in (0xFF10, 0xFF11, 0xFF12, 0xFF50): + raise Refuse("out_of_order") + else: + raise Refuse("reserved_value") + at += 2 + ln + if cdt is None or not wgt or (cpih == 3 and not (cts and crg)): + raise Refuse("missing_structure") + if len(b) < at + 4 or be16(b, len(b) - 2) != 0xFF11: + raise Refuse("missing_structure") + if lcod != 0 and lcod != len(b): + raise Refuse("length_mismatch") + structure = None + if len(cdt) >= 3 and cdt[0][1:] == [1, 1] and cdt[1][1:] == cdt[2][1:]: + structure = {(1, 1): "4:4:4", (2, 1): "4:2:2", (2, 2): "4:2:0"}.get(tuple(cdt[1][1:])) + return { + "profile": "0x%04x" % ppih, "level": "0x%04x" % plev, "width": wf, "height": hf, + "components": cdt, "structure": structure, "colour_transform": cpih, + "decompositions": [nlx, nly], "precinct_width": cw, "slice_height": hsl, + "wavelet_precision": bw, "fractional_bits": fq, "quantizer": qpih, + "long_header": lh == 1, "raw_mode": rl == 1, "cbr_bytes": lcod, "cap_bits": cap_bits, + "rtp": { + "profile": PROFILES.get(ppih), "level": LEVELS.get(plev >> 8), "sublevel": SUBLEVELS.get(plev & 0xFF), + "depth": max(c[0] for c in cdt), "width": wf, "height": hf, + "sampling": "RGB" if cpih != 0 and structure == "4:4:4" else None, + }, + } + + +def jxs_video(b): + if len(b) < 20: + raise Refuse("truncated") + n, h, w, num, den = struct.unpack("<5I", b[:20]) + if 0 in (n, h, w, num, den): + raise Refuse("framing_invalid") + at, first, lcods = 20, None, [] + for _ in range(n): + if at + 4 > len(b): + raise Refuse("truncated") + ln = struct.unpack(" len(b): + raise Refuse("truncated") + c = jxs_codestream(b[at + 4:at + 4 + ln]) + if c["width"] > w or c["height"] > h: + raise Refuse("framing_invalid") + if first is None: + first = c + lcods.append(c["cbr_bytes"]) + at += 4 + ln + if at != len(b): + raise Refuse("trailing_bytes") + constant = lcods[0] if all(x != 0 and x == lcods[0] for x in lcods) else None + return {"frames": n, "size": [w, h], "fps": [num, den], "constant_bytes": constant, "first": first} + + +# ---- H.265 (§4) -------------------------------------------------------------------- + +def h265_ptl(r, present, max_sub): + out = {"idc": None, "tier": None, "compat": 0} + if present: + r.u(2) + out["tier"] = r.u(1) + out["idc"] = r.u(5) + out["compat"] = r.u(32) + r.skip(48) + out["level"] = r.u(8) + flags = [(r.flag(), r.flag()) for _ in range(max_sub)] + if max_sub > 0: + r.skip((8 - max_sub) * 2) + for p, l in flags: + if p: + r.skip(88) + if l: + r.skip(8) + return out + + +def h265_hrd(r, common, prev, max_sub): + nal, vcl, sub = prev + if common: + nal, vcl, sub = r.flag(), r.flag(), False + if nal or vcl: + sub = r.flag() + if sub: + r.skip(19) + r.skip(8) + if sub: + r.skip(4) + r.skip(15) + for _ in range(max_sub + 1): + general = r.flag() + within = True if general else r.flag() + low_delay = False + if within: + r.ue() + else: + low_delay = r.flag() + cnt = 1 if low_delay else r.ue() + 1 + if cnt > 32: + raise Refuse("reserved_value") + for present in (nal, vcl): + if present: + for _ in range(cnt): + r.ue() + r.ue() + if sub: + r.ue() + r.ue() + r.skip(1) + return (nal, vcl, sub) + + +def h265_vps(rbsp): + r = Bits(rbsp) + r.u(4) + internal = r.flag() + r.flag() + max_layers = r.u(6) + max_sub = r.u(3) + if max_sub > 6: + raise Refuse("reserved_value") + r.flag() + if r.u(16) != 0xFFFF: + raise Refuse("reserved_value") + base = h265_ptl(r, True, max_sub) + ordering = r.flag() + for _ in range(0 if ordering else max_sub, max_sub + 1): + r.ue() + r.ue() + r.ue() + max_layer_id = r.u(6) + sets = r.ue() + if sets > 1023: + raise Refuse("reserved_value") + r.skip(sets * (max_layer_id + 1)) + if r.flag(): + r.skip(64) + if r.flag(): + r.ue() + num_hrd = r.ue() + if num_hrd > sets + 1: + raise Refuse("reserved_value") + prev = (False, False, False) + for i in range(num_hrd): + r.ue() + common = r.flag() if i > 0 else True + prev = h265_hrd(r, common, prev, max_sub) + out = {"max_layers": max_layers, "ext": None} + if not r.flag(): + return out + r.align(1) + if max_layers == 0: + return out + out["ext"] = h265_vps_ext(r, internal, max_layers, max_sub, base) + return out + + +def inherit(p, prev): + if p["idc"] is None: + p = dict(p, idc=prev["idc"], tier=prev["tier"], compat=prev["compat"]) + return p + + +def h265_vps_ext(r, internal, max_layers, max_sub, base): + ptls = [] + if internal: + ptls.append(inherit(h265_ptl(r, False, max_sub), base)) + splitting = r.flag() + mask = 0 + for i in range(16): + if r.flag(): + mask |= 1 << i + types = bin(mask).count("1") + lens = [0] * types + for j in range(types - (1 if splitting else 0)): + lens[j] = r.u(3) + 1 + ml = min(62, max_layers) + if splitting and types > 0: + used = sum(lens[:types - 1]) + if used >= 6: + raise Refuse("reserved_value") + lens[types - 1] = 6 - used + id_present = r.flag() + ids = [0] * (ml + 1) + dims = [[0] * types for _ in range(ml + 1)] + for i in range(1, ml + 1): + ids[i] = r.u(6) if id_present else i + if ids[i] <= ids[i - 1]: + raise Refuse("reserved_value") + if not splitting: + for j in range(types): + dims[i][j] = r.u(lens[j]) + if splitting: + for i in range(ml + 1): + off = 0 + for j in range(types): + dims[i][j] = (ids[i] >> off) & ((1 << lens[j]) - 1) + off += lens[j] + view_order, aux, views = [], [], 1 + for i in range(ml + 1): + sid, j = [0] * 16, 0 + for s in range(16): + if mask & (1 << s): + sid[s] = dims[i][j] + j += 1 + if i > 0 and sid[1] not in view_order: + views += 1 + view_order.append(sid[1]) + aux.append(sid[3]) + vlen = r.u(4) + view_ids = [r.u(vlen) for _ in range(views)] if vlen else [0] * views + n = ml + 1 + deps = [[False] * n for _ in range(n)] + for i in range(1, n): + for j in range(i): + deps[i][j] = r.flag() + dep = [row[:] for row in deps] + for i in range(n): + for j in range(n): + for k in range(i): + if deps[i][k] and dep[k][j]: + dep[i][j] = True + sizes, listed = [], set() + for i in range(n): + if any(deps[i]): + continue + h = 1 + for j in range(n): + if dep[j][i] and ids[j] not in listed: + listed.add(ids[j]) + h += 1 + sizes.append(h) + add = r.ue() if len(sizes) > 1 else 0 + if add > 1023: + raise Refuse("reserved_value") + for _ in range(add): + for s in sizes[1:]: + r.skip(s.bit_length()) + if r.flag(): + r.skip(3 * n) + if r.flag(): + for i in range(ml): + for j in range(i + 1, ml + 1): + if deps[j][i]: + r.skip(3) + r.flag() + num = r.ue() + if num > 63: + raise Refuse("reserved_value") + for _ in range(2 if internal else 1, num + 1): + present = r.flag() + p = h265_ptl(r, present, max_sub) + ptls.append(inherit(p, ptls[-1] if ptls else base)) + return {"mask": mask, "ids": ids, "view_order": view_order, "aux": aux, "views": views, "view_ids": view_ids, "ptls": ptls} + + +def h265_sps(rbsp): + r = Bits(rbsp) + r.u(4) + max_sub = r.u(3) + if max_sub > 6: + raise Refuse("reserved_value") + r.flag() + p = h265_ptl(r, True, max_sub) + if r.ue() > 15: + raise Refuse("reserved_value") + chroma = r.ue() + if chroma > 3: + raise Refuse("reserved_value") + if chroma == 3: + r.skip(1) + w, h = r.ue(), r.ue() + if w == 0 or h == 0: + raise Refuse("reserved_value") + c = [r.ue() for _ in range(4)] if r.flag() else [0, 0, 0, 0] + bl, bc = r.ue(), r.ue() + if bl > 8 or bc > 8: + raise Refuse("reserved_value") + return p, chroma, w, h, c, bl + 8, bc + 8 + + +def h265(b, multiview): + units = nal_units(b) + + def has(t): + return any(len(u) >= 2 and (u[0] >> 1) & 0x3F == t and ((u[0] << 8 | u[1]) >> 3) & 0x3F == 0 for u in units) + complete = has(32) and has(33) + vps, sps, layers, vcl = None, None, set(), 0 + for u in units: + if len(u) < 2: + raise Refuse("truncated") + h = u[0] << 8 | u[1] + if h & 0x8000: + raise Refuse("reserved_value") + nut, layer = (h >> 9) & 0x3F, (h >> 3) & 0x3F + if h & 7 == 0: + raise Refuse("reserved_value") + rbsp = unescape(u[2:]) + if nut < 32: + if sps is None or vps is None: + raise Refuse("out_of_order" if complete else "missing_structure") + vcl += 1 + layers.add(layer) + elif nut == 32 and vps is None: + vps = h265_vps(rbsp) + elif nut == 33 and layer == 0 and sps is None: + sps = h265_sps(rbsp) + if vps is None or sps is None or vcl == 0: + raise Refuse("missing_structure") + p, chroma, w, h, c, bl, bc = sps + cropped = crop(chroma, w, h, c) + e = vps["ext"] + views = (e["views"] if e["mask"] & 2 else 1) if e else 1 + mvp = bool(e) and any(q["idc"] == 6 or q["compat"] & (1 << 6) for q in e["ptls"]) + if multiview and (views < 2 or not any(x > 0 for x in layers)): + raise Refuse("not_multiview") + out = { + "profile_idc": p["idc"], "tier": p["tier"], "level_idc": p["level"], "chroma_format_idc": chroma, + "bit_depth": [bl, bc], "coded": [w, h], "cropped": cropped, + "layers": min(62, vps["max_layers"]) + 1, "layers_present": sorted(layers), + } + if e: + out["multilayer"] = { + "scalability_mask": e["mask"], "layer_ids": e["ids"], "view_order": e["view_order"], "aux": e["aux"], + "views": views, "view_ids": e["view_ids"], "profiles": [q["idc"] for q in e["ptls"]], "multiview_profile": mvp, + } + return out + + +# ---- H.266 (§5) -------------------------------------------------------------------- + +def h266_ptl(r, present, max_sub): + idc = tier = None + if present: + idc, tier = r.u(7), r.u(1) + level = r.u(8) + frame_only, multilayer = r.flag(), r.flag() + if present: + if r.flag(): + r.skip(71) + r.skip(r.u(8)) + r.align(0) + flags = [r.flag() for _ in range(max_sub)] + r.align(None) + for f in flags: + if f: + r.skip(8) + subs = [] + if present: + subs = [r.u(32) for _ in range(r.u(8))] + return {"idc": idc, "tier": tier, "level": level, "frame_only": frame_only, "multilayer": multilayer, "subs": subs} + + +def h266(b): + units = nal_units(b) + has_sps = any(len(u) >= 2 and u[1] >> 3 == 15 for u in units) + sps, layers, vcl, vps_layers = None, set(), 0, None + for u in units: + if len(u) < 2: + raise Refuse("truncated") + h = u[0] << 8 | u[1] + if h & 0xC000: + raise Refuse("reserved_value") + layer, nut = (h >> 8) & 0x3F, (h >> 3) & 0x1F + if h & 7 == 0 or layer > 55: + raise Refuse("reserved_value") + rbsp = unescape(u[2:]) + if nut <= 11: + if sps is None: + raise Refuse("out_of_order" if has_sps else "missing_structure") + vcl += 1 + layers.add(layer) + elif nut == 14 and vps_layers is None: + r = Bits(rbsp) + r.u(4) + vps_layers = r.u(6) + 1 + elif nut == 15 and sps is None: + r = Bits(rbsp) + r.u(4) + r.u(4) + max_sub = r.u(3) + if max_sub > 6: + raise Refuse("reserved_value") + chroma = r.u(2) + ctu = r.u(2) + if ctu > 2: + raise Refuse("reserved_value") + p = h266_ptl(r, True, max_sub) if r.flag() else None + r.flag() + if r.flag(): + r.skip(1) + w, hh = r.ue(), r.ue() + if w == 0 or hh == 0: + raise Refuse("reserved_value") + c = [r.ue() for _ in range(4)] if r.flag() else [0, 0, 0, 0] + sps = (p, chroma, 1 << (ctu + 5), w, hh, c) + if sps is None or vcl == 0: + raise Refuse("missing_structure") + p, chroma, ctu, w, h, c = sps + return { + "profile_idc": p["idc"] if p else None, "tier": p["tier"] if p else None, + "level_idc": p["level"] if p else None, "frame_only": p["frame_only"] if p else None, + "multilayer_enabled": p["multilayer"] if p else None, "sub_profiles": p["subs"] if p else [], + "chroma_format_idc": chroma, "ctu_size": ctu, "coded": [w, h], "cropped": crop(chroma, w, h, c), + "layers": vps_layers if vps_layers is not None else 1, "layers_present": sorted(layers), + } + + +# ---- LCEVC (§6) -------------------------------------------------------------------- + +TABLE20 = [tuple(int(x) for x in m) for m in re.findall(r"(\d+)×(\d+)", section(FP, "## 6. LCEVC").split("Table 20, types 1 to 50:")[1])] +assert len(TABLE20) == 50, "Table 20 in format-probes §6.5" + + +def multibyte(b, at): + v = 0 + for _ in range(10): + if at >= len(b): + raise Refuse("truncated") + x = b[at] + at += 1 + v = (v << 7) | (x & 0x7F) + if not x & 0x80: + return v, at + raise Refuse("reserved_value") + + +def lcevc(b): + units = nal_units(b) + seq = glob = None + idr = non = pics = 0 + for i, u in enumerate(units): + if len(u) < 3: + raise Refuse("truncated") + if u[0] & 0xC1 != 0x41 or u[1] != 0xFF: + raise Refuse("reserved_value") + t = (u[0] >> 1) & 0x1F + if t == 29: + idr += 1 + elif t == 28: + non += 1 + else: + raise Refuse("reserved_value") + if i == 0 and t != 29: + raise Refuse("out_of_order") + rbsp = unescape(u[2:]) + if rbsp[-1] != 0x80: + raise Refuse("reserved_value") + pay, at = rbsp[:-1], 0 + while at < len(pay): + st, kind = pay[at] >> 5, pay[at] & 0x1F + at += 1 + if st <= 5: + size = st + elif st == 6: + raise Refuse("reserved_value") + else: + size, at = multibyte(pay, at) + if at + size > len(pay): + raise Refuse("length_mismatch") + body = pay[at:at + size] + at += size + if kind == 0 and seq is None: + seq = lcevc_seq(body) + elif kind == 1 and glob is None: + glob = lcevc_glob(body) + elif kind == 2: + if seq is None or glob is None: + raise Refuse("out_of_order") + pics += 1 + if seq is None or glob is None: + raise Refuse("missing_structure") + out = {"nal_units": [idr, non], "pictures": pics} + out.update(seq) + out.update(glob) + return out + + +def lcevc_seq(b): + r = Bits(b) + prof, lev, sub, win = r.u(4), r.u(4), r.u(2), r.flag() + if r.u(5) != 0: + raise Refuse("reserved_value") + if prof == 15 or lev == 15: + raise Refuse("outside_probe") + at, window = 2, None + if win: + window = [] + for _ in range(4): + v, at = multibyte(b, at) + window.append(v) + if at != len(b): + raise Refuse("length_mismatch") + return {"profile_idc": prof, "level_idc": lev, "sublevel_idc": sub, "conformance_window": window} + + +def lcevc_glob(b): + r = Bits(b) + planes, rt, tt = r.flag(), r.u(6), r.u(1) + chroma, bd, ed = r.u(2), r.u(2), r.u(2) + tsw = r.flag() + r.flag() + r.flag() + temporal, up, l1 = r.flag(), r.u(3), r.flag() + r.u(2) + r.u(2) + tiles = r.u(2) + r.u(2) + r.flag() + r.flag() + if planes: + r.skip(8) + if tsw: + r.skip(8) + if up == 4: + r.skip(64) + if l1: + r.skip(8) + if tiles > 0: + if tiles == 3: + r.skip(32) + r.skip(8) + if rt == 0 or 51 <= rt < 63 or up > 4: + raise Refuse("reserved_value") + if rt == 63: + size = [r.u(16), r.u(16)] + if 0 in size: + raise Refuse("reserved_value") + else: + size = list(TABLE20[rt - 1]) + return {"resolution_type": rt, "size": size, "transform_type": tt, "chroma_sampling_type": chroma, + "base_depth": 8 + 2 * bd, "enhancement_depth": 8 + 2 * ed, "temporal_enabled": temporal, + "upsample_type": up, "tile_dimensions_type": tiles} + + +def unwrap_lcevcbin(b): + if b[:8] != b"lcevcbin": + raise ValueError("not the container") + at, out = 12, b"" + while at < len(b): + size = struct.unpack(">I", b[at + 2:at + 6])[0] + out += b[at + 6 + 16:at + 6 + size] + at += 6 + size + return out + + +# ---- MHAS (§7) --------------------------------------------------------------------- + +RATES = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, None, None, + 57600, 51200, 40000, 38400, 34150, 28800, 25600, 20000, 19200, 17075, 14400, 12800, 9600, None, None, None] + + +def escaped(r, n1, n2, n3): + v = r.u(n1) + if v == (1 << n1) - 1: + a = r.u(n2) + v += a + if a == (1 << n2) - 1: + v += r.u(n3) + return v + + +def mhas(b): + try: + first = escaped(Bits(b), 3, 8, 8) + except Refuse: + raise Refuse("no_signature") + if first not in (1, 6): + raise Refuse("no_signature") + packets, at = [], 0 + while at < len(b): + r = Bits(b[at:]) + t, lab, ln = escaped(r, 3, 8, 8), escaped(r, 2, 8, 32), escaped(r, 11, 24, 24) + s = at + r.p // 8 + if s + ln > len(b): + raise Refuse("truncated") + packets.append((t, lab, b[s:s + ln])) + at = s + ln + cfg, frames, syncs, configs = None, 0, 0, 0 + for t, lab, pay in packets: + if t == 6: + if lab != 0 or pay != b"\xa5": + raise Refuse("reserved_value") + syncs += 1 + elif t == 1: + configs += 1 + if cfg is None: + cfg = mhas_cfg(pay) + elif t == 2: + if cfg is None: + raise Refuse("out_of_order") + frames += 1 + if cfg is None: + raise Refuse("missing_structure") + cfg.update(packets=len(packets), frames=frames, syncs=syncs, configs=configs) + return cfg + + +def mhas_cfg(b): + r = Bits(b) + pl, sfi = r.u(8), r.u(5) + if sfi == 31: + rate = r.u(24) + else: + rate = RATES[sfi] + if rate is None: + raise Refuse("reserved_value") + if rate == 0: + raise Refuse("reserved_value") + core = r.u(3) + r.u(1) + r.u(1) + lt = r.u(2) + cicp = speakers = None + if lt == 0: + cicp = r.u(6) + elif lt < 3: + speakers = escaped(r, 5, 8, 16) + 1 + profile = level = None + if 0x0B <= pl <= 0x0F: + profile, level = "low-complexity", pl - 0x0A + elif 0x10 <= pl <= 0x14: + profile, level = "baseline", pl - 0x0F + return {"profile_level": pl, "profile": profile, "level": level, "sampling_rate": rate, + "core_frame_length_index": core, "speaker_layout_type": lt, "cicp_layout": cicp, "speakers": speakers} + + +# ---- AVS3 (§8, §9) ----------------------------------------------------------------- + +def avs3_units(b): + i = 0 + while i < len(b) and b[i] == 0: + i += 1 + if i < 2 or i >= len(b) or b[i] != 1: + raise Refuse("no_signature") + starts, j = [], 0 + while j + 3 < len(b): + if b[j] == 0 and b[j + 1] == 0 and b[j + 2] == 1: + starts.append(j + 3) + j += 3 + else: + j += 1 + return [(b[s], b[s + 1:(starts[k + 1] - 3 if k + 1 < len(starts) else len(b))]) for k, s in enumerate(starts)] + + +def demulate(b): + bits = [] + for i, x in enumerate(b): + n = 6 if i >= 2 and b[i - 2] == 0 and b[i - 1] == 0 and x == 2 else 8 + bits.extend((x >> (7 - k)) & 1 for k in range(n)) + while len(bits) % 8: + bits.append(0) + return bytes(int("".join(map(str, bits[k:k + 8])), 2) for k in range(0, len(bits), 8)) + + +def avs3_video(b): + us = avs3_units(b) + if not us: + raise Refuse("no_signature") + seq = next((p for c, p in us if c == 0xB0), None) + if seq is None: + raise Refuse("missing_structure") + if us[0][0] != 0xB0: + raise Refuse("out_of_order") + r = Bits(demulate(seq)) + + def marker(): + if r.u(1) != 1: + raise Refuse("reserved_value") + prof, lev, prog, field = r.u(8), r.u(8), r.flag(), r.flag() + if r.flag() or r.flag(): + raise Refuse("outside_probe") + marker() + w = r.u(14) + marker() + h = r.u(14) + chroma, sp = r.u(2), r.u(3) + ep = r.u(3) if prof in (0x22, 0x32) else None + marker() + ar, frc = r.u(4), r.u(4) + marker() + lo = r.u(18) + marker() + hi = r.u(12) + ld, tid = r.flag(), r.flag() + marker() + bbv = r.u(18) + marker() + dpb = r.u(4) + 1 + if w == 0 or h == 0 or chroma == 0 or sp == 0 or frc == 0 or frc > 13: + raise Refuse("reserved_value") + intra = sum(1 for c, _ in us if c == 0xB3) + inter = sum(1 for c, _ in us if c == 0xB6) + if intra == 0: + raise Refuse("missing_structure") + return {"profile_id": prof, "level_id": lev, "progressive": prog, "field_coded": field, "size": [w, h], + "chroma_format": chroma, "sample_precision": sp, "encoding_precision": ep, "aspect_ratio": ar, + "frame_rate_code": frc, "bit_rate": hi * (1 << 18) + lo, "low_delay": ld, "temporal_id_enabled": tid, + "bbv_buffer_size": bbv, "max_dpb_size": dpb, "pictures": [intra, inter], + "sequence_end": any(c == 0xB1 for c, _ in us)} + + +def avs3_audio(b): + r = Bits(b) + try: + sync = r.u(12) + except Refuse: + raise Refuse("no_signature") + if sync != 0xFFF: + raise Refuse("no_signature") + codec = r.u(4) + if codec == 1: + raise Refuse("outside_probe") + if codec != 2: + raise Refuse("reserved_value") + if r.flag(): + raise Refuse("outside_probe") + nn = r.u(3) + if nn > 1: + raise Refuse("reserved_value") + prof = r.u(3) + if prof > 2: + raise Refuse("reserved_value") + sfi = r.u(4) + if sfi > 8: + raise Refuse("reserved_value") + r.skip(8) + out = {"codec_id": codec, "nn_type": nn, "coding_profile": prof, + "sampling_rate": [192000, 96000, 48000, 44100, 32000, 24000, 22050, 16000, 8000][sfi]} + if prof == 0: + out["channel_number_index"] = r.u(7) + elif prof == 1: + bed = r.u(2) + out["soundbed_type"] = bed + if bed == 0: + out["objects"] = r.u(7) + 1 + out["bitrate_index_per_object"] = r.u(4) + elif bed == 1: + out["channel_number_index"] = r.u(7) + out["bitrate_index"] = r.u(4) + out["objects"] = r.u(7) + 1 + out["bitrate_index_per_object"] = r.u(4) + else: + raise Refuse("reserved_value") + else: + order = r.u(4) + 1 + if not 1 <= order <= 3: + raise Refuse("reserved_value") + out["ambisonic_order"] = order + res = r.u(2) + if res > 2: + raise Refuse("reserved_value") + out["resolution_bits"] = 8 * (res + 1) + if prof != 1: + out["bitrate_index"] = r.u(4) + r.skip(8) + return out + + +# ---- IEEE 1857.11 (§10) and the projection descriptor (projection.md) --------------- + +def nic(b): + if len(b) < 13: + raise Refuse("truncated") + ids = int.from_bytes(b[0:3], "big") + h, w = struct.unpack(">II", b[3:11]) + fmt = int.from_bytes(b[11:13], "big") + prof, depth = ids >> 21, (fmt >> 11) & 7 + if prof > 2 or fmt >> 14 or depth not in (1, 2) or h == 0 or w == 0: + raise Refuse("reserved_value") + return {"profile_id": prof, "level_id": (ids >> 15) & 0x3F, "model_id": (ids >> 12) & 7, + "parameter_set_id": (ids >> 5) & 0x7F, "quality_id": ids & 0x1F, "size": [w, h], + "bit_depth": 8 if depth == 1 else 10, "picture_format": (fmt >> 8) & 7, + "slice_size": [1 << ((fmt & 15) + 6), 1 << (((fmt >> 4) & 15) + 6)]} + + +DEG = 1 << 16 +PROJ = ["rectilinear", "equirectangular", "half-equirectangular", "fisheye"] +STEREO = ["mono", "side-by-side", "top-bottom", "layers"] + + +def projection_read(b): + if len(b) < 4 or b[:4] != b"WPJ1": + raise Refuse("no_signature") + if len(b) != 40: + raise Refuse("length_mismatch") + if b[4] != 1 or b[5] > 3 or b[6] > 3 or b[7] > 1 or any(b[34:]): + raise Refuse("reserved_value") + az, el = struct.unpack("o>lpzWS006XD3ibd1 literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/avs3-audio/no_sync.bin b/wai/format-probe-conformance/gen/avs3-audio/no_sync.bin new file mode 100644 index 0000000000000000000000000000000000000000..d003bd2728c38d1818c08efd446975847f6ab042 GIT binary patch literal 31 RcmezOiNS#(lpzWS006XC3ibd1 literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/avs3-audio/profile_3.bin b/wai/format-probe-conformance/gen/avs3-audio/profile_3.bin new file mode 100644 index 0000000000000000000000000000000000000000..5eec014d1d8807350770e38539b4225fec9c0792 GIT binary patch literal 31 RcmezWiOqp&1w#}L006zn3sL|8 literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/avs3-audio/rate_index_9.bin b/wai/format-probe-conformance/gen/avs3-audio/rate_index_9.bin new file mode 100644 index 0000000000000000000000000000000000000000..f4bbddfe3c31056623b83cb8571dbfca30c373a1 GIT binary patch literal 31 RcmezWiBW+elpzWS006NQ3fKSu literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/avs3-audio/resolution_3.bin b/wai/format-probe-conformance/gen/avs3-audio/resolution_3.bin new file mode 100644 index 0000000000000000000000000000000000000000..046f3c79e1b54ea3f1a1ef7f90e3511123f72987 GIT binary patch literal 31 RcmezWiNS%PlpzWS006hP3l{(Y literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/avs3-audio/truncated.bin b/wai/format-probe-conformance/gen/avs3-audio/truncated.bin new file mode 100644 index 0000000000000000000000000000000000000000..b206bed0023a35f5fc591f0a2253d5c8f47add14 GIT binary patch literal 3 KcmezWi2(ov@U|`(Lz|a5yTf_zO literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/avs3/library_stream.bin b/wai/format-probe-conformance/gen/avs3/library_stream.bin new file mode 100644 index 0000000000000000000000000000000000000000..3dfbdf0b12212721ab0ae2fb48f3716bd63968e8 GIT binary patch literal 3061 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 F0kIJFtt literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/ieee1857_11/depth_code_0.bin b/wai/format-probe-conformance/gen/ieee1857_11/depth_code_0.bin new file mode 100644 index 0000000000000000000000000000000000000000..747fb8c26ae30c6a2b88e552f220c91a7201d919 GIT binary patch literal 18 UcmZQ*U;qLKFjit@5Ku4x01pEJga7~l literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/ieee1857_11/main_10bit.bin b/wai/format-probe-conformance/gen/ieee1857_11/main_10bit.bin new file mode 100644 index 0000000000000000000000000000000000000000..0a6d26d35ac34d7ebf5cb1382e548302d2416927 GIT binary patch literal 18 ZcmY!sQDtCYv0z|eZxB>sWDrm=000jI0kQx9 literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/ieee1857_11/profile_3.bin b/wai/format-probe-conformance/gen/ieee1857_11/profile_3.bin new file mode 100644 index 0000000000000000000000000000000000000000..159ee506896062099a2a646a5b44f0105df124a8 GIT binary patch literal 18 VcmYdjU;qLK5av)~WDrm=000&H0qg(( literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/ieee1857_11/top_bits.bin b/wai/format-probe-conformance/gen/ieee1857_11/top_bits.bin new file mode 100644 index 0000000000000000000000000000000000000000..6e3591b9120f3354a0f2fc35fb917a297583d961 GIT binary patch literal 18 VcmZQ*U;qLK5cW`FWDrm=000pC0n7jZ literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/ieee1857_11/truncated.bin b/wai/format-probe-conformance/gen/ieee1857_11/truncated.bin new file mode 100644 index 0000000000000000000000000000000000000000..fa6fb91f704f489ed7add7775d1f9c8c57846774 GIT binary patch literal 12 PcmZQ*U;qLK5as{?2b2K` literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/jxs-video/four_frames_stated.bin b/wai/format-probe-conformance/gen/jxs-video/four_frames_stated.bin new file mode 100644 index 0000000000000000000000000000000000000000..c60a18e2a36d8ce37aa029da95277242dae6ac00 GIT binary patch literal 3488 zcmc&%&uiR96n>)}$CG50Xl*BPdnl^t!59j`f%GbZvMU!K9EaR{F8w2378NEYg#?;H zZ;MI6=++Q=UWy$|>O&i7&Z5vu|B3DUA!)UZLz^Dj0ev(3^z`)2H*el(EdX4nE2ZlL zx(r=t0cO!`7p%kC3cL?saVxc!oY>X&tXnH!FP*J|a|4&SWVQx3d(aDe{oZ@Qa&Rti z!61l&6|-cl>6y#sk_o_=b#v9MnQP{XS%r&m0nX22pmzb>?Q@qLjwfVYhoAPJ?tsVK zpP)wwzyXf4fE@vAWaD&WV4(*Q;2^a$pdB96jK0C8ECF)XqB*SDPW{)RYJi>~xb zEfu{tg4DRd;0+H17>1WMSblDsHGV9MKcQtC zrgiLr^r)Gv^eTFwSt}hQ8*1rjJPNPUPZ5__?C4&!16l6zgzizRG^FxsOnPIEYHx8A z661KYEK&E@(MC?I)d-++w$2o#qN{lmr#3h2;%vaO{kJt}ug{sBlm`fX+9fYuf17$& zIPmwaclRUNcsTZ>E>9V&V$(R$MLBCetokZBk2$5!k?O_G*t^VOqG9vo{25`{REb>? zW!&OlLO%YK>b3WjdQvd+QFjUTp-KXDQSB|(HK<|fhiJ1b3F3BUQYFl(#L=fJ8Fx|b zH7?emaY@^R)h(aBCbll4EWyGLYjtyT7H27dq%OBg5EpSfyhw(54f-Osne(_6JyPEB zzpFuXW&oWT6F5%`_UhNjK|xu)?381*$bo*TrHe?`PG*n79{rS^$2oAkL^UYKh_*a6 zuC@+7%k&r4HoY@PwKv$-pgsron>E+p4<6;G-sMQaRRMe`ed?vdvrZ2wJ-pC$4N|@7 zO?enn@U{lMyn{OqQ(xSYSW^nd3xuDLQ#02}#$80Qv^bO#NyOzE^z?3;c}vNVHd?TI z>rLZ|u0=$#w3vr0WN^G(e0uH|KngT_p`fEfNVbzA--@n9M6opZf7GD6{Uv}niO0=h zGMV~<_Q}?D-P9|lDV98)$TVL++29^I(8&^EMn@;LN)Q)u7;<>3(<~BsPxpM5k>;Fv z({;^?cawd{TQ8R6kceMic<6kh$7%b~(r{)nl`!feilxQvaG4AekyGQron1deg1@M5M^BuWAQU>Lydn=dfM&Wnlnsa z%E`{H62wKkt-G5yA!p!=55d~ZY>JZ}5wX^xv>T+V?{kMTZgE?_g4&IT$B$EIPtX{* zphRjrk5w}6B8sKO5vPF4%Pdba`h>?7O|NNwTj!a+!-~yoP(XnXH}_TCKfZYa`H}a1 zXQpWq>-$xPx?+kF|wfy{Bw9Q$H`8Ug}=Tt8+Mf6HkYv<(3JJ9AYcs`L2c{A|0 z)xq~tG2Hr;tV}}+hH}E%q&McM_7?NzZwAU4kH72!pKthgMNRY0b6jYH$#^#S6Q?)) A9smFU literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/jxs-video/frame_cut.bin b/wai/format-probe-conformance/gen/jxs-video/frame_cut.bin new file mode 100644 index 0000000000000000000000000000000000000000..3eee866efce05781ae774514b0f6eb6bc8c9000f GIT binary patch literal 3481 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 A{Qv*} literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/jxs-video/zero_frames.bin b/wai/format-probe-conformance/gen/jxs-video/zero_frames.bin new file mode 100644 index 0000000000000000000000000000000000000000..6a82df85150a6467ece9fae6b6e19102e5695406 GIT binary patch literal 3488 zcmc&%&1>976n~?YTN?004LBO6dB4 zE<+dk0F!XC1HQ${61)%K<7VRf?MFg5liP7I9Fgh~6fa*x*V=REFv`7} zFAK_gazjB?06&5rCq?}plyQSw!6F&f3u0}F^;bWH-4lndv$?IrBVT&zxS?Qq0oC5% zAg}<@F$WcKPhWubGv}J;S(B)}F8?6pjJl^j*!&DbC7BrM>n^>(Ps2C-=st?LB;IbY z1`r*N4lAy|okHeY2N_DtF}7#U$xQi|L#pdJ##w0%-Gv02nPtHWy-U<(A%6TAvol(@ zep1EmNspSzO0T2`>b24_vZ0cW#)IG%{STWn^N>^e8mXS&i=0g@#u_$@7cL3Qrc&&R zDB}kI67uP%RIi<*)RTgl4cben50w(2jcRYOszEhN*GE51V=roECRM_mOB{WwlyMu? zUg2yF8WyxoSl!~qTViWd$`Z_7zfw0dmvNQ?Na}Jc1#uR)g6m|M)}SvUKeY}w!zao+ z`gb*m&J3V4V+0GdU~hg6E##Ed^Hw>QvmEG`TH1(YZDsZ#=+IBud7J~Mb5w&e3~9?# z<7#W+vs8a!Wz#!TRC|q04eD}Ww^4EZ{pd+{?req>Tou5_(kD(jyzKOl(!&c~)gaZI z-joFa1#fE5>s{Qon7I7D#2QmDULgF0oa(ujGHxS^rNO?ONFpxRpy&HZ>U>Iuq}GDn z8*dtybj>1)rNJy%B7^1S;?wgm2U4Kk3ppJfLb90@`IdCeB8sKP|Dy)&cjo}2I2zW6 z$!P3y+9#WLbW<;xx>)jbLQ{YJq`d=jppzxUl#Whnr6A5?Kj83Gr&%QO9`Cs{CCxeW z#%r1tSChTZTQ8F2kceL{KDI8_C?peXK0L_ zQ6lkMkCifRBZ{TL0jGe<%Ph}Q`h>?NO|NnGKL3Bz~Yv*Y73bgqPo=@as-VFS0 zb@aVd47)ZaE7OpIp`5TX>5VC>y}_*hn}Kr1<1ahF=NtZAQPaG092c5kGM@DQ1aPljA6n=Mh>BZQ^ZW2gSD!C;?m8nu;MK@T{p+tZpbP9gBhjJ^urMIe$?6|5@9u08AW;%W^1Zx!_xZc;eRp}7*TDsuZ$lH{7Chpv3mks{ zuH&-W5c)i?0`nqo-`me?@YvaM0>`HndYgK5hc;-F03n2uDyflea*y04b%@{&gow*v z1000ig!`}ydwGmGeENQf@AD67I)VUV_<8ej)B~xMtIiZtNtPwuJFvVvC9yB05Nf@HylQ3Eq z;h3!xi$HJ2f-(&{7-_1CJ~&zj25Ymj-e`HIO@HR2g<{)2Erh(edM;e1Kn#Uk1qhuO0_QwRaAzS5IG&v1J6&J9>hO=qUv^&z z!@9$zl|e?ucwDZ*FljA6nP9gBhjJ^urMIe$?6|5@9u0GAW<7f^1Zx!_xZc;eRoAv9D)Z}tV0Xn7CaK32ZDY8 zp6l_(2nM34gYU=wj=xhh;IX^rhHl{0oZF7?Y&uP+#egy9usUn7b#{;4Wg8H~9f$~* z!wR?ry9)PV3$}}da`X&>h~6h3vTO_?B=Ga{S~?M3T?~2o&#_|? zmsvYE=Pkn~KyvJBc#nACTaR;f01kno-0fEZzYd7z^QuM$jKYkbG5FAg>?q}gN>9q2 zs))vXnOKH;GZvI<(BW89RnCL`Wnic_uj-A)MD=OlQi>!dz5!{ReeOZP`8TDoEih@9 z^;(n3UToTO1wxZ~S)hl1;$FBxNyk9fi1q136!0^J%6&MYc1O@gk5#>!v;KcXn|Ts$ zQr|3Ptp@)1MeIgWDL@TFJd7`{cYvw2D?o;Iin0?eghoC8B=~5Gila8+)#j|_(!X-E z^E0v0n4119APXh7eOd^-JU^5kS0G0cZvsS4j6etxr?j&OhJsE``JG;%UA6Z|tla3*~s)hL45>k@J zs^WShU1vXOG$soVf^ACsS%=!UMn3+2dhsHqyJD(dswr|+Yue*HN~_~+8|lr0EspJ7 zvHME9V})9P@ALuWXhDzDuGLs9#b27<{9 literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/jxs/cpih_2.bin b/wai/format-probe-conformance/gen/jxs/cpih_2.bin new file mode 100644 index 0000000000000000000000000000000000000000..a09ffc91487b9d46bdcc788b74755c91a4c852ea GIT binary patch literal 1536 zcmb7EF>ljA6n=Mh>DAc9Zc|88D!C;?m8nu;MK@T{p+-Yjm-AMsRWI1mdGLX1&| z3cN9}N1{a;U|~R_lhr?B-rdGd?(=uw`|irPJOCfCT!9wAEqEk-A0+(% ze9sq+A@9p_6-1B(TftV@fXCjl7kMFH;kFaxfDwsv!sB2WnD9q^@K>$t24>Lii^kjn9 zMKl)k#40kIaiGG04n~Hm<_~t~fuY)>uD2Q!)u*9HDUz7@hNN-+c^^U{zG;nZiD|p4 zH=2xh65Ezb5SlEi0yF#*??-Et3xU*8h*_GEbs) z>YJl%)W9FVh}}?W4QOD9hxNtv4zRUu1*mA1qWsthVNlOLNim$D7HCX(xjARK?62JP z{8Vf;rlvm&$wH}XpAiBt&JL6>G^nA%n*fm$V~|oN8SN~FfuxgDeP=&3uG;w{@|TV0 z%ChcCbTVL4jeGSP8jI^<=i^I}PY>fBWJ==%tEE;5G;Y5EoU5^*X~&XXG(voBNu@|* zU30yWuG62i8k2?l;U=YnyhH6<{9 literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/jxs/lcod_off_by_one.bin b/wai/format-probe-conformance/gen/jxs/lcod_off_by_one.bin new file mode 100644 index 0000000000000000000000000000000000000000..2d2613d2a6971cab3a455f9360f067b5df7d41c1 GIT binary patch literal 1536 zcmb7EF>ljA6n=Mh+l#S_-K3DFRB}s(DpRGxif*u?Ly4e@)I9?uKjKtnI8YZPgczd^ z6?kJ{k3@?yz`}q;C#!$Nyt}hWfJ8|g$@lW^-RJMV_ub`TegZDYd*O(aUEufw za2=P`2GHYq6_^)!``&(DgD1|W6F5Gt&^y$lJG4$41PCFNR7s6&k^AHx*@g)2LWsBw z*1&;v*nkJH3wwEtIedD4i0_LJX*z@eV)%Lcao7c^l&j7aQ{)H;E*!Zf$=MMOceN##{xs01d zz1C!O7@4+AKyWfE3iR+#+zGZZNey%bSzq3SKD$yyx(lD~4( z^HZ_W7@PjwM+?QaeOd^4dwn8Yra%mYTm}f87y{=!N^oZ(^f{iK;yWE*yXx?d$X|9} z2*bL~rIkTO#dutljA6nP9gBhjJ^urMIe$?6|5@9u0GAW;)X^1Zx!_xZc;eRoAv9DxT|tivY2EqEk64+Q-H zJlErmA@oI22kyuIuD@F};IX^rhHl{0oZF7?Y&uP6iveTIVRhDE>+Bx8%Qhf}I}i~r zhZS%Mb`|c!HtZA$<>=`L5xq}8WZ4KpNZ{wy$59VZDNnstT(JXSv~(;wx)}2EpJT@) zF0*!S##@F>faJv2@E-BNw;t!}5F7$Ux!0=#eiIPQ=T(gi7=;-TDVm7bJ4 zRS}K(GO-NxW-KVzpo5X7s+$N81{n)hS8iXeEvOo|2#NBX%l8%9{5$m(dDB$M`mHTi^?T(;@9;y0pJHXW16(GYpMcJtqLZe=M5_~v8#ZjB^dUMut>0i0& z`Kj1wOih0lkcAT4J}m@ZT^vb|E09BpivW=mBM?HwDeWwRfuNI9ey1B~SMC21`OEfm zX;^mzS{X1d$HQt3jrmQn_3^dH=f_bGQmJr)+~m$#$@4sutRA-Yg7By$j9H$E?=Z{S4`APH9@XwO?#Y&X?2`!BfV*``LVq# z_FidstWXQ^oj!mZE$B(wu^Nk|_)F6}QiDLvUD1AT?~0Va!TDThCZ9hm!v;V?HYQM` TaW+7MN}|{;tNDv(Ll^OHYQ`4^ literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/jxs/ng_3.bin b/wai/format-probe-conformance/gen/jxs/ng_3.bin new file mode 100644 index 0000000000000000000000000000000000000000..a22342562573fe819138be800c9e5876af9a51c7 GIT binary patch literal 1536 zcmb7EF>ljA6nP9gBhjJ^urMIe$?6|5@9u0GAW<7f^1Zx!_xZc;eRoAv9D)Z}tV0Xn7CaK32ZDY8 zp6l|)2nM341NUQp$KNR$@Yr2*LpN}0&TYqcHl3!^V!#-4Se-T4I=jd2vJHsg4n%~@ zVFg@*U4{Fw1=~eJIeG>`MDLRiSvH0c68L%faoh(~%2TfuS8N{`EuDz2E{44P=h!ic z%dDN7^Oj)~AUXCmyhl9nt;e}K0Ea+P?)IyIUk60y2fJHXW16(GYpMcIiKLZhC45_~j8#ZjB^YID|d>0i0o z`I*>gOih0lkcAT4J}m@Zo*zn&E080JHvu9iMj(WUQ`%VsLqR8}{7x^>uG;$}@|UgW z(y;Cdv@&2^jtA8m8uRO7>*Fht&yJ!#q*CD&%cVvL)Na24oUJjSNym^~)k1u22`Ncq zRdKzMuCt#s8k2=5;xA3_NDTrtcSYyDy(?1w1{ZUonSAlA3>yFm*_c3$ U#@P@JDv4sZtQIex4_(B+0czM61^@s6 literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/jxs/no_eoc.bin b/wai/format-probe-conformance/gen/jxs/no_eoc.bin new file mode 100644 index 0000000000000000000000000000000000000000..e7ef53ff04e6a26c307dd0616c0a518a00564521 GIT binary patch literal 1536 zcmb7EF>ljA6nP9gBhjJ^urMIe$?6|5@9u08AW<4e^1Zx!_xZc;eRp}3AA<*&Z$cB`7CaK32ZDY8 zp6l`Y5c(po0r#u^p1+sZ;jz2nhHl_goZF7?Ya$$&BDuo|ngO?HpnWm`~%I}i~r zhc$2sb{+1+4(#SJ<>=`L5xvhor0EDkh~ek;$59VZDNnstT(Lu7v~(;wx)`$JpJT_k zT4e3qoVN@c0P(4>;XUGkZ#~Y{5jX^ja=%vwd>IhUXJw5H7=a`~0gQ{uE6oe+TqCgM-#NBXgOih0lkcDE~J}m@ZUmi=3E09BpHvu9iMj(W!CbY8%27*pb@ttmXl1}^F&>p`Xv~(y*2hzkFHWK!BvRo7i={>g)Na24oUJjSaodnx*Ft=43Molr zRdT(NuCt#s8k2?l!7io!v`y{XARm7}zj~3-T`^HF)dab!H0*I6CgpLqjr3;07RUCk z*ng$nu|_SxclrQww4kR+$7(EA;xA3_NDTrtcSZZXy(<#_1{ZUonQZZ_4C??f*_c3$ T#_0eJDv4sZtQIex51qqraHljA6nP9gBhjJ^urMIe$?6{o@9u08AW<4e^1Zx!_xZc;eRq>%@Bk;9&;YmvkA&xepdWze zdb~D(o|sgD`?0_0?@em(*xhhLH*hM>ZO3=Eox0Otz!-B_mDSiLyT|UbEr{U`M1;#> z4P1gmm^6!e zt;y&hHf@=L&}3c|=;5EZ6K+w`G0+uaeSQ@M{6e8}AC9Qq5j4?bW$)&!{~yt2o`l=f zH%nQofj@o`yMa^+P{R-ppko z6B~`G>CXbPP-5Gsg~02}W9e}Pav*zU2Y*EVvh!RT z)@^}S28@gGs9Zy1zAUyro{D^N5_KV!3ddM1HA0|v`wif1jR8$shU~f);%h@lNgAt? z>y31s{iM;DEZhrrDeY%1YTpL=`1|?Qi>>0}Bg-0}wJWumAvR9t9Nu literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/jxs/unknown_marker.bin b/wai/format-probe-conformance/gen/jxs/unknown_marker.bin new file mode 100644 index 0000000000000000000000000000000000000000..38e92e1ab28ad16c569730ee73c66c3f31f16b8b GIT binary patch literal 1540 zcmb7EF>ljA6n=Mh>BZQ^Zc<27D!C;?l_^qTMK@T{p+rzc>Yjm-A91QO9Eb%8A;zdf z1>P9gBhjJ^urMIe$?6|5@9u08AW;%W^1Zx!_xZc;eRp}7pMVQ8--ZUjEqKIT7dZX^ zT*qa#A@q6P2AWrad69SE9pp85>})xK$E|D5JE|n)W|luNA8jxh~N%{ zh|ORF9E9G4`>+T5d5k%H`hJM-^ABk{f&gOpdGm491F4j&UMr?Z8wf5P^NucttoTRm z7)M3c&dqs?pbik9c^ckD9`dcrm^ucEpi&(6%785c!uhPM5kA2%#b*Q_)FC}h7^32n zFj^Mjn5`3wKySu^G7UNyX{w4oXs-ihC7WEv z&7xjwGCqn-Tc#j5nH2?k_$TfLJD8*fx`M1Pu0x+)Dyf`@V{CT_P3e-dcXQVNk7zSb zf?e#JrL5JEKYk&*p->8>hCv?27t0-BYV8UTK^3F)TnnL5uRd`$oJhr_HsN%0)^f>T zx!L)d*l3JRf99iwV%t6~guJ;r5iV08hC;3agiZ{Ba~>tQvk(RxPfqcjuCHBn^he|` zd(VYo-Qm*8AfsYDF4y3gEsL#>ry^gThCN7xk`q}hH9|=3_7&i4jRB2YhU}&m;%kEo zff}ol>q@%Le$r@+7Vi7|nD){Zwr>l4{QctkMS^$5M7>lK$yB9okMl4okF#y0I}5fr zws*zhEA5UAY=L~I4a?;zyRXyOpqkklZ=@R*ZS_Q literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/lcevc/stop_byte_0x81.bin b/wai/format-probe-conformance/gen/lcevc/stop_byte_0x81.bin new file mode 100644 index 0000000000000000000000000000000000000000..05ebb5ae98773077d6d1f7f894167560cc3d9f6d GIT binary patch literal 15 WcmZQzU|_8N?;zyRXyOpq&<4oI literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/lcevc/window_custom_tiles.bin b/wai/format-probe-conformance/gen/lcevc/window_custom_tiles.bin new file mode 100644 index 0000000000000000000000000000000000000000..05802bd96f5335971379e1cdf250aec8fe63e077 GIT binary patch literal 80 zcmZQzU|_8N|A3t(fsv_6hvlJYU5Mfa);r7!j5-V&j0_A742&5JDM<}N439Y(7?>GD Rfdqrr%mxOazRLfY;sD+)5it5J}Ng{#4XRgs~N!RF!h{|pSK#-m^~1V%$(Gz3ONU^E0qLtr!nhzJ1y Dgc_;5 literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/mhas/frame_before_config.bin b/wai/format-probe-conformance/gen/mhas/frame_before_config.bin new file mode 100644 index 0000000000000000000000000000000000000000..6889f1e1172815ffe52474700e797a56c349782c GIT binary patch literal 14 VcmX@WxYUD*kx7GTOh5_%XSN02 literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/mhas/no_config.bin b/wai/format-probe-conformance/gen/mhas/no_config.bin new file mode 100644 index 0000000000000000000000000000000000000000..802ccd4d49925c17ec46fe43aedebb0d0370591c GIT binary patch literal 9 OcmX@WxRil~0SEvKXaQ>g literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/mhas/packet_overrun.bin b/wai/format-probe-conformance/gen/mhas/packet_overrun.bin new file mode 100644 index 0000000000000000000000000000000000000000..b194a3eb3a59c80add0cfa51a0f1765ce449bcfd GIT binary patch literal 22 ccmX@WxKx9cSCVlBg9oc3Lmh(+kYcO@068TDcmMzZ literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/mhas/rate_index_13.bin b/wai/format-probe-conformance/gen/mhas/rate_index_13.bin new file mode 100644 index 0000000000000000000000000000000000000000..96c81c3d9937ec34042ef47236fbab3b63014b36 GIT binary patch literal 17 YcmX@WxKx9cH-a%m(8mp z*bR>OpO{~I1uT_7uMnPa8K$zSY7b-^f2CRc@hD314J)XLJVFXHNmT)l+H5YuS{EJk0b#3eVtliFKW6yviO~+ajF`u>PNx8^ZjW)f@p383$iHw z5qkp$4@nB;UvEL!?kZJvkxWAzXIivY5R8IS$HJhGuscDut>i?pvNApUa6pxaegu;k zb{7^v&uEpRSLC+CLFH=e&NvX7OBsH&JjAxD1?Djtx2#_B_ z$|~%OI8S^S3t_b*B*blUdidNKpli^wyQIJyQ0TL9WmxuKS#5^4%kCu#V)Wz7yI(Z` z0003(2@ZvlM%Za$Dx}22FSF~-`DfRK5sAyTpkM$100Be^NLzJ*H2|Oh0003d36Ow2 z4q*Pvi1~m1kk9|)hy7YXB%r*T_wzrl=jVV0q660KupP(3y3B7B-`NBoc;@hBsL1_j ziaA;^000010s+Vp${)yc^5;mWk!HgAWxxKzBh6a>}5HpmiTDc0Eqj8us~o;XOVDcnZv=yrLwDv_0G8EuKg>%8Q=!@@aE!jT8hDZ1G8jK4m)Gj1gbl=C&a{IJCu_9Mj#ZE zQwH?_`q+PLH%|qthjYaG^$!ERFX_EOtBZ`W08l&enMA{x1gx6I_EBmmOFCJScXo007C}biu*6k1ME68jq0( z&b!T4A>i~N!~MDibW~qr5CWE9{6HBle&Ki`1^kHVQ)3w7b{Rqc>)RQ6{#td56xPOz z&wxvV7yC)AR{N;8x!1jlNB7Z{4tvW28QXblfFyTs#Zh7;w-_J%v0-}v0003138Dgu LU|?7zz>my;;v2e` literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/mvhevc/no_sps.bin b/wai/format-probe-conformance/gen/mvhevc/no_sps.bin new file mode 100644 index 0000000000000000000000000000000000000000..ba6b2f75db9db6fcd64f4e89b4d73f532ef8b552 GIT binary patch literal 1425 zcmV;C1#bEP0003%0SpoU{{dhC00RI30{{R6000930F;nc{{SEWAsSp52mk;B06ri9 z00RI30|GPw009DkeiWqifB*mh0Ym}CrC1SA3IG5A0VV;X!q{2(@me$QJ`?|!&8s5V z4UYJqm|uDYER{g75T0-urn0GO4`dsErCI#(C`$1SE2xP)LJBiURRNFMY%aoD9jnt` zh}D`GUsM0Xk{nXjvD7Uh9hg8|HT>F&CNHD%*IEcdX(qRCl`31ClvuT0xdJzWC)zTW zxxZ}F_6@T!8FGh0C{TNTJSDrQC$=QA|Nm(MAvYr4htw_%TuEMO=MD`;WMKi8lC-V9 zNrn`fPah%lchJRjg+H$Dw)kDP)R|<)e1erh6kT4n4F!)EM>2KT*=BuWB~($Q8XZ4k;qY5Hzv{HIBJN8nP;R1$jQ=mN&LBHCht_O`e8~<7x<}~0{#PTyMj$lq z3Er<9cWuLrXaGei{;`|Z+hwV^3OuDTDMc`A z}5HpmiTDc0Eqj8us~o;XOVDcnZv= zyrLwDv_0G8EuKg>%8Q=!@@aE!jT8hDZ1G8jK z4m)Gj1gbl=C&a{IJCu_9Mj#ZEQwH?_`q+PLH%|qthjYaG^$!ERFX_EOtBZ`W08l&enMA{x1gx6I_EBmmOFC zJScXo007C}biu*6k1ME68jq0(&b!T4A>i~N!~MDibW~qr5CWE9{6HBle&Ki`1^kHV zQ)3w7b{Rqc>)RQ6{#td56xPOz&wxvV7yC)AR{N;8x!1jlNB7Z{4tvW28QXblfFyTs f#Zh7;w-_J%v0-}v0003138DguU|?7zz>my;WP6@X literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/mvhevc/no_start_code.bin b/wai/format-probe-conformance/gen/mvhevc/no_start_code.bin new file mode 100644 index 0000000000000000000000000000000000000000..01e4b2bf42ee5cb330dd42300adfd1273228d0b6 GIT binary patch literal 1503 zcmV<51t9uB0SpoU{{dhC00RI30{{R6000930F;nc{{SEWAsSp52mk;B06ri900RI3 z0|GPw009DkeiWqifB*mh0YU)*0bl?C0{{R60009300RI3pdf(<`DG?a8)2o0s*@88 zzR#~W<)2;@Mkg-XfPer100Be+#idvgPznG50VV;X!q{2(@me$QJ`?|!&8s5V4UYJq zm|uDYER{g75T0-urn0GO4`dsErCI#(C`$1SE2xP)LJBiURRNFMY%aoD9jnt`h}D`G zUsM0Xk{nXjvD7Uh9hg8|HT>F&CNHD%*IEcdX(qRCl`31ClvuT0xdJzWC)zTWxxZ}F z_6@T!8FGh0C{TNTJSDrQC$=QA|Nm(MAvYr4htw_%TuEMO=MD`;WMKi8lC-V9Nrn`f zPah%lchJRjg+H$Dw)kDP)R|<)e1erh6kT4n4F!)EM>2KT*=BuWB~($Q8XZ4k;qY5Hzv{HIBJN8nP;R1$jQ=mN&LBHCht_O`e8~<7x<}~0{#PTyMj$lq3Er<9 zcWuLrXaGei{;`|Z+hwV^3OuDTDMc`A0VWBMfISXi{>zB@ zfBulq|Ko@KT0tbByqov)Kd$HJfCZuh*6gqy$HKbIZxr9z1Rr?j@MWmT{b-6gS}*_r z009C4$P&sQ$aC`NNT-oz!ue&t{=*~9TL0y1=3;dJZ+62_0Ps)ZziI^~*ScmxIiH$I zFWzH32@{}oA1!u0PSqOr`F!C$J_UFR%%!}dB#E>=+vF{tNH@xhp7Z4>vgX)M#Bo?` z=v4Zg`3XzpeV5ZJ(rWAvS>|D(+&KjNif&*%u(o1+O?bbdI@FgOKbZOb@7gL*!gn!( ztown#gcNBt*y$9EF_kY}4BvQ2Tk0xv-_sLRnHE=32@7VXhJZ{F9G3a4+$~F0c{TLn z{QoBYRsL2U>?zm$KsfN`;&NJw!F&U=WKIq{W7PzzJGCdo#9=#>lKVy=6q8d1^#J2?eH~g{M$9ewYR{D&lJNs#h zQm-s_JMa1xSI5rQc@?H0}xBuN-%6!;EMEMJfKVo7UT9w~Qge zug7xyYz8?955Xe5b1!ONv$9oq=Z_=+`F|ylxUA%!$BS<=k}440?^XT*jh`F z+(6*xRUL1{c&y#dWn<5PBTdJ%UwPJreq%ZT|6Jrl(xy^pmI~(Y`YU7P`gs!B#e6 zZ~CSalg0gK3BUAV{U_lNz#29Lhnn>@A)0!(GNCMk?KIpYX})=2hC>QGr7v5_Ytm;R>zVrQQK7wd&1Pih#{t*zPJ-b&*U%9A{dz zRuGJWQpduekFYyIwXNhtv9dBf`*1*&h<*f<7&`e3noAje zv^>PNss-jcX*;F7QbaiQ6#BBbIabBiod}R0Ldq)ai#Shw7z<&wBP7Iaa(ejO8K7&> zvb&_f8&K%8ab;NcUs-L2w#)7%3S#u*%e!AS00001KmiO9|NjAC0009300RI30{{R6 z005MbR{sDX03jM&7zh9W0{}iC0009300ROv0RRC4fPNID^nd^W00BY)0RdnD00RI3 z0{{R6000930H7d&2l-_tNgH9Mh^mtl48G5=H|3vR6h}5HpmiTDc0Eqj8us~o;XOVDcnZv=yrLwDv_0G8EuKg>%8Q=!@@aE!jT8hDZ1G8jK4m)Gj1gbl=C&a{IJCu_9Mj#ZE zQwH?_`q+PLH%|qthjYaG^$!ERFX_EOtBZ`W08l&enMA{x1gx6I_EBmmOFCJScXo007C}biu*6k1ME68jq0( z&b!T4A>i~N!~MDibW~qr5CWE9{6HBle&Ki`1^kHVQ)3w7b{Rqc>)RQ6{#td56xPOz z&wxvV7yC)AR{N;8x!1jlNB7Z{4tvW28QXblfFyTs#Zh7;w-_J%v0-}v0003138Dgu LU|?7zz>my;EF&CNHD%*IEcdX(qRCl`31C zlvuT0xdJzWC)zTWxxZ}F_6@T!8FGh0C{TNTJSDrQC$=QA|Nm(MAvYr4htw_%TuEMO z=MD`;WMKi8lC-V9Nrn`fPah%lchJRjg+H$Dw)kDP)R|<)e1erh6kT4n4F!)EM>2KT z*=BuWB~($Q8XZ4k;qY5Hzv{HIBJN8nP;R1$jQ=mN&LBHCht_O`e8~<7 zx<}~0{#PTyMj$lq3Er<9cWuLrXaGei{;`|Z+hwV^3OuDTDMc`A00AZmkbpf7VE)U9`G5Y9&;R3x{aQgJpuC&+^FOZV=YR#G1J>-Y9mm4D%x@Im z*#sYW=I~{x$o*)FIa)9P000330mu@{AINj^=SZiKX2SVpzy8A`&07EEYvy8f|8I7~ zPyp~x;=gJICD*!ULOGwBNiW`GJP8w^bssHuJxCoY*Q}Kb>(Z^NK@tgPCR*hEP z*Ik{NLf4gp${4}G000010tuo7vWKK>5n%rEKbMpq@XP@dga7~l0RREW3Mqkshp_x7 z{Wtuv+Q)hR;a2*LraSv-ic+sEc02F-6<5d3)_CjwF9!w_T!R>w9asN6D0Zv>0Lk8T z!NItXE2vExkC6z@yUkW1;PfEF{kjEoR9|8c0+wL>Kp8H6;dmki{D|pOV;JIg8A1Q+ z+ZlTPT6K#Q*2as^fJ=iH`$?@<`>40M*S(5I_tBLOd&>eD+j(n%BzJGcQDP*w7$5wx WVS4}o0096Aq5_IwU|1x;kIaClgRK?- literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/mvhevc/tid_zero.bin b/wai/format-probe-conformance/gen/mvhevc/tid_zero.bin new file mode 100644 index 0000000000000000000000000000000000000000..6d1f2dbc772191f481797dc1ecac38ad6a4c2f2e GIT binary patch literal 1509 zcmV-a%m(8mp z*bR>OpO{~I1uT_7uMnPa8K$zSY7b-^f2CRc@hD314J)XLJVFXHNmT)l+H5YuS{EJk0b#3eVtliFKW6yviO~+ajF`u>PNx8^ZjW)f@p383$iHw z5qkp$4@nB;UvEL!?kZJvkxWAzXIivY5R8IS$HJhGuscDut>i?pvNApUa6pxaegu;k zb{7^v&uEpRSLC+CLFH=e&NvX7OBsH&JjAxD1?Djtx2#_B_ z$|~%OI8S^S3t_b*B*blUdidNKpli^wyQIJyQ0TL9WmxuKS#5^4%kCu#V)Wz7yI(Z` z0003(2@ZvlM%Za$Dx}22FSF~-`DfRK5sAyTpkM$100Be^NLzJ*H2|Oh0003d36Ow2 z4q*Pvi1~m1kk9|)hy7YXB%r*T_wzrl=jVV0q660KupP(3y3B7B-`NBoc;@hBsL1_j ziaA;^000010s+Vp${)yc^5;mWk!HgAWxxKzBh6a>}5HpmiTDc0Eqj8us~o;XOVDcnZv=yrLwDv_0G8EuKg>%8Q=!@@aE!jT8hDZ1G8jK4m)Gj1gbl=C&a{IJCu_9Mj#ZE zQwH?_`q+PLH%|qthjYaG^$!ERFX_EOtBZ`W08l&enMA{x1gx6I_EBmmOFCJScXo007C}biu*6k1ME68jq0( z&b!T4A>i~N!~MDibW~qr5CWE9{6HBle&Ki`1^kHVQ)3w7b{Rqc>)RQ6{#td56xPOz z&wxvV7yC)AR{N;8x!1jlNB7Z{4tvW28QXblfFyTs#Zh7;w-_J%v0-}v0003138Dgu LU|?7zz>my;>&&_q literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/mvhevc/vps_reserved.bin b/wai/format-probe-conformance/gen/mvhevc/vps_reserved.bin new file mode 100644 index 0000000000000000000000000000000000000000..37de0ffeef575ca26d7d25ddb9f410b5bd62150c GIT binary patch literal 1509 zcmV-a%m(8mp z*bR>OpO{~I1uT_7uMnPa8K$zSY7b-^f2CRc@hD314J)XLJVFXHNmT)l+H5YuS{EJk0b#3eVtliFKW6yviO~+ajF`u>PNx8^ZjW)f@p383$iHw z5qkp$4@nB;UvEL!?kZJvkxWAzXIivY5R8IS$HJhGuscDut>i?pvNApUa6pxaegu;k zb{7^v&uEpRSLC+CLFH=e&NvX7OBsH&JjAxD1?Djtx2#_B_ z$|~%OI8S^S3t_b*B*blUdidNKpli^wyQIJyQ0TL9WmxuKS#5^4%kCu#V)Wz7yI(Z` z0003(2@ZvlM%Za$Dx}22FSF~-`DfRK5sAyTpkM$100Be^NLzJ*H2|Oh0003d36Ow2 z4q*Pvi1~m1kk9|)hy7YXB%r*T_wzrl=jVV0q660KupP(3y3B7B-`NBoc;@hBsL1_j ziaA;^000010s+Vp${)yc^5;mWk!HgAWxxKzBh6a>}5HpmiTDc0Eqj8us~o;XOVDcnZv=yrLwDv_0G8EuKg>%8Q=!@@aE!jT8hDZ1G8jK4m)Gj1gbl=C&a{IJCu_9Mj#ZE zQwH?_`q+PLH%|qthjYaG^$!ERFX_EOtBZ`W08l&enMA{x1gx6I_EBmmOFCJScXo007C}biu*6k1ME68jq0( z&b!T4A>i~N!~MDibW~qr5CWE9{6HBle&Ki`1^kHVQ)3w7b{Rqc>)RQ6{#td56xPOz z&wxvV7yC)AR{N;8x!1jlNB7Z{4tvW28QXblfFyTs#Zh7;w-_J%v0-}v0003138Dgu LU|?7zz>my;>yf$^ literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/other/png_has_no_probe.bin b/wai/format-probe-conformance/gen/other/png_has_no_probe.bin new file mode 100644 index 000000000..0dd1608e4 --- /dev/null +++ b/wai/format-probe-conformance/gen/other/png_has_no_probe.bin @@ -0,0 +1,2 @@ +‰PNG + diff --git a/wai/format-probe-conformance/gen/projection/centre_180.bin b/wai/format-probe-conformance/gen/projection/centre_180.bin new file mode 100644 index 0000000000000000000000000000000000000000..6f9639707276ff84eacd4c3e83d584d6f4241d50 GIT binary patch literal 40 YcmWF!@G@j%WB`H;Ah`udgD@rl09sW7_5c6? literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/projection/erp_359.bin b/wai/format-probe-conformance/gen/projection/erp_359.bin new file mode 100644 index 0000000000000000000000000000000000000000..6023ce6a71fc35ebd77f268d256e0b530e8e67ec GIT binary patch literal 40 WcmWF!@G@j%WB`J6Ah`v^zzP6LzXCx3 literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/projection/erp_mono.bin b/wai/format-probe-conformance/gen/projection/erp_mono.bin new file mode 100644 index 0000000000000000000000000000000000000000..7763663b552577300a8c1fa121e5120fa6f4f6c7 GIT binary patch literal 40 WcmWF!@G@j%WB`H;Ah`v^zzP6L-2y@Y literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/projection/fisheye_top_bottom.bin b/wai/format-probe-conformance/gen/projection/fisheye_top_bottom.bin new file mode 100644 index 0000000000000000000000000000000000000000..ee5db2d4093ce2a9f0ae6fda88345b56825b7eb3 GIT binary patch literal 40 dcmWF!@G@j%W@2Pu*vA0DKne;HUcp!l3;<;j1MmO< literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/projection/half_erp_side_by_side.bin b/wai/format-probe-conformance/gen/projection/half_erp_side_by_side.bin new file mode 100644 index 0000000000000000000000000000000000000000..8358eee88c245be51e750a3a22e132e79a1c0ae4 GIT binary patch literal 40 ccmWF!@G@j%Vq{=o*aF2s3JN5?!B`9o0AHU2od5s; literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/projection/layers_equal.bin b/wai/format-probe-conformance/gen/projection/layers_equal.bin new file mode 100644 index 0000000000000000000000000000000000000000..b6427c53ca3459091560509383fc86a158731cf5 GIT binary patch literal 40 acmWF!@G@j%U}j)ohyr43AVvbe5NrTP(*m{t literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/projection/length_39.bin b/wai/format-probe-conformance/gen/projection/length_39.bin new file mode 100644 index 0000000000000000000000000000000000000000..d56bdc32b531dc85f3383b3f06659985d9679c5d GIT binary patch literal 39 WcmWF!@G@j%WB`H;Ah`v^zy<(Fn*u@r literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/projection/magic.bin b/wai/format-probe-conformance/gen/projection/magic.bin new file mode 100644 index 0000000000000000000000000000000000000000..7703315ab1a2d00130bbb8cf740fc72f2ef25773 GIT binary patch literal 40 WcmWF!@G@d#WB`H;Ah`v^zzP6M0s=z- literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/projection/mono_baseline.bin b/wai/format-probe-conformance/gen/projection/mono_baseline.bin new file mode 100644 index 0000000000000000000000000000000000000000..1536057ba286bfdcd24ceac35e8d7e51d879397a GIT binary patch literal 40 acmWF!@G@j%WB`H;Ah`v^fCI2Flm`Gx=>kIl literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/projection/projection_4.bin b/wai/format-probe-conformance/gen/projection/projection_4.bin new file mode 100644 index 0000000000000000000000000000000000000000..009be510b5ce1295090bee4564fd699a1d058c7b GIT binary patch literal 40 YcmWF!@G@j%VE}>*Mh1p0AO=^M= literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/projection/reserved_byte.bin b/wai/format-probe-conformance/gen/projection/reserved_byte.bin new file mode 100644 index 0000000000000000000000000000000000000000..ed0b7fa725609eb387e3b0cb727e4c28f9a7af0c GIT binary patch literal 40 XcmWF!@G@j%WB`H;Ah`v^zy=rrO5OrP literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/projection/version_2.bin b/wai/format-probe-conformance/gen/projection/version_2.bin new file mode 100644 index 0000000000000000000000000000000000000000..c8763b94c247a7ca71b1dd4889561ebb096e18e6 GIT binary patch literal 40 WcmWF!@G@j#WB`H;Ah`v^zzP6M0Rlq+ literal 0 HcmV?d00001 diff --git a/wai/format-probe-conformance/gen/vvc/ctu_size_reserved.bin b/wai/format-probe-conformance/gen/vvc/ctu_size_reserved.bin new file mode 100644 index 0000000000000000000000000000000000000000..b54cea121468b14971679c2fa94fddd4fc55cfbf GIT binary patch literal 1408 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 + Splits a file of consecutive JPEG XS codestreams (each with its length in its + picture header's Lcod, as a constant-bit-rate encoder writes) and writes them in + wai.video.jxs's framing: five u32 little-endian values n, h, w, fps_num, fps_den, + then each codestream preceded by its u32 little-endian length (format-probes §3.3). + + python3 -I frame.py base + Keeps the NAL units of an H.265 Annex B byte stream whose nuh_layer_id is 0: the + base layer of a multilayer bitstream, each NAL unit after a four-byte start code. +""" + +import struct +import sys + + +def codestreams(b): + out, at = [], 0 + while at < len(b): + if b[at:at + 2] != b"\xff\x10" or b[at + 2:at + 4] != b"\xff\x50": + raise SystemExit("no SOC and CAP at %d" % at) + lcap = struct.unpack(">H", b[at + 4:at + 6])[0] + pih = at + 4 + lcap + if b[pih:pih + 2] != b"\xff\x12": + raise SystemExit("no PIH at %d" % pih) + lcod = struct.unpack(">I", b[pih + 4:pih + 8])[0] + if lcod == 0: + raise SystemExit("a codestream without Lcod: the encoder was not at a constant rate") + out.append(b[at:at + lcod]) + at += lcod + return out + + +def jxsvideo(out, w, h, num, den, src): + cs = codestreams(open(src, "rb").read()) + data = struct.pack("<5I", len(cs), int(h), int(w), int(num), int(den)) + for c in cs: + data += struct.pack(" s and b[e - 1] == 0: + e -= 1 + units.append(b[s:e]) + return units + + +def base(src, out): + keep = [u for u in nal_units(open(src, "rb").read()) if ((u[0] << 8 | u[1]) >> 3) & 0x3F == 0] + open(out, "wb").write(b"".join(b"\x00\x00\x00\x01" + u for u in keep)) + + +if __name__ == "__main__": + {"jxsvideo": jxsvideo, "base": base}[sys.argv[1]](*sys.argv[2:]) diff --git a/wai/format-probe-conformance/tools/make_external.sh b/wai/format-probe-conformance/tools/make_external.sh new file mode 100644 index 000000000..79a123f2b --- /dev/null +++ b/wai/format-probe-conformance/tools/make_external.sh @@ -0,0 +1,179 @@ +#!/bin/bash +# Re-creates ext/ — the format-probe vectors written by real encoders — and provenance.json. +# +# TOOLS= bash tools/make_external.sh (from wai/format-probe-conformance) +# +# Every input is a synthetic picture tools/yuv.py writes; no captured content enters. Every +# encoder and decoder is open source under a permissive licence, at the revision pinned +# below, and is built in $TOOLS/src/ (the build lines below are the ones used). The +# script refuses to run against another revision. Each vector is decoded again by a +# decoder, and each JPEG XS vector by two independent decoders and by one of them along two +# code paths; provenance.json records every command, digest and comparison. +# +# Not run in CI (it needs the encoders, a C++ toolchain and the network); its outputs are +# committed, and format_probe_verify.py and wai-rs check them on every change. +set -euo pipefail +cd "$(dirname "$0")/.." +HERE=$(pwd) +: "${TOOLS:?set TOOLS to the directory holding the built encoders and decoders}" +S=$TOOLS/src +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT + +# key | repository | revision | licence (SPDX) | build +# vvc_enc https://github.com/fraunhoferhhi/vvenc.git # allow-name: repository URL +# 9428ea8636ae7f443ecde89999d16b2dfc421524 (tag v1.14.0) BSD-3-Clause-Clear +# cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build +# vvc_dec https://github.com/fraunhoferhhi/vvdec.git # allow-name: repository URL +# cb1a3efd88b2ebd1141f1a9ca87c774f05de9249 (tag v3.2.1) BSD-3-Clause-Clear +# cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build +# jxs https://github.com/OpenVisualCloud/SVT-JPEG-XS.git # allow-name: repository URL +# f4720203534d6f5ea90f828a58814af5597461d7 BSD-2-Clause-Patent +# cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build +# jxs2 the crate oxideav-jpegxs 0.0.8 (crates.io), MIT, without default features # allow-name: crate name +# mvhevc https://vcgit.hhi.fraunhofer.de/jvet/HTM.git # allow-name: repository URL +# 7a38d7f5dce928ec456de048a07929acac2dfd6e (tag HTM-16.3) BSD-3-Clause +# make -C build/linux HEVC_EXT=1 release (with -Werror removed from +# build/linux/common/makefile.base: the 2018 sources warn under current compilers) +# avs3_enc https://github.com/uavs3/uavs3e.git # allow-name: repository URL +# e1ff0f37a8d67814e1d650f6b49d495c49dde946 BSD-4-Clause +# cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 +# avs3_dec https://github.com/uavs3/uavs3d.git # allow-name: repository URL +# 0e20d2c291853f196c68922a264bcd8471d75b68 BSD-3-Clause +# cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCOMPILE_10BIT=0 +pin() { + local got + got=$(git -C "$S/$1" rev-parse HEAD) + [ "$got" = "$2" ] || { echo "$1 is at $got, not the pinned $2" >&2; exit 1; } +} +pin vvenc 9428ea8636ae7f443ecde89999d16b2dfc421524 +pin vvdec cb1a3efd88b2ebd1141f1a9ca87c774f05de9249 +pin SVT-JPEG-XS f4720203534d6f5ea90f828a58814af5597461d7 +pin HTM 7a38d7f5dce928ec456de048a07929acac2dfd6e +pin uavs3e e1ff0f37a8d67814e1d650f6b49d495c49dde946 +pin uavs3d 0e20d2c291853f196c68922a264bcd8471d75b68 + +VVENC=$S/vvenc/bin/release-static/vvencapp +VVDEC=$S/vvdec/bin/release-static/vvdecapp +JXSENC=$S/SVT-JPEG-XS/Bin/Release/SvtJpegxsEncApp +JXSDEC=$S/SVT-JPEG-XS/Bin/Release/SvtJpegxsDecApp +HTMENC=$S/HTM/bin/TAppEncoderStatic +HTMDEC=$S/HTM/bin/TAppDecoderStatic +AVS3ENC=$S/uavs3e/build/uavs3enc +AVS3DEC=$S/uavs3d/build/uavs3dec +JXS2=$TOOLS/jxs2/target/release/jxs2 + +# The second JPEG XS decoder: a program over the crate's component decode, writing each +# component plane as the crate returns it (one byte a sample at 8 bits, else two, LE). +if [ ! -x "$JXS2" ] || ! grep -q -- --unrestricted "$TOOLS/jxs2/src/main.rs"; then + mkdir -p "$TOOLS/jxs2/src" + cat > "$TOOLS/jxs2/Cargo.toml" <<'EOF' +[package] +name = "jxs2" +version = "0.0.0" +edition = "2021" +[dependencies] +oxideav-jpegxs = { version = "=0.0.8", default-features = false } +EOF + cat > "$TOOLS/jxs2/src/main.rs" <<'EOF' +fn main() { + let a: Vec = std::env::args().collect(); + let mut bytes = std::fs::read(&a[1]).expect("input"); + if a.get(3).map(String::as_str) == Some("--unrestricted") { + // Ppih and Plev rewritten to 0 (unrestricted): for a profile or level code this + // decoder does not know. Neither field changes how the codestream decodes. + let lcap = u16::from_be_bytes([bytes[4], bytes[5]]) as usize; + let at = 4 + lcap + 8; + bytes[at..at + 4].fill(0); + } + let c = oxideav_jpegxs::decode_components(&bytes).expect("decode"); + let mut out = Vec::new(); + for p in &c.planes { + out.extend_from_slice(&p.data); + } + std::fs::write(&a[2], out).expect("output"); +} +EOF + (cd "$TOOLS/jxs2" && cargo build -q --release --target-dir "$TOOLS/jxs2/target") +fi + +Y="python3 -I tools/yuv.py" +F="python3 -I tools/frame.py" +mkdir -p ext/vvc ext/hevc ext/mvhevc ext/jxs ext/jxs-video ext/avs3 +LOG=$WORK/log.jsonl +: > "$LOG" +note() { # file, encoder key, input description, command + python3 -I -c 'import json,sys; print(json.dumps({"file": sys.argv[1], "encoder": sys.argv[2], "input": sys.argv[3], "command": sys.argv[4]}))' "$@" >> "$LOG" +} + +# ---- H.266 ---- +$Y "$WORK/v64.yuv" 64 64 3 420 10 +$VVENC -i "$WORK/v64.yuv" -s 64x64 -c yuv420_10 -r 30 -f 3 --preset faster -q 37 -t 1 -o ext/vvc/main10_64x64.266 >/dev/null +note vvc/main10_64x64.266 vvc_enc "64x64 4:2:0 10-bit, 3 frames" "vvencapp -s 64x64 -c yuv420_10 -r 30 -f 3 --preset faster -q 37 -t 1" +$Y "$WORK/v100.yuv" 100 58 2 420 10 +$VVENC -i "$WORK/v100.yuv" -s 100x58 -c yuv420_10 -r 25 -f 2 --preset faster -q 40 -t 1 -o ext/vvc/main10_100x58.266 >/dev/null +note vvc/main10_100x58.266 vvc_enc "100x58 4:2:0 10-bit, 2 frames" "vvencapp -s 100x58 -c yuv420_10 -r 25 -f 2 --preset faster -q 40 -t 1" + +# ---- MV-HEVC and its base layer ---- +$Y "$WORK/l.yuv" 64 64 3 420 8 0 +$Y "$WORK/r.yuv" 64 64 3 420 8 4 +(cd "$WORK" && $HTMENC -c "$S/HTM/cfg/MV-HEVC/baseCfg_2view.cfg" --InputFile_0=l.yuv --InputFile_1=r.yuv \ + --SourceWidth=64 --SourceHeight=64 --FrameRate=30 --FramesToBeEncoded=3 --QP=37 \ + --BitstreamFile=mv.bit --ReconFile_0=rec0.yuv --ReconFile_1=rec1.yuv >/dev/null) +cp "$WORK/mv.bit" ext/mvhevc/2view_64x64.265 +note mvhevc/2view_64x64.265 mvhevc "two views 64x64 4:2:0 8-bit, 3 frames each, the second shifted 4 columns" "TAppEncoder -c cfg/MV-HEVC/baseCfg_2view.cfg --SourceWidth=64 --SourceHeight=64 --FrameRate=30 --FramesToBeEncoded=3 --QP=37" +$F base ext/mvhevc/2view_64x64.265 ext/hevc/base_64x64.265 +note hevc/base_64x64.265 mvhevc "the base layer (nuh_layer_id 0) of mvhevc/2view_64x64.265" "tools/frame.py base" + +# ---- JPEG XS ---- +$Y "$WORK/j422.yuv" 64 64 1 422 10 +$JXSENC -i "$WORK/j422.yuv" -w 64 -h 64 --colour-format yuv422 --input-depth 10 --bpp 3 -n 1 -b ext/jxs/main422_10_64x64.jxs --no-progress 1 >/dev/null +note jxs/main422_10_64x64.jxs jxs "64x64 4:2:2 10-bit" "SvtJpegxsEncApp -w 64 -h 64 --colour-format yuv422 --input-depth 10 --bpp 3 -n 1" +$Y "$WORK/jrgb.yuv" 64 64 1 rgb 8 +$JXSENC -i "$WORK/jrgb.yuv" -w 64 -h 64 --colour-format rgb --input-depth 8 --color-transform 1 --profile latency --bpp 4 -n 1 -b ext/jxs/rgb_rct_8_64x64.jxs --no-progress 1 >/dev/null +note jxs/rgb_rct_8_64x64.jxs jxs "64x64 R, G, B planes, 8-bit" "SvtJpegxsEncApp -w 64 -h 64 --colour-format rgb --input-depth 8 --color-transform 1 --profile latency --bpp 4 -n 1" +$Y "$WORK/j444.yuv" 48 32 1 444 8 +$JXSENC -i "$WORK/j444.yuv" -w 48 -h 32 --colour-format yuv444 --input-depth 8 --lossless 1 --coding-raw 0 -n 1 -b ext/jxs/lossless_444_8_48x32.jxs --no-progress 1 >/dev/null +note jxs/lossless_444_8_48x32.jxs jxs "48x32 4:4:4 8-bit" "SvtJpegxsEncApp -w 48 -h 32 --colour-format yuv444 --input-depth 8 --lossless 1 --coding-raw 0 -n 1" +$Y "$WORK/j420.yuv" 64 48 3 420 8 +$JXSENC -i "$WORK/j420.yuv" -w 64 -h 48 --colour-format yuv420 --input-depth 8 --bpp 3 -n 3 -b "$WORK/j420.cs" --no-progress 1 >/dev/null +$F jxsvideo ext/jxs-video/main420_8_64x48_3f.jxsv 64 48 30 1 "$WORK/j420.cs" +note jxs-video/main420_8_64x48_3f.jxsv jxs "64x48 4:2:0 8-bit, 3 frames at 30/1, in wai.video.jxs's framing" "SvtJpegxsEncApp -w 64 -h 48 --colour-format yuv420 --input-depth 8 --bpp 3 -n 3; tools/frame.py jxsvideo" +python3 -I -c 'import struct,sys; b=open(sys.argv[1],"rb").read(); n=struct.unpack("/dev/null) +note avs3/main_128x64.avs3 avs3_enc "128x64 4:2:0 8-bit, 3 frames" "uavs3enc -w 128 -h 64 -f 3 -q 40 --fps_num 30 --fps_den 1 -d 8" + +# ---- Decodes and comparisons ---- +DEC=$WORK/dec.jsonl +: > "$DEC" +dec() { # file, decoder key, output file + python3 -I -c 'import hashlib,json,sys; d=open(sys.argv[3],"rb").read(); print(json.dumps({"file": sys.argv[1], "decoder": sys.argv[2], "bytes": len(d), "sha256": hashlib.sha256(d).hexdigest()}))' "$@" >> "$DEC" +} +for f in ext/vvc/*.266; do $VVDEC -b "$f" -o "$WORK/d.yuv" >/dev/null; dec "${f#ext/}" vvc_dec "$WORK/d.yuv"; done +(cd "$WORK" && $HTMDEC -b "$HERE/ext/mvhevc/2view_64x64.265" -o dmv.yuv >/dev/null) +for o in "$WORK"/dmv*.yuv; do dec "mvhevc/2view_64x64.265" "mvhevc:$(basename "$o")" "$o"; done +(cd "$WORK" && $HTMDEC -b "$HERE/ext/hevc/base_64x64.265" -o dbase.yuv >/dev/null) +for o in "$WORK"/dbase*.yuv; do dec "hevc/base_64x64.265" "mvhevc:$(basename "$o")" "$o"; done +$AVS3DEC -i ext/avs3/main_128x64.avs3 -o "$WORK/da.yuv" -l 0 >/dev/null +dec avs3/main_128x64.avs3 avs3_dec "$WORK/da.yuv" +jx() { # codestream, name + $JXSDEC -i "$1" -o "$WORK/s.yuv" --asm c -v 0 >/dev/null; dec "$2" jxs:c "$WORK/s.yuv" + $JXSDEC -i "$1" -o "$WORK/s.yuv" -v 0 >/dev/null; dec "$2" jxs:simd "$WORK/s.yuv" + # The second decoder refuses a profile or level code it does not know (MLS.12, added by + # ISO/IEC 21122-2:2022/Amd 1; the level codes 0x04 and 0x2C); then it decodes a copy whose + # Ppih and Plev are 0, and the record says so. + if "$JXS2" "$1" "$WORK/s2.yuv" 2>/dev/null; then dec "$2" jxs2 "$WORK/s2.yuv" + else "$JXS2" "$1" "$WORK/s2.yuv" --unrestricted; dec "$2" jxs2:unrestricted "$WORK/s2.yuv"; fi +} +for f in ext/jxs/*.jxs; do jx "$f" "${f#ext/}"; done +# The lossless codestream's source picture, which its decode must reproduce. +dec jxs/lossless_444_8_48x32.jxs source "$WORK/j444.yuv" +for i in 0 1 2; do jx "$WORK/frame$i.jxs" "jxs-video/main420_8_64x48_3f.jxsv#$i"; done + +python3 -I tools/provenance.py "$LOG" "$DEC" > provenance.json diff --git a/wai/format-probe-conformance/tools/provenance.py b/wai/format-probe-conformance/tools/provenance.py new file mode 100644 index 000000000..05fa2592d --- /dev/null +++ b/wai/format-probe-conformance/tools/provenance.py @@ -0,0 +1,50 @@ +"""Writes provenance.json from make_external.sh's logs: every external vector with its +digest, encoder, input and command, and every decode of it with its output's size and +digest. Where several decoders or code paths decode one JPEG XS codestream, it records +whether their outputs are byte-identical. + + python3 -I provenance.py +""" + +import hashlib +import json +import os +import sys + + +def main(enc_log, dec_log): + here = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "ext") + vectors = [] + for line in open(enc_log): + e = json.loads(line) + b = open(os.path.join(here, e["file"]), "rb").read() + e["bytes"] = len(b) + e["sha256"] = hashlib.sha256(b).hexdigest() + e["decodes"] = [] + vectors.append(e) + by_file = {} + for line in open(dec_log): + d = json.loads(line) + by_file.setdefault(d["file"], []).append(d) + for e in vectors: + e["decodes"] = [{k: d[k] for k in ("decoder", "bytes", "sha256")} for d in by_file.get(e["file"], [])] + agreement = [] + for f, ds in sorted(by_file.items()): + js = [d for d in ds if d["decoder"].startswith("jxs") or d["decoder"] == "source"] + if len(js) > 1: + agreement.append({ + "codestream": f, + "outputs": {d["decoder"]: d["sha256"] for d in js}, + "identical": len({d["sha256"] for d in js}) == 1, + }) + out = { + "note": "Written by tools/make_external.sh; each encoder and decoder key is defined, with its repository, revision and licence, in that script's header.", + "vectors": vectors, + "jpeg_xs_decodes": agreement, + } + json.dump(out, sys.stdout, indent=1, sort_keys=True) + sys.stdout.write("\n") + + +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/wai/format-probe-conformance/tools/yuv.py b/wai/format-probe-conformance/tools/yuv.py new file mode 100644 index 000000000..8828938b2 --- /dev/null +++ b/wai/format-probe-conformance/tools/yuv.py @@ -0,0 +1,42 @@ +"""Writes the synthetic pictures the external vectors are encoded from: planar samples of a +fixed pattern, so the inputs are the same on every machine and hold no captured content. + + python3 -I yuv.py [] + + is 420, 422, 444 (planes Y, Cb, Cr) or rgb (planes R, G, B). Samples above 8 bits +are two bytes, little-endian. moves the pattern right by that many columns, the +second view of a stereo pair. +""" + +import sys + + +def main(out, w, h, frames, layout, depth, shift=0): + w, h, frames, depth, shift = int(w), int(h), int(frames), int(depth), int(shift) + top = (1 << depth) - 1 + if layout == "rgb": + planes = [(1, 1)] * 3 + else: + sub = {"420": (2, 2), "422": (2, 1), "444": (1, 1)}[layout] + planes = [(1, 1), sub, sub] + data = bytearray() + for f in range(frames): + for p, (sx, sy) in enumerate(planes): + pw, ph = (w + sx - 1) // sx, (h + sy - 1) // sy + for y in range(ph): + for x in range(pw): + xs = (x * sx - shift) % w + # a diagonal ramp, a checker and a moving bar, per plane + v = (xs * 3 + y * sy * 5 + f * 7 + p * 40) * (top // 255 if top > 255 else 1) + if ((xs // 8) + (y * sy // 8) + p) % 2: + v += top // 6 + if (xs + f * 4) % w < 6: + v = top - v % (top // 4 + 1) + v %= top + 1 + data += bytes([v]) if depth <= 8 else v.to_bytes(2, "little") + with open(out, "wb") as fh: + fh.write(data) + + +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/wai/format-probe-conformance/vectors.json b/wai/format-probe-conformance/vectors.json new file mode 100644 index 000000000..8c5840c93 --- /dev/null +++ b/wai/format-probe-conformance/vectors.json @@ -0,0 +1,1981 @@ +{ + "probes": [ + { + "capability": "wai.image.jxs", + "basis": "implementation" + }, + { + "capability": "wai.video.jxs", + "basis": "implementation" + }, + { + "capability": "wai.video.hevc", + "basis": "standard" + }, + { + "capability": "wai.video.mvhevc", + "basis": "standard" + }, + { + "capability": "wai.video.vvc", + "basis": "standard" + }, + { + "capability": "wai.video.lcevc", + "basis": "implementation" + }, + { + "capability": "wai.audio.mpegh", + "basis": "implementation" + }, + { + "capability": "wai.video.avs3", + "basis": "implementation" + }, + { + "capability": "wai.audio.avs3", + "basis": "implementation" + }, + { + "capability": "wai.image.ieee1857_11", + "basis": "implementation" + }, + { + "capability": "wai.meta.projection", + "basis": "wai" + } + ], + "refusals": [ + "no_signature", + "truncated", + "out_of_order", + "reserved_value", + "length_mismatch", + "missing_structure", + "not_multiview", + "framing_invalid", + "trailing_bytes", + "descriptor_invalid", + "layer_absent", + "outside_probe", + "no_probe" + ], + "facts": [ + { + "file": "ext/vvc/main10_64x64.266", + "pointer": "/cropped", + "value": [ + 64, + 64 + ] + }, + { + "file": "ext/vvc/main10_64x64.266", + "pointer": "/chroma_format_idc", + "value": 1 + }, + { + "file": "ext/vvc/main10_64x64.266", + "pointer": "/profile_idc", + "value": 1 + }, + { + "file": "ext/vvc/main10_100x58.266", + "pointer": "/cropped", + "value": [ + 100, + 58 + ] + }, + { + "file": "ext/mvhevc/2view_64x64.265", + "pointer": "/cropped", + "value": [ + 64, + 64 + ] + }, + { + "file": "ext/mvhevc/2view_64x64.265", + "pointer": "/multilayer/views", + "value": 2 + }, + { + "file": "ext/mvhevc/2view_64x64.265", + "pointer": "/multilayer/view_ids", + "value": [ + 1, + 0 + ] + }, + { + "file": "ext/mvhevc/2view_64x64.265", + "pointer": "/multilayer/multiview_profile", + "value": true + }, + { + "file": "ext/mvhevc/2view_64x64.265", + "pointer": "/layers_present", + "value": [ + 0, + 1 + ] + }, + { + "file": "ext/hevc/base_64x64.265", + "pointer": "/layers_present", + "value": [ + 0 + ] + }, + { + "file": "ext/hevc/base_64x64.265", + "pointer": "/bit_depth", + "value": [ + 8, + 8 + ] + }, + { + "file": "ext/jxs/main422_10_64x64.jxs", + "pointer": "/structure", + "value": "4:2:2" + }, + { + "file": "ext/jxs/main422_10_64x64.jxs", + "pointer": "/rtp/depth", + "value": 10 + }, + { + "file": "ext/jxs/main422_10_64x64.jxs", + "pointer": "/width", + "value": 64 + }, + { + "file": "ext/jxs/rgb_rct_8_64x64.jxs", + "pointer": "/colour_transform", + "value": 1 + }, + { + "file": "ext/jxs/rgb_rct_8_64x64.jxs", + "pointer": "/rtp/sampling", + "value": "RGB" + }, + { + "file": "ext/jxs/lossless_444_8_48x32.jxs", + "pointer": "/width", + "value": 48 + }, + { + "file": "ext/jxs/lossless_444_8_48x32.jxs", + "pointer": "/height", + "value": 32 + }, + { + "file": "ext/jxs/lossless_444_8_48x32.jxs", + "pointer": "/fractional_bits", + "value": 0 + }, + { + "file": "ext/jxs-video/main420_8_64x48_3f.jxsv", + "pointer": "/frames", + "value": 3 + }, + { + "file": "ext/jxs-video/main420_8_64x48_3f.jxsv", + "pointer": "/first/structure", + "value": "4:2:0" + }, + { + "file": "ext/jxs-video/main420_8_64x48_3f.jxsv", + "pointer": "/fps", + "value": [ + 30, + 1 + ] + }, + { + "file": "ext/avs3/main_128x64.avs3", + "pointer": "/size", + "value": [ + 128, + 64 + ] + }, + { + "file": "ext/avs3/main_128x64.avs3", + "pointer": "/sample_precision", + "value": 1 + }, + { + "file": "ext/avs3/main_128x64.avs3", + "pointer": "/chroma_format", + "value": 1 + } + ], + "vectors": [ + { + "name": "avs3_main_128x64.avs3", + "capability": "wai.video.avs3", + "file": "ext/avs3/main_128x64.avs3", + "origin": "encoder", + "sha256": "1e76c3ce417aa13e40207ddb63eda4cdde9064fbf1833fe505b90656b1c1eea2", + "outcome": { + "accept": { + "profile_id": 34, + "level_id": 106, + "progressive": true, + "field_coded": false, + "size": [ + 128, + 64 + ], + "chroma_format": 1, + "sample_precision": 1, + "encoding_precision": 1, + "aspect_ratio": 1, + "frame_rate_code": 5, + "bit_rate": 0, + "low_delay": false, + "temporal_id_enabled": true, + "bbv_buffer_size": 262143, + "max_dpb_size": 16, + "pictures": [ + 1, + 2 + ], + "sequence_end": true + } + } + }, + { + "name": "hevc_base_64x64.265", + "capability": "wai.video.hevc", + "file": "ext/hevc/base_64x64.265", + "origin": "encoder", + "sha256": "bda8d82cf63924fcc7156781023549fceb44100fe8d4ce59f72b4a2658f31208", + "outcome": { + "accept": { + "profile_idc": 1, + "tier": 0, + "level_idc": 0, + "chroma_format_idc": 1, + "bit_depth": [ + 8, + 8 + ], + "coded": [ + 64, + 64 + ], + "cropped": [ + 64, + 64 + ], + "layers": 2, + "layers_present": [ + 0 + ], + "multilayer": { + "scalability_mask": 2, + "layer_ids": [ + 0, + 1 + ], + "view_order": [ + 0, + 1 + ], + "aux": [ + 0, + 0 + ], + "views": 2, + "view_ids": [ + 1, + 0 + ], + "profiles": [ + 1, + 6 + ], + "multiview_profile": true + } + } + } + }, + { + "name": "jxs-video_main420_8_64x48_3f.jxsv", + "capability": "wai.video.jxs", + "file": "ext/jxs-video/main420_8_64x48_3f.jxsv", + "origin": "encoder", + "sha256": "866126ee1486679a22d1aec94f6493d1455675929f8112d8286244443fbcc7a6", + "outcome": { + "accept": { + "frames": 3, + "size": [ + 64, + 48 + ], + "fps": [ + 30, + 1 + ], + "constant_bytes": 1152, + "first": { + "profile": "0x3240", + "level": "0x0404", + "width": 64, + "height": 48, + "components": [ + [ + 8, + 1, + 1 + ], + [ + 8, + 2, + 2 + ], + [ + 8, + 2, + 2 + ] + ], + "structure": "4:2:0", + "colour_transform": 0, + "decompositions": [ + 5, + 2 + ], + "precinct_width": 0, + "slice_height": 4, + "wavelet_precision": 20, + "fractional_bits": 8, + "quantizer": 0, + "long_header": false, + "raw_mode": true, + "cbr_bytes": 1152, + "cap_bits": [ + 4, + 8 + ], + "rtp": { + "profile": "Main420.12", + "level": "1k-1", + "sublevel": "Sublev3bpp", + "depth": 8, + "width": 64, + "height": 48, + "sampling": null + } + } + } + } + }, + { + "name": "jxs_lossless_444_8_48x32.jxs", + "capability": "wai.image.jxs", + "file": "ext/jxs/lossless_444_8_48x32.jxs", + "origin": "encoder", + "sha256": "1507c4217c658c4ec8725866748247615eb90e7eb6a09b05d1b7cd969c7ed5b6", + "outcome": { + "accept": { + "profile": "0x6ec0", + "level": "0x0400", + "width": 48, + "height": 32, + "components": [ + [ + 8, + 1, + 1 + ], + [ + 8, + 1, + 1 + ], + [ + 8, + 1, + 1 + ] + ], + "structure": "4:4:4", + "colour_transform": 0, + "decompositions": [ + 5, + 2 + ], + "precinct_width": 0, + "slice_height": 4, + "wavelet_precision": 8, + "fractional_bits": 0, + "quantizer": 0, + "long_header": false, + "raw_mode": false, + "cbr_bytes": 4261, + "cap_bits": [ + 6 + ], + "rtp": { + "profile": "MLS.12", + "level": "1k-1", + "sublevel": "Unrestricted", + "depth": 8, + "width": 48, + "height": 32, + "sampling": null + } + } + } + }, + { + "name": "jxs_main422_10_64x64.jxs", + "capability": "wai.image.jxs", + "file": "ext/jxs/main422_10_64x64.jxs", + "origin": "encoder", + "sha256": "b9c76cb5c720e9640c380e63be39e22429e6b6b13556a7aed39678d2e6036bce", + "outcome": { + "accept": { + "profile": "0x3540", + "level": "0x0404", + "width": 64, + "height": 64, + "components": [ + [ + 10, + 1, + 1 + ], + [ + 10, + 2, + 1 + ], + [ + 10, + 2, + 1 + ] + ], + "structure": "4:2:2", + "colour_transform": 0, + "decompositions": [ + 5, + 2 + ], + "precinct_width": 0, + "slice_height": 4, + "wavelet_precision": 20, + "fractional_bits": 8, + "quantizer": 0, + "long_header": false, + "raw_mode": true, + "cbr_bytes": 1536, + "cap_bits": [ + 8 + ], + "rtp": { + "profile": "Main422.10", + "level": "1k-1", + "sublevel": "Sublev3bpp", + "depth": 10, + "width": 64, + "height": 64, + "sampling": null + } + } + } + }, + { + "name": "jxs_rgb_rct_8_64x64.jxs", + "capability": "wai.image.jxs", + "file": "ext/jxs/rgb_rct_8_64x64.jxs", + "origin": "encoder", + "sha256": "bbc1573b3b512afc1246b339089b0e4fb74abd7c21d7bd1f2e095eb2f141f65a", + "outcome": { + "accept": { + "profile": "0x3a40", + "level": "0x0406", + "width": 64, + "height": 64, + "components": [ + [ + 8, + 1, + 1 + ], + [ + 8, + 1, + 1 + ], + [ + 8, + 1, + 1 + ] + ], + "structure": "4:4:4", + "colour_transform": 1, + "decompositions": [ + 5, + 2 + ], + "precinct_width": 0, + "slice_height": 4, + "wavelet_precision": 20, + "fractional_bits": 8, + "quantizer": 0, + "long_header": false, + "raw_mode": true, + "cbr_bytes": 2048, + "cap_bits": [ + 8 + ], + "rtp": { + "profile": "Main444.12", + "level": "1k-1", + "sublevel": "Sublev4bpp", + "depth": 8, + "width": 64, + "height": 64, + "sampling": "RGB" + } + } + } + }, + { + "name": "mvhevc_2view_64x64.265", + "capability": "wai.video.mvhevc", + "file": "ext/mvhevc/2view_64x64.265", + "origin": "encoder", + "sha256": "74077851dbb7c08cf52a437b083d9e91d6d9c9b45759d26b09787a8b3e27ce79", + "outcome": { + "accept": { + "profile_idc": 1, + "tier": 0, + "level_idc": 0, + "chroma_format_idc": 1, + "bit_depth": [ + 8, + 8 + ], + "coded": [ + 64, + 64 + ], + "cropped": [ + 64, + 64 + ], + "layers": 2, + "layers_present": [ + 0, + 1 + ], + "multilayer": { + "scalability_mask": 2, + "layer_ids": [ + 0, + 1 + ], + "view_order": [ + 0, + 1 + ], + "aux": [ + 0, + 0 + ], + "views": 2, + "view_ids": [ + 1, + 0 + ], + "profiles": [ + 1, + 6 + ], + "multiview_profile": true + } + } + } + }, + { + "name": "vvc_main10_100x58.266", + "capability": "wai.video.vvc", + "file": "ext/vvc/main10_100x58.266", + "origin": "encoder", + "sha256": "ba3ce2bdac9aec4d7af650bcdb7513dc6aa24cb326393743cf32366300dff905", + "outcome": { + "accept": { + "profile_idc": 1, + "tier": 0, + "level_idc": 16, + "frame_only": true, + "multilayer_enabled": false, + "sub_profiles": [], + "chroma_format_idc": 1, + "ctu_size": 64, + "coded": [ + 104, + 64 + ], + "cropped": [ + 100, + 58 + ], + "layers": 1, + "layers_present": [ + 0 + ] + } + } + }, + { + "name": "vvc_main10_64x64.266", + "capability": "wai.video.vvc", + "file": "ext/vvc/main10_64x64.266", + "origin": "encoder", + "sha256": "f0cf2922e5be33a5503465119c8dc61776d024e9f676f56adc4872dd9e1caa0a", + "outcome": { + "accept": { + "profile_idc": 1, + "tier": 0, + "level_idc": 16, + "frame_only": true, + "multilayer_enabled": false, + "sub_profiles": [], + "chroma_format_idc": 1, + "ctu_size": 64, + "coded": [ + 64, + 64 + ], + "cropped": [ + 64, + 64 + ], + "layers": 1, + "layers_present": [ + 0 + ] + } + } + }, + { + "name": "mvhevc_as_hevc", + "capability": "wai.video.hevc", + "file": "ext/mvhevc/2view_64x64.265", + "origin": "encoder", + "sha256": "74077851dbb7c08cf52a437b083d9e91d6d9c9b45759d26b09787a8b3e27ce79", + "outcome": { + "accept": { + "profile_idc": 1, + "tier": 0, + "level_idc": 0, + "chroma_format_idc": 1, + "bit_depth": [ + 8, + 8 + ], + "coded": [ + 64, + 64 + ], + "cropped": [ + 64, + 64 + ], + "layers": 2, + "layers_present": [ + 0, + 1 + ], + "multilayer": { + "scalability_mask": 2, + "layer_ids": [ + 0, + 1 + ], + "view_order": [ + 0, + 1 + ], + "aux": [ + 0, + 0 + ], + "views": 2, + "view_ids": [ + 1, + 0 + ], + "profiles": [ + 1, + 6 + ], + "multiview_profile": true + } + } + } + }, + { + "name": "base_as_mvhevc", + "capability": "wai.video.mvhevc", + "file": "ext/hevc/base_64x64.265", + "origin": "encoder", + "sha256": "bda8d82cf63924fcc7156781023549fceb44100fe8d4ce59f72b4a2658f31208", + "outcome": { + "refuse": "not_multiview" + } + }, + { + "name": "forbidden_bit", + "capability": "wai.video.mvhevc", + "file": "gen/mvhevc/forbidden_bit.bin", + "origin": "mutation of ext/mvhevc/2view_64x64.265", + "sha256": "1cbed19ff0e2002fb4f11d2ba2c29f87a812817725e4aa8d423e45b8983b237c", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "tid_zero", + "capability": "wai.video.mvhevc", + "file": "gen/mvhevc/tid_zero.bin", + "origin": "mutation of ext/mvhevc/2view_64x64.265", + "sha256": "b619f2b768537982f40c11effbbdc3f8aac4c3bf643b75afd2d7842a0716f01a", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "no_sps", + "capability": "wai.video.mvhevc", + "file": "gen/mvhevc/no_sps.bin", + "origin": "mutation of ext/mvhevc/2view_64x64.265", + "sha256": "0adb9a675e69f438d4af7859c575596ba3b94beafb02223e017d43fd4da7cfe8", + "outcome": { + "refuse": "missing_structure" + } + }, + { + "name": "slice_first", + "capability": "wai.video.mvhevc", + "file": "gen/mvhevc/slice_first.bin", + "origin": "mutation of ext/mvhevc/2view_64x64.265", + "sha256": "59d3a5792d0abaebbfcc246132a4dabc20d20797a62964f90749a2a37cf86d23", + "outcome": { + "refuse": "out_of_order" + } + }, + { + "name": "vps_reserved", + "capability": "wai.video.mvhevc", + "file": "gen/mvhevc/vps_reserved.bin", + "origin": "mutation of ext/mvhevc/2view_64x64.265", + "sha256": "1fd7416c5a79dcb271a7405ff0955620b441667573f3d9ce2e67fb8484b5b1bc", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "sps_truncated", + "capability": "wai.video.mvhevc", + "file": "gen/mvhevc/sps_truncated.bin", + "origin": "mutation of ext/mvhevc/2view_64x64.265", + "sha256": "2b5cc46c92bfd7b3e3eab3c666147165b0a84e40a2a436820db4258a66107b79", + "outcome": { + "refuse": "truncated" + } + }, + { + "name": "no_start_code", + "capability": "wai.video.mvhevc", + "file": "gen/mvhevc/no_start_code.bin", + "origin": "mutation of ext/mvhevc/2view_64x64.265", + "sha256": "7968d5b98a2b9127484640962ef77397fcdbd7833df13bab9cda68930c400ca9", + "outcome": { + "refuse": "no_signature" + } + }, + { + "name": "reserved_zero_bit", + "capability": "wai.video.vvc", + "file": "gen/vvc/reserved_zero_bit.bin", + "origin": "mutation of ext/vvc/main10_64x64.266", + "sha256": "502a0eb82d76d1329c2fee7dd4571fefd954897244e716302cee99439971afab", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "no_sps", + "capability": "wai.video.vvc", + "file": "gen/vvc/no_sps.bin", + "origin": "mutation of ext/vvc/main10_64x64.266", + "sha256": "8a28923b1d0afce381ed06036e28857754db4b0f45bd31e04696cbcf5213bc4d", + "outcome": { + "refuse": "missing_structure" + } + }, + { + "name": "slice_first", + "capability": "wai.video.vvc", + "file": "gen/vvc/slice_first.bin", + "origin": "mutation of ext/vvc/main10_64x64.266", + "sha256": "7a51643c5f3ebaace95743e4150a2bbdb0a047e9f8d01139b8e4c7daf2ec729f", + "outcome": { + "refuse": "out_of_order" + } + }, + { + "name": "ctu_size_reserved", + "capability": "wai.video.vvc", + "file": "gen/vvc/ctu_size_reserved.bin", + "origin": "mutation of ext/vvc/main10_64x64.266", + "sha256": "6303eeb7c3deabae76320d41fbfc3108b114b588b88588189f0118cd08a325c3", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "tid_zero", + "capability": "wai.video.vvc", + "file": "gen/vvc/tid_zero.bin", + "origin": "mutation of ext/vvc/main10_64x64.266", + "sha256": "5c7aff529ba04f64ea5744681287dcf295a2203646149d1931b460af5f0d8711", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "no_soc", + "capability": "wai.image.jxs", + "file": "gen/jxs/no_soc.bin", + "origin": "mutation of ext/jxs/main422_10_64x64.jxs", + "sha256": "83c5596a6502a42bb112f22e2352471f5fe7f17e45074cba0d6ff808565758f5", + "outcome": { + "refuse": "no_signature" + } + }, + { + "name": "cap_missing", + "capability": "wai.image.jxs", + "file": "gen/jxs/cap_missing.bin", + "origin": "mutation of ext/jxs/main422_10_64x64.jxs", + "sha256": "0872759d60dbacd62f56dbc9ac044a137056635ecb09150add481cf701485b48", + "outcome": { + "refuse": "out_of_order" + } + }, + { + "name": "lpih_25", + "capability": "wai.image.jxs", + "file": "gen/jxs/lpih_25.bin", + "origin": "mutation of ext/jxs/main422_10_64x64.jxs", + "sha256": "5716622b5abea640ab1a9c09dc0814631399b9c960e8a7b88e6218777b4b5812", + "outcome": { + "refuse": "length_mismatch" + } + }, + { + "name": "ng_3", + "capability": "wai.image.jxs", + "file": "gen/jxs/ng_3.bin", + "origin": "mutation of ext/jxs/main422_10_64x64.jxs", + "sha256": "0a0639cbf3dd087103752633d989f7772ed602c6b6e93278f2b33e938b967f93", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "cpih_2", + "capability": "wai.image.jxs", + "file": "gen/jxs/cpih_2.bin", + "origin": "mutation of ext/jxs/main422_10_64x64.jxs", + "sha256": "ec30d59f51781b32c2e5c4556586e695e2f06439513478e97f33a41f4d24a3ea", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "lcod_off_by_one", + "capability": "wai.image.jxs", + "file": "gen/jxs/lcod_off_by_one.bin", + "origin": "mutation of ext/jxs/main422_10_64x64.jxs", + "sha256": "aeaa23b6f2978ffbd5ccaf07aa0d5ed210a85bdfb3147f88f0e765630a174b1e", + "outcome": { + "refuse": "length_mismatch" + } + }, + { + "name": "no_eoc", + "capability": "wai.image.jxs", + "file": "gen/jxs/no_eoc.bin", + "origin": "mutation of ext/jxs/main422_10_64x64.jxs", + "sha256": "a74c9cb6ca5ec1597da9b38582ace2e2277035cc9b173ba8bdeacebdb950d607", + "outcome": { + "refuse": "missing_structure" + } + }, + { + "name": "unknown_marker", + "capability": "wai.image.jxs", + "file": "gen/jxs/unknown_marker.bin", + "origin": "mutation of ext/jxs/main422_10_64x64.jxs", + "sha256": "584f7dcec7ad4130ecaec24c02d0a7ad75c00a8ae2d9fb5c37878ee7a07bad6b", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "cdt_length", + "capability": "wai.image.jxs", + "file": "gen/jxs/cdt_length.bin", + "origin": "mutation of ext/jxs/main422_10_64x64.jxs", + "sha256": "f7d63fc497ddb6772ddb3eadc30e90e01a446624bc438d861c555bdd4796c633", + "outcome": { + "refuse": "length_mismatch" + } + }, + { + "name": "truncated", + "capability": "wai.image.jxs", + "file": "gen/jxs/truncated.bin", + "origin": "mutation of ext/jxs/main422_10_64x64.jxs", + "sha256": "5eff9e0365ad02241428ed906790754a198300615a8ed1d6d1edb45bcc5419e9", + "outcome": { + "refuse": "truncated" + } + }, + { + "name": "zero_frames", + "capability": "wai.video.jxs", + "file": "gen/jxs-video/zero_frames.bin", + "origin": "mutation of ext/jxs-video/main420_8_64x48_3f.jxsv", + "sha256": "d61afb39a1daf176947b2b748edd364d6fcc7cf3fa57828e889f2bd003f38718", + "outcome": { + "refuse": "framing_invalid" + } + }, + { + "name": "width_too_small", + "capability": "wai.video.jxs", + "file": "gen/jxs-video/width_too_small.bin", + "origin": "mutation of ext/jxs-video/main420_8_64x48_3f.jxsv", + "sha256": "dd95a6929dfc368dbafc2291e0fa5a2e3c65595d54225b126c786e05fffefd36", + "outcome": { + "refuse": "framing_invalid" + } + }, + { + "name": "trailing_byte", + "capability": "wai.video.jxs", + "file": "gen/jxs-video/trailing_byte.bin", + "origin": "mutation of ext/jxs-video/main420_8_64x48_3f.jxsv", + "sha256": "a6829e80d8f20d5bcfb02558c72005d0d156f97270d4d2ab87393b9fbb5eafc0", + "outcome": { + "refuse": "trailing_bytes" + } + }, + { + "name": "frame_cut", + "capability": "wai.video.jxs", + "file": "gen/jxs-video/frame_cut.bin", + "origin": "mutation of ext/jxs-video/main420_8_64x48_3f.jxsv", + "sha256": "24e5d0b29be8b05d119078f219105141cf87ca861826e12ac0afdd2c8818e3d6", + "outcome": { + "refuse": "truncated" + } + }, + { + "name": "four_frames_stated", + "capability": "wai.video.jxs", + "file": "gen/jxs-video/four_frames_stated.bin", + "origin": "mutation of ext/jxs-video/main420_8_64x48_3f.jxsv", + "sha256": "d369ee688745a399ec30265724ed99a402e0b1fd4aa50c06e13c293977f2041b", + "outcome": { + "refuse": "truncated" + } + }, + { + "name": "no_sequence_header", + "capability": "wai.video.avs3", + "file": "gen/avs3/no_sequence_header.bin", + "origin": "mutation of ext/avs3/main_128x64.avs3", + "sha256": "573d9ca75e9ee7ead07e2cf92dbd50d8a555364228c24c3223a93bd60116c7fd", + "outcome": { + "refuse": "missing_structure" + } + }, + { + "name": "picture_first", + "capability": "wai.video.avs3", + "file": "gen/avs3/picture_first.bin", + "origin": "mutation of ext/avs3/main_128x64.avs3", + "sha256": "5df53897c1ecea5bddacf037ebda35f114c5d4b964cd4af0d93f131ed280527f", + "outcome": { + "refuse": "out_of_order" + } + }, + { + "name": "marker_cleared", + "capability": "wai.video.avs3", + "file": "gen/avs3/marker_cleared.bin", + "origin": "mutation of ext/avs3/main_128x64.avs3", + "sha256": "e3d2b95620773c45f353135063c4f403fb0e7352f4ed63a3576c4da7ee3dc13f", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "library_stream", + "capability": "wai.video.avs3", + "file": "gen/avs3/library_stream.bin", + "origin": "mutation of ext/avs3/main_128x64.avs3", + "sha256": "dcbdbf51651aeaf5d4de38ff4b198f167a8ad9defe4f3c72b73e8e7d2e82132c", + "outcome": { + "refuse": "outside_probe" + } + }, + { + "name": "truncated_header", + "capability": "wai.video.avs3", + "file": "gen/avs3/truncated_header.bin", + "origin": "mutation of ext/avs3/main_128x64.avs3", + "sha256": "8c33030d1d874e23466080c49e864a9c2fa7b5dee8070d9d07701a7e407f0b6d", + "outcome": { + "refuse": "truncated" + } + }, + { + "name": "no_start_code", + "capability": "wai.video.avs3", + "file": "gen/avs3/no_start_code.bin", + "origin": "mutation of ext/avs3/main_128x64.avs3", + "sha256": "50ff50ec0053e5d5cf9b7b9cc782b1101a75c2ac5f82a1f760e77cc9b483af2c", + "outcome": { + "refuse": "no_signature" + } + }, + { + "name": "main10_1080p_emulated", + "capability": "wai.video.avs3", + "file": "gen/avs3/main10_1080p_emulated.bin", + "origin": "generator", + "sha256": "3905f6f21a827c52eb2e6797b7e73bdb627e6b744f822b070236d3cb8d839135", + "outcome": { + "accept": { + "profile_id": 34, + "level_id": 32, + "progressive": true, + "field_coded": false, + "size": [ + 1920, + 1080 + ], + "chroma_format": 1, + "sample_precision": 1, + "encoding_precision": 1, + "aspect_ratio": 3, + "frame_rate_code": 6, + "bit_rate": 0, + "low_delay": false, + "temporal_id_enabled": false, + "bbv_buffer_size": 0, + "max_dpb_size": 4, + "pictures": [ + 1, + 0 + ], + "sequence_end": false + } + } + }, + { + "name": "frame_rate_zero", + "capability": "wai.video.avs3", + "file": "gen/avs3/frame_rate_zero.bin", + "origin": "generator", + "sha256": "09a7a53bf78ff47141efc34919a0df4a7dcf95608579873dd42d397a894153c3", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "channels_5_1", + "capability": "wai.audio.avs3", + "file": "gen/avs3-audio/channels_5_1.bin", + "origin": "generator", + "sha256": "f85b5729f82c30112d4c45160062d00277f6d1161c335be7a136c3ed020e74a0", + "outcome": { + "accept": { + "codec_id": 2, + "nn_type": 0, + "coding_profile": 0, + "sampling_rate": 48000, + "channel_number_index": 2, + "resolution_bits": 16, + "bitrate_index": 5 + } + } + }, + { + "name": "bed_and_objects", + "capability": "wai.audio.avs3", + "file": "gen/avs3-audio/bed_and_objects.bin", + "origin": "generator", + "sha256": "0e370761929c73cb02f212c794cf3689a72a7095313971dd67b53584d666ff59", + "outcome": { + "accept": { + "codec_id": 2, + "nn_type": 0, + "coding_profile": 1, + "sampling_rate": 48000, + "soundbed_type": 1, + "channel_number_index": 7, + "bitrate_index": 3, + "objects": 4, + "bitrate_index_per_object": 2, + "resolution_bits": 24 + } + } + }, + { + "name": "ambisonic_order_2", + "capability": "wai.audio.avs3", + "file": "gen/avs3-audio/ambisonic_order_2.bin", + "origin": "generator", + "sha256": "31bb167135ead769144d0da60bc2cd019ecf27dbdd96192bdfb7606d4d8ad39a", + "outcome": { + "accept": { + "codec_id": 2, + "nn_type": 0, + "coding_profile": 2, + "sampling_rate": 44100, + "ambisonic_order": 2, + "resolution_bits": 16, + "bitrate_index": 5 + } + } + }, + { + "name": "no_sync", + "capability": "wai.audio.avs3", + "file": "gen/avs3-audio/no_sync.bin", + "origin": "generator", + "sha256": "eaa1df028a380e4ce8a961e2bd109ea42f428f67738b366250b54de7c96aafbf", + "outcome": { + "refuse": "no_signature" + } + }, + { + "name": "lossless", + "capability": "wai.audio.avs3", + "file": "gen/avs3-audio/lossless.bin", + "origin": "generator", + "sha256": "bb09476490e384b3878b46d9dfc6a0c97049a3f76de3d968dfe1fe7247014117", + "outcome": { + "refuse": "outside_probe" + } + }, + { + "name": "profile_3", + "capability": "wai.audio.avs3", + "file": "gen/avs3-audio/profile_3.bin", + "origin": "generator", + "sha256": "88f89cc395f85f15609e86f300ff0dba43de8ea7f0445a150015f50145347ad3", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "rate_index_9", + "capability": "wai.audio.avs3", + "file": "gen/avs3-audio/rate_index_9.bin", + "origin": "generator", + "sha256": "284c66b54940a1fde5a8f44673d1712490f0845db6077767ecaca65fae19fd67", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "resolution_3", + "capability": "wai.audio.avs3", + "file": "gen/avs3-audio/resolution_3.bin", + "origin": "generator", + "sha256": "212acb88f6e01fd10d4d228704c013ac82b3f3b1ee9caa02f3b34ba783f46016", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "truncated", + "capability": "wai.audio.avs3", + "file": "gen/avs3-audio/truncated.bin", + "origin": "generator", + "sha256": "336ba7ec1b3d77e2736104121ef3d6337f84d37de533e458d19764b303d627c4", + "outcome": { + "refuse": "truncated" + } + }, + { + "name": "main_1080p", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/main_1080p.bin", + "origin": "generator", + "sha256": "d7a65ca31b739f1aef1c0f67109ff0f5feb2509204332f19f2dba86630216bb7", + "outcome": { + "accept": { + "nal_units": [ + 1, + 2 + ], + "pictures": 3, + "profile_idc": 1, + "level_idc": 2, + "sublevel_idc": 1, + "conformance_window": null, + "resolution_type": 26, + "size": [ + 1920, + 1080 + ], + "transform_type": 0, + "chroma_sampling_type": 1, + "base_depth": 8, + "enhancement_depth": 8, + "temporal_enabled": true, + "upsample_type": 2, + "tile_dimensions_type": 0 + } + } + }, + { + "name": "window_custom_tiles", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/window_custom_tiles.bin", + "origin": "generator", + "sha256": "d7d2251a5ed846048d9c796c89cb2d90a43ca14065da0edeef2821cd3942c7a7", + "outcome": { + "accept": { + "nal_units": [ + 1, + 1 + ], + "pictures": 2, + "profile_idc": 0, + "level_idc": 4, + "sublevel_idc": 1, + "conformance_window": [ + 1, + 2, + 300, + 4 + ], + "resolution_type": 63, + "size": [ + 360, + 100 + ], + "transform_type": 0, + "chroma_sampling_type": 1, + "base_depth": 10, + "enhancement_depth": 10, + "temporal_enabled": false, + "upsample_type": 4, + "tile_dimensions_type": 3 + } + } + }, + { + "name": "no_start_code", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/no_start_code.bin", + "origin": "generator", + "sha256": "fb6a0d3c9990de3a57ae25ed0d0e48b8dd1b8a180f6a2f961e86974d60b12931", + "outcome": { + "refuse": "no_signature" + } + }, + { + "name": "non_idr_first", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/non_idr_first.bin", + "origin": "generator", + "sha256": "2d93994a5d79a252c48e6ef8828138be0a3dbd1809986a34d712e413f645193f", + "outcome": { + "refuse": "out_of_order" + } + }, + { + "name": "reserved_flag", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/reserved_flag.bin", + "origin": "generator", + "sha256": "3dfac2b3d3640ebf7d5fd2c0d5f0fe82d8dc1a4c8c7c5f64b56c9db5c2f86de9", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "nal_type_27", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/nal_type_27.bin", + "origin": "generator", + "sha256": "1c717a45f046dc20a235b1e9d8558a4fa2129b992ab0439452c415df1bfb0568", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "stop_byte_0x81", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/stop_byte_0x81.bin", + "origin": "generator", + "sha256": "4ea37aa153b57eda2aa8f12feb35f8f8894271d5fb00e628b6229f3006ca7af9", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "block_overrun", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/block_overrun.bin", + "origin": "generator", + "sha256": "a2caca3f58609ba755fbc7ba742e4ad621c774124adde70fd7934406972d57f0", + "outcome": { + "refuse": "length_mismatch" + } + }, + { + "name": "size_type_6", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/size_type_6.bin", + "origin": "generator", + "sha256": "c775d856d75577228660ecba0bdbe2432e7e0d48ca71424877fe7c77b7e27920", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "no_global_config", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/no_global_config.bin", + "origin": "generator", + "sha256": "d289eac80136509deb42ec3494282decc6f2fcc8c5fd533e0394bd8195eb82b3", + "outcome": { + "refuse": "missing_structure" + } + }, + { + "name": "picture_before_global", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/picture_before_global.bin", + "origin": "generator", + "sha256": "bcd84b8e7ad8532db0b864b2d033485011fe03fd2600e87af38e37b30622dcb0", + "outcome": { + "refuse": "out_of_order" + } + }, + { + "name": "resolution_type_0", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/resolution_type_0.bin", + "origin": "generator", + "sha256": "c7093cc17d302d978c612e2e0c36dbd197a7fdd55199c2f6bc44cc64162902de", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "resolution_type_55", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/resolution_type_55.bin", + "origin": "generator", + "sha256": "2df19764201ffef59034a5e5ddf1c3229dee9be300dabc3cbe45b615305a3c23", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "upsample_type_6", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/upsample_type_6.bin", + "origin": "generator", + "sha256": "5a9f425ba169a7bd24b93f04e4581b73370a1cffa654f56f53b89321b6361c8a", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "sequence_reserved_bits", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/sequence_reserved_bits.bin", + "origin": "generator", + "sha256": "5a8f3a19aea6db88e66c65a74c186d4be224b0152856889742ed0be653ac600e", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "extended_profile", + "capability": "wai.video.lcevc", + "file": "gen/lcevc/extended_profile.bin", + "origin": "generator", + "sha256": "9c851bc10730dcabb1c5c1405a62b69f439a82848c4a89326f148efb75ae63d4", + "outcome": { + "refuse": "outside_probe" + } + }, + { + "name": "lc3_48k_5_1", + "capability": "wai.audio.mpegh", + "file": "gen/mhas/lc3_48k_5_1.bin", + "origin": "generator", + "sha256": "91a9aa36c477f242ed522ee1ae1f47daedb2d2a9f2976746091c1a20efadf014", + "outcome": { + "accept": { + "profile_level": 13, + "profile": "low-complexity", + "level": 3, + "sampling_rate": 48000, + "core_frame_length_index": 1, + "speaker_layout_type": 0, + "cicp_layout": 6, + "speakers": null, + "packets": 5, + "frames": 3, + "syncs": 1, + "configs": 1 + } + } + }, + { + "name": "baseline2_escaped_rate_speakers", + "capability": "wai.audio.mpegh", + "file": "gen/mhas/baseline2_escaped_rate_speakers.bin", + "origin": "generator", + "sha256": "b19ca46ebf451c29d51b18a33ada30a7351c4fe55bff3b20618d8f3036373460", + "outcome": { + "accept": { + "profile_level": 17, + "profile": "baseline", + "level": 2, + "sampling_rate": 47952, + "core_frame_length_index": 1, + "speaker_layout_type": 1, + "cicp_layout": null, + "speakers": 3, + "packets": 3, + "frames": 1, + "syncs": 0, + "configs": 1 + } + } + }, + { + "name": "frame_first", + "capability": "wai.audio.mpegh", + "file": "gen/mhas/frame_first.bin", + "origin": "generator", + "sha256": "a6c6ca4544c09888cdb5d2c0d2a4697bd804246a600d7dbe6b8c32c8d592d22f", + "outcome": { + "refuse": "no_signature" + } + }, + { + "name": "frame_before_config", + "capability": "wai.audio.mpegh", + "file": "gen/mhas/frame_before_config.bin", + "origin": "generator", + "sha256": "addab2a3ab7b51c62de606ac557ab94feb3b8438db45e5291a5ce85aefd27fe8", + "outcome": { + "refuse": "out_of_order" + } + }, + { + "name": "sync_byte", + "capability": "wai.audio.mpegh", + "file": "gen/mhas/sync_byte.bin", + "origin": "generator", + "sha256": "68b90450a109c3db4908d36f01920dda58f348c7adba49421ccf2dab8d259766", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "packet_overrun", + "capability": "wai.audio.mpegh", + "file": "gen/mhas/packet_overrun.bin", + "origin": "generator", + "sha256": "e59ec3f73d5cf5dbc613a2d5b871e6517bb609d784275d31d22b84cc7e32c379", + "outcome": { + "refuse": "truncated" + } + }, + { + "name": "rate_index_13", + "capability": "wai.audio.mpegh", + "file": "gen/mhas/rate_index_13.bin", + "origin": "generator", + "sha256": "f8534d08d79ed3ecd32798e6dbb9afc445d26dc59530fdaba240ff6b0a1b5e12", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "no_config", + "capability": "wai.audio.mpegh", + "file": "gen/mhas/no_config.bin", + "origin": "generator", + "sha256": "53428dfa8840650ac6ecaa8b3d73259fe5d409b2c513f0e9314c97ee54e8b43f", + "outcome": { + "refuse": "missing_structure" + } + }, + { + "name": "base_8bit", + "capability": "wai.image.ieee1857_11", + "file": "gen/ieee1857_11/base_8bit.bin", + "origin": "generator", + "sha256": "d4456f19ba6a496c4f3f5ac8ce30b440a0983401666487a502db945aeb416b52", + "outcome": { + "accept": { + "profile_id": 0, + "level_id": 1, + "model_id": 0, + "parameter_set_id": 7, + "quality_id": 3, + "size": [ + 768, + 512 + ], + "bit_depth": 8, + "picture_format": 0, + "slice_size": [ + 256, + 256 + ] + } + } + }, + { + "name": "main_10bit", + "capability": "wai.image.ieee1857_11", + "file": "gen/ieee1857_11/main_10bit.bin", + "origin": "generator", + "sha256": "8967ff4fe845cd27a0d612ece7d93abe87e536c82db201761eb6e8c2beb6b93c", + "outcome": { + "accept": { + "profile_id": 1, + "level_id": 2, + "model_id": 1, + "parameter_set_id": 33, + "quality_id": 5, + "size": [ + 1920, + 1080 + ], + "bit_depth": 10, + "picture_format": 1, + "slice_size": [ + 256, + 256 + ] + } + } + }, + { + "name": "profile_3", + "capability": "wai.image.ieee1857_11", + "file": "gen/ieee1857_11/profile_3.bin", + "origin": "generator", + "sha256": "030d54559bd5b27e12e32281da6a607156f236ec581401e4093900c3767afac3", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "depth_code_0", + "capability": "wai.image.ieee1857_11", + "file": "gen/ieee1857_11/depth_code_0.bin", + "origin": "generator", + "sha256": "1084233d2d1566e3ca90e0193bd09d00d66b950081ac58833e8b29f93dfb09de", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "top_bits", + "capability": "wai.image.ieee1857_11", + "file": "gen/ieee1857_11/top_bits.bin", + "origin": "generator", + "sha256": "366fc836ba68ab3990a0630548a006d3ac5e7b9c9f5bfa78e103dbd215dd92ad", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "truncated", + "capability": "wai.image.ieee1857_11", + "file": "gen/ieee1857_11/truncated.bin", + "origin": "generator", + "sha256": "fef287e28a6d6a7164ffeee52b9da5ef23b2ab9d7d5e7ebd8b8c1d115fe22e98", + "outcome": { + "refuse": "truncated" + } + }, + { + "name": "erp_mono", + "capability": "wai.meta.projection", + "file": "gen/projection/erp_mono.bin", + "origin": "generator", + "sha256": "d0e01da375ee6c0dd0e7511e8f06b322866243f578c1db08421f62bce9c733cf", + "outcome": { + "accept": { + "projection": "equirectangular", + "stereo": "mono", + "first_view": "left", + "coverage": [ + 23592960, + 11796480 + ], + "centre": [ + 0, + 0, + 0 + ], + "baseline_um": 0, + "layers": null + } + } + }, + { + "name": "half_erp_side_by_side", + "capability": "wai.meta.projection", + "file": "gen/projection/half_erp_side_by_side.bin", + "origin": "generator", + "sha256": "dd0af80ea219cb9895cd7b7562eb7699484dedd2e9f8e98d2c1f2c74a5addf1d", + "outcome": { + "accept": { + "projection": "half-equirectangular", + "stereo": "side-by-side", + "first_view": "left", + "coverage": [ + 11796480, + 11796480 + ], + "centre": [ + 0, + 0, + 0 + ], + "baseline_um": 63000, + "layers": null + } + } + }, + { + "name": "fisheye_top_bottom", + "capability": "wai.meta.projection", + "file": "gen/projection/fisheye_top_bottom.bin", + "origin": "generator", + "sha256": "7d7f225ccd8e0cad1ae04dabaefe30006fae26a728bc5e9062c7e2bdd81c3cbf", + "outcome": { + "accept": { + "projection": "fisheye", + "stereo": "top-bottom", + "first_view": "right", + "coverage": [ + 12451840, + 12451840 + ], + "centre": [ + 0, + 0, + 0 + ], + "baseline_um": 60000, + "layers": null + } + } + }, + { + "name": "rectilinear_layers", + "capability": "wai.meta.projection", + "file": "gen/projection/rectilinear_layers.bin", + "origin": "generator", + "sha256": "c6481b5378f298ac21a0651ae9121578056cc2131295136763342b3b7f093f8e", + "outcome": { + "accept": { + "projection": "rectilinear", + "stereo": "layers", + "first_view": "left", + "coverage": [ + 5898240, + 3866624 + ], + "centre": [ + 0, + 0, + 0 + ], + "baseline_um": 64000, + "layers": [ + 0, + 1 + ] + } + } + }, + { + "name": "rectilinear_unstated", + "capability": "wai.meta.projection", + "file": "gen/projection/rectilinear_unstated.bin", + "origin": "generator", + "sha256": "388927c016ec93a2046c98652fb877206100fedeea452479c4ec519aa99a81cf", + "outcome": { + "accept": { + "projection": "rectilinear", + "stereo": "mono", + "first_view": "left", + "coverage": [ + 0, + 0 + ], + "centre": [ + 0, + 0, + 0 + ], + "baseline_um": 0, + "layers": null + } + } + }, + { + "name": "magic", + "capability": "wai.meta.projection", + "file": "gen/projection/magic.bin", + "origin": "generator", + "sha256": "8663692d01b0d58d0ea3c0d63409148d456c0a3087da4e15b650303a1eafb9e1", + "outcome": { + "refuse": "no_signature" + } + }, + { + "name": "length_39", + "capability": "wai.meta.projection", + "file": "gen/projection/length_39.bin", + "origin": "generator", + "sha256": "68191baf8ae0fa0962b824a4b2f7a7f0c8d67d019fa81901ed59099e151b0b46", + "outcome": { + "refuse": "length_mismatch" + } + }, + { + "name": "version_2", + "capability": "wai.meta.projection", + "file": "gen/projection/version_2.bin", + "origin": "generator", + "sha256": "d36578c8c4d7518b746d286b611cc6efc74308964bc5411489f1b4c5126884c5", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "projection_4", + "capability": "wai.meta.projection", + "file": "gen/projection/projection_4.bin", + "origin": "generator", + "sha256": "65785b09d10e77a44591ca2056c62cfd9b196f15d256598e690a6634ddd7d95e", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "reserved_byte", + "capability": "wai.meta.projection", + "file": "gen/projection/reserved_byte.bin", + "origin": "generator", + "sha256": "080fbdfc4c6a349105be6b1edd8924140d51a7e09682fc446b02d5a9a20ec7ea", + "outcome": { + "refuse": "reserved_value" + } + }, + { + "name": "erp_359", + "capability": "wai.meta.projection", + "file": "gen/projection/erp_359.bin", + "origin": "generator", + "sha256": "db127bc7b90910890507f7f85ab3fd8c97e94c929269673df64bccab38190080", + "outcome": { + "refuse": "descriptor_invalid" + } + }, + { + "name": "mono_baseline", + "capability": "wai.meta.projection", + "file": "gen/projection/mono_baseline.bin", + "origin": "generator", + "sha256": "1ba4ea9a60019ceb78b8171a8609a19362b65fba3eed3f06029117bc2e7b4b0c", + "outcome": { + "refuse": "descriptor_invalid" + } + }, + { + "name": "layers_equal", + "capability": "wai.meta.projection", + "file": "gen/projection/layers_equal.bin", + "origin": "generator", + "sha256": "e3740d628f95e4180d83fe8f7a48b2ed530a55f05d35a9f644f1e0aab5dda41c", + "outcome": { + "refuse": "descriptor_invalid" + } + }, + { + "name": "centre_180", + "capability": "wai.meta.projection", + "file": "gen/projection/centre_180.bin", + "origin": "generator", + "sha256": "3908370d9a8b0ee09f96a4a11c4ade5555f8ce0904bf4d46ded3ffeb6367cd21", + "outcome": { + "refuse": "descriptor_invalid" + } + }, + { + "name": "png_has_no_probe", + "capability": "wai.image.png", + "file": "gen/other/png_has_no_probe.bin", + "origin": "generator", + "sha256": "4c4b6a3be1314ab86138bef4314dde022e600960d8689a2c8f8631802d20dab6", + "outcome": { + "refuse": "no_probe" + } + }, + { + "name": "rectilinear_layers_0_2", + "capability": "wai.meta.projection", + "file": "gen/projection/rectilinear_layers_0_2.bin", + "origin": "generator", + "sha256": "d326e933e92c218ee69467db96d42bfbc24440a2bab6b402bd565678dd541910", + "outcome": { + "accept": { + "projection": "rectilinear", + "stereo": "layers", + "first_view": "left", + "coverage": [ + 5898240, + 3866624 + ], + "centre": [ + 0, + 0, + 0 + ], + "baseline_um": 64000, + "layers": [ + 0, + 2 + ] + } + } + } + ], + "bindings": [ + { + "name": "layers_over_mvhevc", + "descriptor": "gen/projection/rectilinear_layers.bin", + "base_capability": "wai.video.mvhevc", + "base": "ext/mvhevc/2view_64x64.265", + "outcome": "accept" + }, + { + "name": "layer_2_over_mvhevc", + "descriptor": "gen/projection/rectilinear_layers_0_2.bin", + "base_capability": "wai.video.mvhevc", + "base": "ext/mvhevc/2view_64x64.265", + "outcome": { + "refuse": "layer_absent" + } + }, + { + "name": "layers_over_hevc", + "descriptor": "gen/projection/rectilinear_layers.bin", + "base_capability": "wai.video.hevc", + "base": "ext/hevc/base_64x64.265", + "outcome": { + "refuse": "layer_absent" + } + }, + { + "name": "side_by_side_over_hevc", + "descriptor": "gen/projection/half_erp_side_by_side.bin", + "base_capability": "wai.video.hevc", + "base": "ext/hevc/base_64x64.265", + "outcome": "accept" + } + ], + "fetched": [ + { + "file": "parse_std.bin", + "sha256": "0ad059dd167093ef773aa9be932afdf0cd2108656daaed18bfc7ee8041c4ee05", + "expect": { + "/conformance_window": [ + 1, + 2, + 3, + 4 + ], + "/tile_dimensions_type": 3, + "/nal_units": [ + 1, + 2 + ] + } + }, + { + "file": "parse_gops.bin", + "sha256": "20b7d666216b20419b6e4d80b2d9b68b8c9b4a66f9ba01849895e00d7c9d2583", + "expect": { + "/nal_units": [ + 3, + 2 + ] + } + }, + { + "file": "parse_hdr.bin", + "sha256": "1492419eb19f34dd1e4dd3f50b3525a32f2bb34ad48fdeea8e4e9fc1ab54c149", + "expect": { + "/nal_units": [ + 1, + 0 + ], + "/base_depth": 10 + } + }, + { + "file": "decode_temp_on.bin", + "sha256": "156568e8c5cbb0307e5c1b6dde25a8932315abfffe063140dfa6366edea4c9e4", + "expect": { + "/temporal_enabled": true + } + }, + { + "file": "decode_temp_off.bin", + "sha256": "a486c0a40abdfdde11d95b51491a3b78a273a48b75548e7a86e32de361df781c", + "expect": { + "/temporal_enabled": false + } + } + ] +} -- 2.54.0 (Apple Git-157) From 4845a3eb6ae6756de0459370f2e37774eef88592 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 07:59:05 -0400 Subject: [PATCH 015/242] =?UTF-8?q?wai:=20register=20the=20tensor-file=20p?= =?UTF-8?q?rior=20forms=20and=20the=20mapping=20identifiers;=20SPEC=20?= =?UTF-8?q?=C2=A75,=20CHANGELOG=20and=20CI=20steps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wai.prior.safetensors and wai.prior.gguf join SPEC §5's prior-artifact table (lossless, royalty-free, StandardDefined, established exact); the NNC row says what the reference now reads. Wire identifiers wai-int/1, wsr1/1, whm1/1 (prior-carriage §10), wai-scene/1 and urn:wai:asset: (scene-carriage §2). A §5 paragraph states scene carriage. The registry export is regenerated. wai-conformance.yml runs both modules' tests and both independent verifiers, and fires on edits of the two extension texts. Clippy notes on the new code fixed. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/wai-conformance.yml | 43 ++++++++++++++++ wai/CHANGELOG.md | 37 ++++++++++++++ wai/REGISTRY.md | 12 ++++- wai/SPEC.md | 13 ++++- wai/registry/capabilities.json | 63 ++++++++++++++++++++++++ wai/vocab/v2.shapes.ttl | 8 +++ wai/vocab/v2.ttl | 25 ++++++++++ wai/wai-rs/src/codecs/registry.rs | 7 +++ wai/wai-rs/src/lib.rs | 3 +- wai/wai-rs/src/prior_wire.rs | 4 +- wai/wai-rs/src/prior_wire/binding.rs | 9 +++- wai/wai-rs/src/prior_wire/cabac.rs | 2 +- wai/wai-rs/src/prior_wire/corpus.rs | 4 +- wai/wai-rs/src/prior_wire/gguf.rs | 4 +- wai/wai-rs/src/prior_wire/nnc.rs | 10 ++-- wai/wai-rs/src/prior_wire/safetensors.rs | 4 +- wai/wai-rs/src/scene_map.rs | 19 ++++--- wai/wai-rs/src/scene_map/corpus.rs | 6 ++- wai/wai-rs/src/scene_map/gltf.rs | 19 ++++--- wai/wai-rs/src/scene_map/num.rs | 2 +- wai/wai-rs/src/scene_map/usda.rs | 6 +-- wai/wai-rs/src/strict_json.rs | 13 +---- 22 files changed, 259 insertions(+), 54 deletions(-) diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index 40c5188d9..117c42721 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -135,6 +135,14 @@ on: # reports, upscale_verify.py reads that table, and the registry's wire identifiers test # reads its sections. - 'wai/extensions/exact-upscale.md' + # tests/prior_wire.rs requires this extension's §13 to list the codes the reference + # reports, prior_forms_verify.py reads that table, and the registry's wire identifiers + # test reads its sections. + - 'wai/extensions/prior-carriage.md' + # tests/scene_map.rs requires this extension's §8 to list the codes the reference + # reports, scene_verify.py reads that table, and the registry's wire identifiers test + # reads its sections. + - 'wai/extensions/scene-carriage.md' # tests/ai_usage.rs requires this extension's §9 to list the codes the reference reports, # ai_usage_verify.py reads that table, and the registry's wire identifiers test reads its # sections. @@ -1294,6 +1302,41 @@ jobs: (cd wai-rs && cargo test --no-default-features --features exact_upscale --lib exact_upscale::) (cd wai-rs && cargo test --no-default-features --features exact_upscale --test exact_upscale) python3 -I live-conformance/upscale_verify.py live-conformance/upscale + # Tensor files as prior forms (extensions/prior-carriage.md §9 to §14): the module's unit + # tests (each layout read strictly and written canonically, the arithmetic coder's integers + # round-tripping and a damaged payload caught, the profile's checks in their order, both + # bindings through every form) and tests/prior_wire.rs (the committed corpus regenerates + # byte for byte from its pinned native artifacts; §13 lists exactly the reference's codes; + # every corpus prior in every form loads to its native bytes, and the two trained WSR1 + # priors give the same upscaled picture as their native files); then the corpus's second + # implementation, prior_forms_verify.py, written from the extension's text and sharing no + # code with wai-rs, which also requires every byte-flipped copy of an accepted file to be + # caught; standard library only, run isolated (-I). + - name: prior forms · wai-rs and prior_forms_verify.py + working-directory: wai + shell: bash + run: | + set -euo pipefail + (cd wai-rs && cargo test --no-default-features --features prior_wire --lib -- prior_wire:: strict_json:: codecs::prior_form) + (cd wai-rs && cargo test --no-default-features --features prior_wire,neural_int --test prior_wire) + python3 -I live-conformance/prior_forms_verify.py live-conformance/prior-forms + # Scene carriage (extensions/scene-carriage.md): the module's unit tests (the conversions + # and spellings of §3 against fixed values, the containers an import builds equal to the + # ones wai-rs's world and audio-scene modules write, base64 and the USD subset's reader) + # and tests/scene_map.rs (the committed corpus regenerates byte for byte: every bundle + # written as glTF and USD and read back, every refusal reached; §8 lists exactly the + # reference's codes); then the corpus's second implementation, scene_verify.py, written + # from the extension's text and sharing no code with wai-rs, which also requires every + # byte-flipped copy of an exported document to be caught; standard library only, run + # isolated (-I). + - name: scene carriage · wai-rs and scene_verify.py + working-directory: wai + shell: bash + run: | + set -euo pipefail + (cd wai-rs && cargo test --no-default-features --features scene_map --lib -- scene_map:: strict_json::) + (cd wai-rs && cargo test --no-default-features --features scene_map --test scene_map) + python3 -I live-conformance/scene_verify.py live-conformance/scene # wai.ai.usage (extensions/ai-usage.md): the reference module's unit tests (RFC 9651's # Dictionary parser against the vocabulary's examples and the RFC's edges, statements # read and refused in their steps' order, the precedence rule, HTTP fields compared by diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index 39e1210ff..9acd1bdfe 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -6,6 +6,43 @@ wire format may change and such changes are recorded here. ## v0.4.0 — current +### Priors in tensor files and NNC, and scenes in MPEG-I scene description and USD + +- **Tensor files as prior forms** ([prior-carriage](extensions/prior-carriage.md) §9 to §14). + `wai.prior.safetensors` and `wai.prior.gguf` are registered prior forms: a pin's digest covers + the whole file. Each layout is read strictly (a name twice, overlapping or uncovered bytes, a + header past the file are each refused with a named code) and written canonically, and a reader + refuses a file that is not canonical, so one set of tensors has one digest in each form. The + registry lists both forms as established exact: reading hands the decoder each tensor's bytes + as the file holds them. +- **The integer-quantised profile `wai-int/1`** (§10): every tensor a signed integer the kernel + reads as it is, every scale itself an integer tensor, nothing floating point, so loading does no + rounding. Two bindings map native integer artifacts to named tensors and back, byte for byte: + `wsr1/1` (the `WSR1` prior of exact-upscale) and `whm1/1` (the `WHM1` model of + integer-payloads). The two trained `WSR1` priors of the exact-upscale corpus, carried in each + form, load to the same native bytes and give the same upscaled pictures. +- **NNC read and written** (§12 and Appendix A): the subset of ISO/IEC 15938-17 an open + implementation of its 2024 edition reads — the start, model parameter set and topology units, + and data units of integer tensors under the standard's context-adaptive arithmetic coding or + of raw 32-bit floats. Quantised payloads, dependent quantisation, block scans and the other unit + types are refused with `nnc_not_read`, naming what was met; the standard's own clauses were not + read. Measured against that implementation (§12.4): its decoder reads our files to the same + integers, its encoder writes byte-identical data units, and our reader reads its bitstreams. +- **Scene carriage** ([scene-carriage](extensions/scene-carriage.md)): a world, an audio scene, + a world's assets and timed media items written as an ISO/IEC 23090-14 glTF document + (`MPEG_media`, `MPEG_buffer_circular`, `MPEG_accessor_timed`, `MPEG_audio_spatial`, + `MPEG_texture_video`) and as a USD layer of the USD Core Specification v1.0.1 (prims, + translate operations, time samples, asset references by `urn:wai:asset:`), each carrying the + WAI bytes exactly. Reading a document WAI wrote gives the same bytes, and is refused unless + writing them again gives the document; a document WAI did not write becomes new WAI bytes + under stated rules, refused where a rotation, an inexact position, a time between samples, a + gain in decibels or media that names no WAI object would not carry. Meshes travel by content + hash, as a world names its assets. +- **Reference implementation and corpora.** `wai-rs` features `prior_wire` and `scene_map`; + corpora [`live-conformance/prior-forms/`](live-conformance/prior-forms/) and + [`live-conformance/scene/`](live-conformance/scene/), with negative controls for every code, + and their independent verifiers `prior_forms_verify.py` and `scene_verify.py`. + ### `wai.media.upscale`: super-resolution as an exact derivation, residuals against it, and an account of sink-made pictures - **The extension** ([exact-upscale](extensions/exact-upscale.md)) registers the record diff --git a/wai/REGISTRY.md b/wai/REGISTRY.md index 5cf04f36e..6213256f8 100644 --- a/wai/REGISTRY.md +++ b/wai/REGISTRY.md @@ -64,8 +64,9 @@ they serve ([jwp-receipts](extensions/jwp-receipts.md) §7), [live-rooms](extensions/live-rooms.md), [capture-binding](extensions/capture-binding.md), [live-compose](extensions/live-compose.md), [live-recording](extensions/live-recording.md), [timed-text](extensions/timed-text.md), [ai-usage](extensions/ai-usage.md), -[exploration](extensions/exploration.md) and [browser-sink](extensions/browser-sink.md) put on -the wire outside a manifest — magic numbers, a media type, `codecs` prefixes, paths, playlist tags and +[exploration](extensions/exploration.md), [browser-sink](extensions/browser-sink.md), +[prior-carriage](extensions/prior-carriage.md) and +[scene-carriage](extensions/scene-carriage.md) put on the wire outside a manifest — magic numbers, a media type, `codecs` prefixes, paths, playlist tags and attributes, document kinds, catalog fields, MOQT Setup Options, HTTP fields and a member of a decoded frame's metadata — each with its kind, the section that defines it, and whether it is defined, provisional (test material only until @@ -245,7 +246,9 @@ and a role are fixed once registered. | `wai.neural.wavtokenizer` | payload | registered | audio | standalone | royalty-free | neural-float | — | §5 Neural capabilities | | `wai.pc.gpcc` | payload | registered | volumetric | standalone | patent-pool | standard-unclassified | — | §5 Immersive audio, cinema, volumetric, haptics, HDR | | `wai.pc.vpcc` | payload | registered | volumetric | standalone | patent-pool | standard-unclassified | — | §5 Immersive audio, cinema, volumetric, haptics, HDR | +| `wai.prior.gguf` | payload | registered | binary | standalone | royalty-free | standard-defined | — | §5 Prior artifacts, general tensors and derivation operations | | `wai.prior.nnc` | payload | registered | binary | standalone | patent-pool | standard-unclassified | — | §5 Prior artifacts, general tensors and derivation operations | +| `wai.prior.safetensors` | payload | registered | binary | standalone | royalty-free | standard-defined | — | §5 Prior artifacts, general tensors and derivation operations | | `wai.prior.set` | payload | registered | binary | standalone | royalty-free | standard-defined | — | §5 Prior artifacts, general tensors and derivation operations | | `wai.quantum.calibration` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content | | `wai.quantum.circuit` | payload | registered | binary | standalone | royalty-free | standard-defined | — | §5 Interactive, temporal & computed content | @@ -389,6 +392,7 @@ and a role are fixed once registered. | `tag:transaction.science,2026:wai/model/` | scheme | defined | timed-text §10.3 | | `tag:transaction.science,2026:wai/stages` | scheme | defined | packaging §8 | | `tag:transaction.science,2026:wai/timed-text` | scheme | defined | timed-text §10.4 | +| `urn:wai:asset:` | scheme | defined | scene-carriage §2.2 | | `urn:wai:event:1` | scheme | defined | live-events §8.4 | | `urn:wai:event:1` | catalog | defined | live-events §8.1 | | `wai` | catalog | defined | moq-streaming-format §3 | @@ -412,6 +416,7 @@ and a role are fixed once registered. | `wai-directed` | document-kind | defined | direction §8.1 | | `wai-distance` | document-kind | defined | exploration §6.3 | | `wai-influence-report` | document-kind | defined | influence §7.1 | +| `wai-int/1` | scheme | defined | prior-carriage §10 | | `wai-key-status` | document-kind | defined | jwp-receipts §7.9 | | `wai-keyframes` | document-kind | defined | direction §5 | | `wai-label-policy` | document-kind | defined | synthesis §10.1 | @@ -430,6 +435,7 @@ and a role are fixed once registered. | `wai-receipt-index` | document-kind | defined | http-delivery §8.2 | | `wai-receipts` | catalog | defined | moq-streaming-format §4.1 | | `wai-review` | document-kind | defined | synthesis §9.1 | +| `wai-scene/1` | scheme | defined | scene-carriage §2.3 | | `wai-sequence` | document-kind | defined | direction §6 | | `wai-session` | document-kind | defined | contribution §3.1 | | `wai-signing` | document-kind | defined | access-services §6.1 | @@ -464,4 +470,6 @@ and a role are fixed once registered. | `wai_receipts` | catalog | defined | moq-streaming-format §4 | | `wai_receipts_track` | catalog | defined | moq-streaming-format §4 | | `wai_staged` | catalog | defined | moq-streaming-format §4.1 | +| `whm1/1` | scheme | defined | prior-carriage §10.2 | +| `wsr1/1` | scheme | defined | prior-carriage §10.2 | diff --git a/wai/SPEC.md b/wai/SPEC.md index 18d124311..4df75816f 100644 --- a/wai/SPEC.md +++ b/wai/SPEC.md @@ -794,8 +794,10 @@ reconstruction, with the acquisition class and coverage C2PA 2.4's | capability | payload | status · extension | |---|---|---| -| `wai.prior.nnc` | a trained network's parameters as an ISO/IEC 15938-17:2024 (NNC, 2nd edition) bitstream | a registered wire form a pin MAY declare (`prior.form`); its digest then covers the coded bitstream. Optional, not recommended: royalty status is not established, and cross-decoder exactness of the reconstruction is not established, so a `replicate` over a neural capability cannot rest on a prior read out of it. As a payload it takes the replicate class of a standard whose decoder conformance the registry has not checked · [prior-carriage](extensions/prior-carriage.md) | +| `wai.prior.nnc` | a trained network's parameters as an ISO/IEC 15938-17:2024 (NNC, 2nd edition) bitstream | a registered wire form a pin MAY declare (`prior.form`); its digest then covers the coded bitstream. Optional, not recommended: royalty status is not established, and cross-decoder exactness of the reconstruction is not established, so a `replicate` over a neural capability cannot rest on a prior read out of it. The reference reads and writes a subset — integer tensors under the standard's arithmetic coding and raw 32-bit floats — measured against an open implementation of the standard, and refuses quantised payloads by name. As a payload it takes the replicate class of a standard whose decoder conformance the registry has not checked · [prior-carriage](extensions/prior-carriage.md) | | `wai.prior.set` | the listing of a parameter set of one or more files: each file's SHA-256 and path (§3.1) | a registered wire form a pin MAY declare (`prior.form`), and the form in which a set of several files is pinned: the pin covers the listing, and each line covers one file. Reading a set through its listing compares digests and hands the decoder each file's bytes unchanged, so the registry lists it as established exact, and a `replicate` MAY rest on a pin in it. WAI-native, royalty-free; as a payload its output is its own bytes (`StandardDefined`) · [prior-carriage](extensions/prior-carriage.md) | +| `wai.prior.safetensors` | a parameter set as one safetensors file: an 8-byte header length, a JSON header naming each tensor's type, shape and bytes, then the tensors' bytes | a registered wire form a pin MAY declare (`prior.form`); its digest covers the whole file, which a reader requires canonical, so one set of tensors has one digest. Reading it hands the decoder each tensor's bytes as the file holds them, so the registry lists it as established exact. A prior in the integer-quantised profile `wai-int/1` loads, bit for bit, into the native artifact an integer kernel reads. Royalty-free (the layout is published with its reference implementation under Apache-2.0); as a payload `StandardDefined` · [prior-carriage](extensions/prior-carriage.md) | +| `wai.prior.gguf` | a parameter set as one GGUF version 3 file: key-value metadata, tensor descriptions, aligned tensor data | as `wai.prior.safetensors`: a registered wire form, its digest over the whole canonical file, established exact, and in the profile `wai-int/1` a prior that loads bit for bit into the integer kernels. Royalty-free (published under MIT); as a payload `StandardDefined` · [prior-carriage](extensions/prior-carriage.md) | | `wai.tensor.nnc` | general tensorial data (split-inference feature maps, 3D/4D scene parameters) coded with NNC | **candidate**: general-tensor coding is in an unpublished amendment, so the payload format is not fixed and MUST NOT be emitted until pinned (*Candidate capabilities*, below) · [prior-carriage](extensions/prior-carriage.md) | The ten derivation operations of ISO/IEC 23001-16:2021 (derived visual tracks) @@ -858,6 +860,15 @@ standard. See the linked extension for each. | `wai.quantum.circuit` | WQC container (gate op-log — Clifford+T + dyadic phase) | the wire carries a **circuit** (kilobytes, linear in gate count), the sink CLASSICALLY simulates it in a fixed-point **complex** floor (`Amp` = i64 re/im), and the reconstructed `2^n`-amplitude statevector is byte-identical on every machine — **statevector-equivalence** (a portable statevector hash); measurement is sampled by the pinned `splitmix64` so a shot histogram at a pinned `(seed, shots)` converges too (**shot-histogram-equivalence**). The dyadic phase ladder transports the **QFT** ("quantum FFT") as a circuit, reconstructed byte-exact; the phase table is frozen as existing content was made with it, so `P(k)` deviates from the ideal phase for k ≥ 8 and applies none for k ≥ 19, and new content uses `wai.quantum.circuit2`. No quantum hardware — NISQ noise breaks the determinism the standard rests on; this makes a quantum *computation* transportable + receiptable, with **no** speedup claimed · [quantum-sim](extensions/quantum-sim.md) | | `wai.quantum.circuit2` | WQC container under magic `WQC2` (the same gate op-log) | the successor of `wai.quantum.circuit`, with the same statevector- and shot-histogram-equivalence: a correctly rounded phase table (each component of every `P(k)` within half a unit of 2^30·e^{2Ï€i/2^k}), so the dyadic phase ladder gives the **QFT**; a byte-canonical contract; an op-log that ends exactly after its last op. A new capability, not a change to the old one, because a registered capability's payload format is fixed (§8): a reader that predates it rejects a `WQC2` payload at the magic · [quantum-sim](extensions/quantum-sim.md) | +*Scene carriage* ([scene-carriage](extensions/scene-carriage.md)) maps a world (`WWLD`), +an audio scene (`WSCN`), the assets a world names and timed media items onto ISO/IEC +23090-14 scene description (a glTF 2.0 document with `MPEG_media`, `MPEG_buffer_circular`, +`MPEG_accessor_timed`, `MPEG_audio_spatial` and `MPEG_texture_video`) and onto a USD layer +of the USD Core Specification v1.0.1, in both directions. It registers no capability: a +document carries the WAI payloads exactly beside the presentation its format's players +read, reading it back gives the same bytes or is refused, and a document WAI did not write +becomes new WAI bytes only where every value carries exactly. + The contract of a `wai.quantum.circuit2` payload names its arithmetic `wai.det.amp64`: complex amplitudes as `i64` pairs at scale `2^frac`, each product exact and then rounded half up, each sum exact. Inside a diff --git a/wai/registry/capabilities.json b/wai/registry/capabilities.json index a16da3ba0..9f9e0c5c9 100644 --- a/wai/registry/capabilities.json +++ b/wai/registry/capabilities.json @@ -1336,6 +1336,20 @@ "edition": null, "registered": "2026-06-17" }, + { + "capability": "wai.prior.gguf", + "kind": "payload", + "status": "registered", + "media": "binary", + "role": "standalone", + "tier": "lossless", + "licence": "royalty-free", + "replicate": "standard-defined", + "sets": [], + "spec_section": "§5 Prior artifacts, general tensors and derivation operations", + "edition": "GGUF version 3", + "registered": "2026-10-22" + }, { "capability": "wai.prior.nnc", "kind": "payload", @@ -1350,6 +1364,20 @@ "edition": "ISO/IEC 15938-17:2024", "registered": "2026-09-28" }, + { + "capability": "wai.prior.safetensors", + "kind": "payload", + "status": "registered", + "media": "binary", + "role": "standalone", + "tier": "lossless", + "licence": "royalty-free", + "replicate": "standard-defined", + "sets": [], + "spec_section": "§5 Prior artifacts, general tensors and derivation operations", + "edition": null, + "registered": "2026-10-22" + }, { "capability": "wai.prior.set", "kind": "payload", @@ -2627,6 +2655,13 @@ "section": "§10.4", "status": "defined" }, + { + "name": "urn:wai:asset:", + "kind": "scheme", + "document": "extensions/scene-carriage.md", + "section": "§2.2", + "status": "defined" + }, { "name": "urn:wai:event:1", "kind": "scheme", @@ -2788,6 +2823,13 @@ "section": "§7.1", "status": "defined" }, + { + "name": "wai-int/1", + "kind": "scheme", + "document": "extensions/prior-carriage.md", + "section": "§10", + "status": "defined" + }, { "name": "wai-key-status", "kind": "document-kind", @@ -2914,6 +2956,13 @@ "section": "§9.1", "status": "defined" }, + { + "name": "wai-scene/1", + "kind": "scheme", + "document": "extensions/scene-carriage.md", + "section": "§2.3", + "status": "defined" + }, { "name": "wai-sequence", "kind": "document-kind", @@ -3151,6 +3200,20 @@ "document": "extensions/moq-streaming-format.md", "section": "§4.1", "status": "defined" + }, + { + "name": "whm1/1", + "kind": "scheme", + "document": "extensions/prior-carriage.md", + "section": "§10.2", + "status": "defined" + }, + { + "name": "wsr1/1", + "kind": "scheme", + "document": "extensions/prior-carriage.md", + "section": "§10.2", + "status": "defined" } ] } diff --git a/wai/vocab/v2.shapes.ttl b/wai/vocab/v2.shapes.ttl index 858902707..f82b53270 100644 --- a/wai/vocab/v2.shapes.ttl +++ b/wai/vocab/v2.shapes.ttl @@ -68,7 +68,9 @@ wai:ManifestShape a sh:NodeShape ; wai:wai.neural.wavtokenizer wai:wai.pc.gpcc wai:wai.pc.vpcc + wai:wai.prior.gguf wai:wai.prior.nnc + wai:wai.prior.safetensors wai:wai.prior.set wai:wai.quantum.circuit wai:wai.quantum.circuit2 @@ -151,7 +153,9 @@ wai:ManifestShape a sh:NodeShape ; wai:wai.neural.wavtokenizer wai:wai.pc.gpcc wai:wai.pc.vpcc + wai:wai.prior.gguf wai:wai.prior.nnc + wai:wai.prior.safetensors wai:wai.prior.set wai:wai.quantum.circuit wai:wai.quantum.circuit2 @@ -248,7 +252,9 @@ wai:ComponentShape a sh:NodeShape ; wai:wai.neural.wavtokenizer wai:wai.pc.gpcc wai:wai.pc.vpcc + wai:wai.prior.gguf wai:wai.prior.nnc + wai:wai.prior.safetensors wai:wai.prior.set wai:wai.quantum.circuit wai:wai.quantum.circuit2 @@ -331,7 +337,9 @@ wai:ComponentShape a sh:NodeShape ; wai:wai.neural.wavtokenizer wai:wai.pc.gpcc wai:wai.pc.vpcc + wai:wai.prior.gguf wai:wai.prior.nnc + wai:wai.prior.safetensors wai:wai.prior.set wai:wai.quantum.circuit wai:wai.quantum.circuit2 diff --git a/wai/vocab/v2.ttl b/wai/vocab/v2.ttl index 30762872b..d651c7d48 100644 --- a/wai/vocab/v2.ttl +++ b/wai/vocab/v2.ttl @@ -1004,6 +1004,19 @@ wai:wai.pc.vpcc a wai:Capability ; wai:specSection "§5 Immersive audio, cinema, volumetric, haptics, HDR" ; wai:registeredOn "2026-06-17"^^xsd:date . +wai:wai.prior.gguf a wai:Capability ; + rdfs:label "wai.prior.gguf" ; + wai:kind wai:kind-payload ; + wai:status wai:status-registered ; + wai:media wai:media-binary ; + wai:role wai:role-standalone ; + wai:tier wai:tier-lossless ; + wai:licence wai:licence-royalty-free ; + wai:replicateClass wai:replicate-standard-defined ; + wai:edition "GGUF version 3" ; + wai:specSection "§5 Prior artifacts, general tensors and derivation operations" ; + wai:registeredOn "2026-10-22"^^xsd:date . + wai:wai.prior.nnc a wai:Capability ; rdfs:label "wai.prior.nnc" ; wai:kind wai:kind-payload ; @@ -1017,6 +1030,18 @@ wai:wai.prior.nnc a wai:Capability ; wai:specSection "§5 Prior artifacts, general tensors and derivation operations" ; wai:registeredOn "2026-09-28"^^xsd:date . +wai:wai.prior.safetensors a wai:Capability ; + rdfs:label "wai.prior.safetensors" ; + wai:kind wai:kind-payload ; + wai:status wai:status-registered ; + wai:media wai:media-binary ; + wai:role wai:role-standalone ; + wai:tier wai:tier-lossless ; + wai:licence wai:licence-royalty-free ; + wai:replicateClass wai:replicate-standard-defined ; + wai:specSection "§5 Prior artifacts, general tensors and derivation operations" ; + wai:registeredOn "2026-10-22"^^xsd:date . + wai:wai.prior.set a wai:Capability ; rdfs:label "wai.prior.set" ; wai:kind wai:kind-payload ; diff --git a/wai/wai-rs/src/codecs/registry.rs b/wai/wai-rs/src/codecs/registry.rs index 9a50a1f0e..04382ac68 100644 --- a/wai/wai-rs/src/codecs/registry.rs +++ b/wai/wai-rs/src/codecs/registry.rs @@ -587,6 +587,8 @@ const ACCESS_SERVICES: &str = "extensions/access-services.md"; const AGE_ASSURANCE: &str = "extensions/age-assurance.md"; const EXPLORATION: &str = "extensions/exploration.md"; const EXACT_UPSCALE: &str = "extensions/exact-upscale.md"; +const PRIOR_CARRIAGE: &str = "extensions/prior-carriage.md"; +const SCENE_CARRIAGE: &str = "extensions/scene-carriage.md"; const fn wire(name: &'static str, kind: WireKind, document: &'static str, section: &'static str, status: WireStatus) -> WireIdentifier { @@ -699,6 +701,11 @@ pub const WIRE_IDENTIFIERS: &[WireIdentifier] = &[ wire("WSR1", WireKind::Magic, EXACT_UPSCALE, "§3.1", WireStatus::Defined), wire("wai-upscale", WireKind::DocumentKind, EXACT_UPSCALE, "§4.2", WireStatus::Defined), wire("wai-presentation", WireKind::DocumentKind, EXACT_UPSCALE, "§6.2", WireStatus::Defined), + wire("wai-int/1", WireKind::Scheme, PRIOR_CARRIAGE, "§10", WireStatus::Defined), + wire("wsr1/1", WireKind::Scheme, PRIOR_CARRIAGE, "§10.2", WireStatus::Defined), + wire("whm1/1", WireKind::Scheme, PRIOR_CARRIAGE, "§10.2", WireStatus::Defined), + wire("wai-scene/1", WireKind::Scheme, SCENE_CARRIAGE, "§2.3", WireStatus::Defined), + wire("urn:wai:asset:", WireKind::Scheme, SCENE_CARRIAGE, "§2.2", WireStatus::Defined), wire("WAU1", WireKind::Magic, AI_USAGE, "§4.2", WireStatus::Defined), wire("wai_ai_usage", WireKind::Catalog, MOQ_FORMAT, "§4", WireStatus::Defined), wire("WAI-Usage-Claim", WireKind::HttpHeader, AI_USAGE, "§7.3", WireStatus::Proposed), diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 6c3affe95..fcde455dd 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -621,7 +621,8 @@ pub mod prior_wire; #[cfg(feature = "scene_map")] pub mod scene_map; -// The strict JSON reader the two mappings above share. +// The strict JSON reader the two mappings above share. Rust-only. +/// cbindgen:ignore #[cfg(any(feature = "prior_wire", feature = "scene_map"))] pub(crate) mod strict_json; // Bearer receipts (extensions/bearer-receipts.md): `wai.bearer.receipts` — one receipt on every diff --git a/wai/wai-rs/src/prior_wire.rs b/wai/wai-rs/src/prior_wire.rs index a32b38abf..404c876fa 100644 --- a/wai/wai-rs/src/prior_wire.rs +++ b/wai/wai-rs/src/prior_wire.rs @@ -170,7 +170,7 @@ impl DType { DType::I16 | DType::U16 | DType::F16 | DType::BF16 => n.checked_mul(2), DType::I32 | DType::U32 | DType::F32 => n.checked_mul(4), DType::I64 | DType::U64 | DType::F64 => n.checked_mul(8), - DType::Q8_0 => (n % 32 == 0).then(|| (n / 32).checked_mul(34)).flatten(), + DType::Q8_0 => n.is_multiple_of(32).then(|| (n / 32).checked_mul(34)).flatten(), } } /// The signed integer types of the profile (§10.1): `I8`, `I16`, `I32`, `I64`. @@ -225,7 +225,7 @@ impl Tensor { /// The values of a profile integer tensor, sign-extended. pub fn ints(&self) -> Option> { let w = self.dtype.int_width()?; - if self.data.len() % w != 0 { + if !self.data.len().is_multiple_of(w) { return None; } Some( diff --git a/wai/wai-rs/src/prior_wire/binding.rs b/wai/wai-rs/src/prior_wire/binding.rs index 0ca969d27..b73ae61a3 100644 --- a/wai/wai-rs/src/prior_wire/binding.rs +++ b/wai/wai-rs/src/prior_wire/binding.rs @@ -65,6 +65,11 @@ pub fn of(set: &TensorSet, form: Form) -> Result<&'static Binding, Refusal> { Ok(b) } +/// A binding's range for a value: `(tensor name, index)` to its bounds, `None` for its type's. +type Range<'a> = &'a dyn Fn(&str, usize) -> Option<(i64, i64)>; +/// The values a check returns, by tensor name. +type Values = BTreeMap>; + /// One tensor a binding names: its name, element type and shape. struct Want { name: String, @@ -76,7 +81,7 @@ struct Want { /// exactly these (`binding_mismatch`), then each tensor's type (the binding's, or `I32` in /// NNC) and shape (`binding_mismatch`), then each value within the type the binding gives /// and the range `range` gives (`value_out_of_range`). Returns the values by name. -fn check(set: &TensorSet, form: Form, wants: &[Want], range: &dyn Fn(&str, usize) -> Option<(i64, i64)>) -> Result>, Refusal> { +fn check(set: &TensorSet, form: Form, wants: &[Want], range: Range<'_>) -> Result { let have: BTreeSet<&str> = set.tensors.iter().map(|t| t.name.as_str()).collect(); let need: BTreeSet<&str> = wants.iter().map(|w| w.name.as_str()).collect(); if have != need { @@ -211,7 +216,7 @@ fn wsr1_from(b: &[u8]) -> Result { let k = u64::from(k); let w = r.ints((cout * cin * k * k) as usize, 1)?; let bias = r.ints(cout as usize, 4)?; - let mult: Vec = r.take(2 * cout as usize)?.chunks_exact(2).map(|c| i64::from(u16::from_le_bytes([c[0], c[1]]))).collect(); + let mult: Vec = r.take(2 * cout as usize)?.as_chunks::<2>().0.iter().map(|c| i64::from(u16::from_le_bytes(*c))).collect(); if mult.contains(&0) { return Err(native_err("field_range")); } diff --git a/wai/wai-rs/src/prior_wire/cabac.rs b/wai/wai-rs/src/prior_wire/cabac.rs index c83e7b9b9..1daac1000 100644 --- a/wai/wai-rs/src/prior_wire/cabac.rs +++ b/wai/wai-rs/src/prior_wire/cabac.rs @@ -461,7 +461,7 @@ mod tests { fn a_damaged_payload_is_refused_or_decodes_to_other_integers() { let vals: Vec = (0..300).map(|i| (i * 37 % 23) - 11).collect(); let b = encode_ints(&vals, 11).unwrap(); - assert_eq!(decode_ints(&b[..b.len() - 1], 300, 11).err().map(|r| r.code).is_some(), true); + assert!(decode_ints(&b[..b.len() - 1], 300, 11).is_err()); for i in 0..b.len() { let mut m = b.clone(); m[i] ^= 0x10; diff --git a/wai/wai-rs/src/prior_wire/corpus.rs b/wai/wai-rs/src/prior_wire/corpus.rs index 3aed91851..61ab68898 100644 --- a/wai/wai-rs/src/prior_wire/corpus.rs +++ b/wai/wai-rs/src/prior_wire/corpus.rs @@ -50,7 +50,7 @@ fn ext(form: Form) -> &'static str { /// A safetensors file with header text `h` (padded with spaces to 8 bytes) and `data`. fn st_raw(h: &str, data: &[u8]) -> Vec { let mut h = h.as_bytes().to_vec(); - while (8 + h.len()) % 8 != 0 { + while !(8 + h.len()).is_multiple_of(8) { h.push(b' '); } let mut o = (h.len() as u64).to_le_bytes().to_vec(); @@ -366,7 +366,7 @@ fn gguf_insert_kv(gg: &[u8], at: usize, kv: &[u8]) -> Vec { hdr.splice(at..at, kv.iter().copied()); let n = u64::from_le_bytes(hdr[16..24].try_into().unwrap()) + 1; hdr[16..24].copy_from_slice(&n.to_le_bytes()); - while hdr.len() % 32 != 0 { + while !hdr.len().is_multiple_of(32) { hdr.push(0); } hdr.extend_from_slice(&gg[gguf_data_start(gg)..]); diff --git a/wai/wai-rs/src/prior_wire/gguf.rs b/wai/wai-rs/src/prior_wire/gguf.rs index e3908ff19..2391a310b 100644 --- a/wai/wai-rs/src/prior_wire/gguf.rs +++ b/wai/wai-rs/src/prior_wire/gguf.rs @@ -169,7 +169,7 @@ pub fn read(b: &[u8]) -> Result { let ty = c.u32()?; let Some(dtype) = DType::from_gguf_type(ty) else { return refuse("dtype_unknown", format!("{name:?}: type {ty}")) }; let offset = c.u64()?; - if offset % alignment != 0 { + if !offset.is_multiple_of(alignment) { return refuse("offset_mismatch", format!("{name:?}: offset {offset} is not a multiple of {alignment}")); } if dtype == DType::Q8_0 && dims[0] % 32 != 0 { @@ -255,7 +255,7 @@ pub fn write(set: &TensorSet) -> Result, Refusal> { at += t.data.len() as u64; } let pad = |o: &mut Vec, base: usize| { - while (o.len() - base) as u64 % DEFAULT_ALIGNMENT != 0 { + while !((o.len() - base) as u64).is_multiple_of(DEFAULT_ALIGNMENT) { o.push(0); } }; diff --git a/wai/wai-rs/src/prior_wire/nnc.rs b/wai/wai-rs/src/prior_wire/nnc.rs index 42dc2a14d..bdf0d6536 100644 --- a/wai/wai-rs/src/prior_wire/nnc.rs +++ b/wai/wai-rs/src/prior_wire/nnc.rs @@ -65,7 +65,7 @@ impl Bits<'_> { if self.u(1)? != 1 { return refuse("nnc_malformed", "a byte alignment that does not begin with a 1 bit"); } - while self.bit % 8 != 0 { + while !self.bit.is_multiple_of(8) { if self.u(1)? != 0 { return refuse("nnc_malformed", "a byte alignment padded with a 1 bit"); } @@ -73,7 +73,7 @@ impl Bits<'_> { Ok(()) } fn st(&mut self) -> Result { - if self.bit % 8 != 0 { + if !self.bit.is_multiple_of(8) { return refuse("nnc_malformed", "a string that does not begin on a byte"); } let at = self.bit / 8; @@ -95,7 +95,7 @@ struct W { impl W { fn u(&mut self, n: u32, v: u64) { for i in (0..n).rev() { - if self.bit % 8 == 0 { + if self.bit.is_multiple_of(8) { self.b.push(0); } if (v >> i) & 1 == 1 { @@ -116,12 +116,12 @@ impl W { } fn align(&mut self) { self.u(1, 1); - while self.bit % 8 != 0 { + while !self.bit.is_multiple_of(8) { self.u(1, 0); } } fn st(&mut self, s: &str) { - debug_assert!(self.bit % 8 == 0); + debug_assert!(self.bit.is_multiple_of(8)); self.b.extend_from_slice(s.as_bytes()); self.b.push(0); self.bit = self.b.len() * 8; diff --git a/wai/wai-rs/src/prior_wire/safetensors.rs b/wai/wai-rs/src/prior_wire/safetensors.rs index 39ddc8dd8..55d6aa7ae 100644 --- a/wai/wai-rs/src/prior_wire/safetensors.rs +++ b/wai/wai-rs/src/prior_wire/safetensors.rs @@ -49,9 +49,9 @@ pub fn read(b: &[u8]) -> Result { let Some(dtype) = DType::from_safetensors_name(ds) else { return refuse("dtype_unknown", format!("{name:?}: dtype {ds:?}")); }; - let shape: Option> = v.get("shape").and_then(Json::as_arr).map(|a| a.iter().map(Json::as_u64).collect()).flatten(); + let shape: Option> = v.get("shape").and_then(Json::as_arr).and_then(|a| a.iter().map(Json::as_u64).collect()); let Some(shape) = shape else { return refuse("tensor_malformed", format!("{name:?}: shape is not a list of integers")) }; - let offs: Option> = v.get("data_offsets").and_then(Json::as_arr).map(|a| a.iter().map(Json::as_u64).collect()).flatten(); + let offs: Option> = v.get("data_offsets").and_then(Json::as_arr).and_then(|a| a.iter().map(Json::as_u64).collect()); let Some(offs) = offs.filter(|o| o.len() == 2 && o[0] <= o[1]) else { return refuse("tensor_malformed", format!("{name:?}: data_offsets is not [begin, end] with begin ≤ end")); }; diff --git a/wai/wai-rs/src/scene_map.rs b/wai/wai-rs/src/scene_map.rs index d61b0d4a9..903264439 100644 --- a/wai/wai-rs/src/scene_map.rs +++ b/wai/wai-rs/src/scene_map.rs @@ -119,7 +119,7 @@ pub(crate) fn hex(b: &[u8]) -> String { } pub(crate) fn unhex(s: &str) -> Option> { - if s.len() % 2 != 0 || !s.bytes().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) { + if !s.len().is_multiple_of(2) || !s.bytes().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) { return None; } (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok()).collect() @@ -204,7 +204,7 @@ pub(crate) struct GraphNode { pub(crate) struct WorldView { pub sim: String, pub nodes: Vec, - pub assets: Vec<(Vec, u8, String)>, + pub assets: Vec, } pub(crate) fn read_world(b: &[u8]) -> Result { @@ -252,7 +252,7 @@ pub(crate) fn read_world(b: &[u8]) -> Result { } /// The asset table of interactive-worlds §4.1. -pub(crate) fn read_assets(a: &[u8], code: &'static str) -> Result, u8, String)>, Refusal> { +pub(crate) fn read_assets(a: &[u8], code: &'static str) -> Result, Refusal> { if a.len() < 4 { return refuse(code, "the asset table is truncated"); } @@ -417,6 +417,11 @@ pub(crate) fn check_bundle(b: &Bundle) -> Result<(Option, Option); +/// An asset table entry: hash, role, name. +pub(crate) type AssetEntryV = (Vec, u8, String); + /// The Q32.32 sum of two positions, or `None` past the range. pub(crate) fn add(a: [i64; 3], b: [i64; 3]) -> Option<[i64; 3]> { Some([a[0].checked_add(b[0])?, a[1].checked_add(b[1])?, a[2].checked_add(b[2])?]) @@ -432,8 +437,8 @@ pub(crate) fn kinematic_state(pos: [i64; 3]) -> Vec { } /// A `WWLD` payload of a contract and tick-0 nodes, no assets, an empty op log (§5.3). -pub(crate) fn world_bytes(contract: &[u8], nodes: &mut [([u8; 16], Vec)], assets: &[(Vec, u8, String)]) -> Vec { - nodes.sort_by(|a, b| a.0.cmp(&b.0)); +pub(crate) fn world_bytes(contract: &[u8], nodes: &mut [([u8; 16], Vec)], assets: &[AssetEntryV]) -> Vec { + nodes.sort_by_key(|a| a.0); let mut graph = (nodes.len() as u32).to_le_bytes().to_vec(); for (id, st) in nodes.iter() { graph.extend_from_slice(id); @@ -448,7 +453,7 @@ pub(crate) fn world_bytes(contract: &[u8], nodes: &mut [([u8; 16], Vec)], as container(b"WWLD", &secs) } -pub(crate) fn asset_table(assets: &[(Vec, u8, String)]) -> Vec { +pub(crate) fn asset_table(assets: &[AssetEntryV]) -> Vec { let mut a = (assets.len() as u32).to_le_bytes().to_vec(); for (h, r, n) in assets { a.extend_from_slice(h); @@ -478,7 +483,7 @@ pub(crate) fn container(magic: &[u8; 4], secs: &[(u8, Vec)]) -> Vec { } /// A `WSCN` payload of a contract, objects of gain 1 and assets (§5.3). -pub(crate) fn audio_bytes(contract: &[u8], objects: &[([u8; 32], Vec<(u64, [i64; 3])>)], assets: &[(Vec, u8, String)]) -> Vec { +pub(crate) fn audio_bytes(contract: &[u8], objects: &[ObjectKeys], assets: &[AssetEntryV]) -> Vec { let mut t = (objects.len() as u32).to_le_bytes().to_vec(); for (src, keys) in objects { t.extend_from_slice(src); diff --git a/wai/wai-rs/src/scene_map/corpus.rs b/wai/wai-rs/src/scene_map/corpus.rs index ba0a7c7b3..cd88e9a03 100644 --- a/wai/wai-rs/src/scene_map/corpus.rs +++ b/wai/wai-rs/src/scene_map/corpus.rs @@ -246,7 +246,8 @@ pub fn generate(inputs: &BTreeMap>) -> BTreeMap> let video = label("asset/screen.av1"); let amb = label("asset/ambience.flac"); let screen = media("screen", video, "wai.video.av1", Some((64, 36))); - let bundles: Vec<(&str, Option, Option, Vec)> = vec![ + type Spec<'a> = (&'a str, Option, Option, Vec); + let bundles: Vec> = vec![ ("kinematic", Some("inputs/kinematic.wwld".into()), None, vec![screen.clone(), media("ambience", amb, "wai.audio.flac", None)]), ("ballistic", Some("inputs/ballistic.wwld".into()), None, vec![]), ("truss", Some("inputs/truss.wwld".into()), None, vec![]), @@ -308,7 +309,8 @@ pub fn generate(inputs: &BTreeMap>) -> BTreeMap> super::audio_bytes(&super::sections(&moving, b"WSCN", "audio_malformed").unwrap()[&1], &[(v.objects[0].source, keys)], &[]) }; files.insert("inputs/neg/descending.wscn".into(), descending); - let neg_bundles: Vec<(&str, Option<&str>, Option<&str>, Vec)> = vec![ + type NegSpec<'a> = (&'a str, Option<&'a str>, Option<&'a str>, Vec); + let neg_bundles: Vec> = vec![ ("unordered_world", Some("inputs/neg/unordered.wwld"), None, vec![]), ("truncated_audio", None, Some("inputs/neg/truncated.wscn"), vec![]), ("descending_keys", None, Some("inputs/neg/descending.wscn"), vec![]), diff --git a/wai/wai-rs/src/scene_map/gltf.rs b/wai/wai-rs/src/scene_map/gltf.rs index ac5c1db9e..a55843ab7 100644 --- a/wai/wai-rs/src/scene_map/gltf.rs +++ b/wai/wai-rs/src/scene_map/gltf.rs @@ -13,6 +13,9 @@ use crate::strict_json::{self, quote, Json}; /// The samples one frame of an audio source's timed accessor holds (§4.4). pub const AUDIO_FRAME: u64 = 1024; +/// A media item as the export lists it: name, hash, capability, picture size. +type Item = (String, [u8; 32], Option, Option<(u32, u32)>); + /// A JSON value to write, its members in the order given. enum J { O(Vec<(&'static str, J)>), @@ -91,7 +94,7 @@ pub fn export(bundle: &Bundle) -> Result, Refusal> { } } // §4.4: media items — the audio scene's sources first, then the bundle's other items. - let mut items: Vec<(String, [u8; 32], Option, Option<(u32, u32)>)> = Vec::new(); + let mut items: Vec = Vec::new(); let mut by_hash: BTreeMap<[u8; 32], &MediaItem> = BTreeMap::new(); for m in &bundle.media { by_hash.entry(m.asset).or_insert(m); @@ -344,7 +347,7 @@ pub(crate) fn unbase64(s: &str) -> Option> { } as u32) }; let b = s.as_bytes(); - if b.len() % 4 != 0 { + if !b.len().is_multiple_of(4) { return None; } let mut o = Vec::with_capacity(b.len() / 4 * 3); @@ -399,7 +402,7 @@ fn f32_of(v: &Json) -> Option { /// Read `bytes` as a glTF document (§5): one WAI wrote, back to its bundle; any other, to a /// new bundle under §5.3 with `params`. pub fn import(bytes: &[u8], params: &ImportParams) -> Result { - let doc = strict_json::read(bytes).map_err(|e| bad(e))?; + let doc = strict_json::read(bytes).map_err(bad)?; if doc.as_obj().is_none() || doc.get("asset").and_then(|a| a.get("version")).and_then(Json::as_str) != Some("2.0") { return Err(bad("not a glTF 2.0 document")); } @@ -558,7 +561,7 @@ fn foreign(doc: &Json, p: &ImportParams) -> Result { let j = strict_json::read(c).map_err(|_| Refusal::new("contract_absent", "the audio contract is not JSON"))?; j.get("sample_rate").and_then(Json::as_u64).filter(|&r| (1..=u64::from(u32::MAX)).contains(&r)).ok_or_else(|| Refusal::new("contract_absent", "the audio contract has no sample_rate")) }; - let mut objects: Vec<([u8; 32], Vec<(u64, [i64; 3])>)> = Vec::new(); + let mut objects: Vec = Vec::new(); let mut source_items: Vec = Vec::new(); let mut source_names: Vec<([u8; 32], String)> = Vec::new(); for &n in &audio_nodes { @@ -611,13 +614,13 @@ fn foreign(doc: &Json, p: &ImportParams) -> Result { // Step 5: the other media items. let textures = arr(doc, "textures"); let mut media: Vec = Vec::new(); - for k in 0..media_items.len() { + for (k, item) in media_items.iter().enumerate() { if source_items.contains(&k) { continue; } let (hash, cap) = wai_alt(k).ok_or_else(|| Refusal::new("media_not_wai", format!("media {k}: no WAI object")))?; let cap = cap.ok_or_else(|| Refusal::new("media_not_wai", format!("media {k}: no capability in its mimeType")))?; - let name = media_items[k].get("name").and_then(Json::as_str).unwrap_or("").to_string(); + let name = item.get("name").and_then(Json::as_str).unwrap_or("").to_string(); let mut size = None; if super::is_picture(&cap) { for t in textures { @@ -661,7 +664,7 @@ fn foreign(doc: &Json, p: &ImportParams) -> Result { /// A sample index from a binary32 time, when `t · rate` is a whole number (§5.3 step 4). pub(crate) fn time_to_sample(t: f32, rate: u64) -> Option { - if !(t >= 0.0) || !t.is_finite() { + if t.is_nan() || t < 0.0 || !t.is_finite() { return None; } if t == 0.0 { @@ -709,7 +712,7 @@ fn read_floats(doc: &Json, a: usize, ty: &str) -> Result, Refusal> { if off.checked_add(need).is_none_or(|e| e > data.len() || e > vstart + len) { return Err(bad(format!("accessor {a} runs past its view or buffer"))); } - Ok(data[off..off + need].chunks_exact(4).map(|c| f32::from_le_bytes(c.try_into().unwrap())).collect()) + Ok(data[off..off + need].as_chunks::<4>().0.iter().map(|c| f32::from_le_bytes(*c)).collect()) } #[cfg(test)] diff --git a/wai/wai-rs/src/scene_map/num.rs b/wai/wai-rs/src/scene_map/num.rs index 08d41a2d8..6eba7d918 100644 --- a/wai/wai-rs/src/scene_map/num.rs +++ b/wai/wai-rs/src/scene_map/num.rs @@ -114,7 +114,7 @@ pub fn json_number(v: f64) -> String { format!("0.{}{d}", "0".repeat((-n) as usize)) } else { let rest = if k > 1 { format!(".{}", &d[1..]) } else { String::new() }; - format!("{}{rest}e{}{}", &d[..1], if n - 1 >= 0 { "+" } else { "-" }, (n - 1).abs()) + format!("{}{rest}e{}{}", &d[..1], if n >= 1 { "+" } else { "-" }, (n - 1).abs()) }; format!("{sign}{body}") } diff --git a/wai/wai-rs/src/scene_map/usda.rs b/wai/wai-rs/src/scene_map/usda.rs index 5ba265c1b..41e15f8cd 100644 --- a/wai/wai-rs/src/scene_map/usda.rs +++ b/wai/wai-rs/src/scene_map/usda.rs @@ -212,11 +212,7 @@ fn lex(text: &str) -> Result, Refusal> { let c = b[i]; if matches!(c, ' ' | '\t' | '\r' | '\n') { i += 1; - } else if c == '#' { - while i < b.len() && b[i] != '\n' && b[i] != '\r' { - i += 1; - } - } else if c == '/' && b.get(i + 1) == Some(&'/') { + } else if c == '#' || (c == '/' && b.get(i + 1) == Some(&'/')) { while i < b.len() && b[i] != '\n' && b[i] != '\r' { i += 1; } diff --git a/wai/wai-rs/src/strict_json.rs b/wai/wai-rs/src/strict_json.rs index 2cba3e188..364801863 100644 --- a/wai/wai-rs/src/strict_json.rs +++ b/wai/wai-rs/src/strict_json.rs @@ -9,6 +9,8 @@ //! in a string and anything after the value are refused. /// The most levels of nesting a document may have. +/// +/// cbindgen:ignore pub const MAX_DEPTH: usize = 64; /// A JSON value with its members in written order and its numbers as spelled. @@ -57,16 +59,6 @@ impl Json { _ => None, } } - /// A number spelled as a plain decimal integer, optionally negative, that fits `i64`. - pub fn as_i64(&self) -> Option { - match self { - Json::Num(s) => { - let d = s.strip_prefix('-').unwrap_or(s); - if is_plain_uint(d) && s != "-0" { s.parse().ok() } else { None } - } - _ => None, - } - } } fn is_plain_uint(s: &str) -> bool { @@ -330,7 +322,6 @@ mod tests { } assert_eq!(Json::Num("12".into()).as_u64(), Some(12)); assert_eq!(Json::Num("012".into()).as_u64(), None); - assert_eq!(Json::Num("-0".into()).as_i64(), None); assert_eq!(quote("a\"\u{1}\n"), "\"a\\\"\\u0001\\n\""); let deep = "[".repeat(MAX_DEPTH + 2) + &"]".repeat(MAX_DEPTH + 2); assert!(read(deep.as_bytes()).is_err()); -- 2.54.0 (Apple Git-157) From 51fda1988264364abd4b6fddd8f86e3281ff3e8a Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 10:36:01 -0400 Subject: [PATCH 016/242] =?UTF-8?q?wai:=20CMSD=20=E2=80=94=20a=20reader=20?= =?UTF-8?q?for=20CMSD-Static=20and=20CMSD-Dynamic,=20the=20custom=20keys?= =?UTF-8?q?=20registered=20as=20wire=20identifiers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit delivery-telemetry §6.6 reads a CMSD field back into the description §6.1 to §6.3 write, passing over other senders' keys, and refuses with cmsd_malformed, cmsd_type or cmsd_version; §10 lists the extension's CMCD and CMSD custom keys in full, and the registry lists each as a wire identifier of the new kind telemetry-key (and WPJ1, the projection descriptor's magic). telemetry-conformance/cmsd-read/ holds the fields and their outcomes; cmsd_read_verify.py is the second implementation. Co-Authored-By: Claude Opus 5.5 --- wai/REGISTRY.md | 33 +- wai/extensions/delivery-telemetry.md | 58 ++- wai/registry/capabilities.json | 168 +++++++++ wai/registry/capabilities.schema.json | 3 +- wai/telemetry-conformance/README.md | 11 + .../cmsd-read/cases.json | 87 +++++ wai/telemetry-conformance/cmsd_read_verify.py | 330 ++++++++++++++++++ .../expected/cmsd-read.json | 142 ++++++++ wai/wai-rs/src/bin/wai_telemetry.rs | 72 +++- wai/wai-rs/src/codecs/registry.rs | 34 +- wai/wai-rs/src/telemetry/cmsd.rs | 176 ++++++++++ 11 files changed, 1104 insertions(+), 10 deletions(-) create mode 100644 wai/telemetry-conformance/cmsd-read/cases.json create mode 100644 wai/telemetry-conformance/cmsd_read_verify.py create mode 100644 wai/telemetry-conformance/expected/cmsd-read.json diff --git a/wai/REGISTRY.md b/wai/REGISTRY.md index 203bc2c04..e9ebbe95b 100644 --- a/wai/REGISTRY.md +++ b/wai/REGISTRY.md @@ -64,10 +64,11 @@ they serve ([jwp-receipts](extensions/jwp-receipts.md) §7), [live-rooms](extensions/live-rooms.md), [capture-binding](extensions/capture-binding.md), [live-compose](extensions/live-compose.md), [live-recording](extensions/live-recording.md), [timed-text](extensions/timed-text.md), [ai-usage](extensions/ai-usage.md), -[exploration](extensions/exploration.md) and [browser-sink](extensions/browser-sink.md) put on -the wire outside a manifest — magic numbers, a media type, `codecs` prefixes, paths, playlist tags and -attributes, document kinds, catalog fields, MOQT Setup Options, HTTP fields and a member of a -decoded frame's metadata — each with its kind, the section that +[exploration](extensions/exploration.md), [browser-sink](extensions/browser-sink.md), +[delivery-telemetry](extensions/delivery-telemetry.md) and [projection](extensions/projection.md) +put on the wire outside a manifest — magic numbers, a media type, `codecs` prefixes, paths, playlist tags and +attributes, document kinds, catalog fields, MOQT Setup Options, HTTP fields, a member of a +decoded frame's metadata and the custom keys of CMCD and CMSD reports — each with its kind, the section that defines it, and whether it is defined, provisional (test material only until registered) or proposed (not registered). A library test checks that each is written in the section it names. @@ -354,6 +355,7 @@ and a role are fixed once registered. | `WPF1` | magic | defined | co-performance §5.6 | | `WPH1` | magic | defined | co-performance §6.3 | | `WPI1` | magic | defined | co-performance §5.1 | +| `WPJ1` | magic | defined | projection §3 | | `WPK1` | magic | defined | co-performance §7.5 | | `WPM1` | magic | defined | co-performance §6.7 | | `WPP1` | magic | defined | co-performance §3.1 | @@ -382,6 +384,29 @@ and a role are fixed once registered. | `contentBinding` | frame-metadata | proposed | browser-sink §7.1 | | `mr/` | path | defined | http-delivery §8.6 | | `rcpt/` | path | defined | http-delivery §8.1 | +| `science.transaction.wai-b` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-cap` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-cf` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-ch` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-clm` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-co` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-coh` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-cr` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-dec` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-drop` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-l` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-late` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-lc` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-lcf` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-lt` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-op` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-p` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-pl` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-rcpt` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-ri` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-sw` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-tx` | telemetry-key | defined | delivery-telemetry §10 | +| `science.transaction.wai-u` | telemetry-key | defined | delivery-telemetry §10 | | `science.transaction.wai.class` | data-id | defined | packaging §8 | | `science.transaction.wai.event` | playlist-attribute | defined | live-events §8.3 | | `science.transaction.wai.multivariant` | data-id | defined | http-delivery §11.1 | diff --git a/wai/extensions/delivery-telemetry.md b/wai/extensions/delivery-telemetry.md index 36f65d90d..3ad0f7e38 100644 --- a/wai/extensions/delivery-telemetry.md +++ b/wai/extensions/delivery-telemetry.md @@ -182,6 +182,25 @@ only when its value is not `null`, and a Boolean (`su`, `du`) only when true. §6.5 CMSD carries no joule figure. An edge's energy travels in a claim. +§6.6 **Reading.** A reader reads `CMSD-Static` as an RFC 9651 Dictionary and `CMSD-Dynamic` +as a List of Strings with parameters, in the subset §3.3 writes (Integers, Strings, Tokens and +Booleans; a dictionary member with parameters, or a Decimal, is outside it); a field it cannot +read so is `cmsd_malformed`. It passes over keys and parameters this section does not name: a +sender may add its own. A key it names with another type is `cmsd_type`: `at`, `d`, `ht`, +`etp`, `mb`, `rd` and `rtt` are non-negative Integers; `nor`, `nrr` and each node's server are +non-empty Strings; `ot`, `sf`, `st` and `science.transaction.wai-op` are Tokens; `su` and +`du` are Booleans, either value; `science.transaction.wai-ch` and `-rcpt` are Strings of 64 +lowercase hexadecimal digits; `science.transaction.wai-l`, `-lt` and `-drop` are Strings of +decimal digits, with no leading zero but in `"0"` and at most 15 digits. A `v` other than the +Integer 1 is `cmsd_version` (a `v` that is not an Integer is `cmsd_type`). A field this section +writes reads back to the description it was written from. + +| code | meaning | +|---|---| +| `cmsd_malformed` | the field is not a Dictionary or List of §3.3's subset | +| `cmsd_type` | a key or parameter §6.1 to §6.3 name holds another type or form | +| `cmsd_version` | the field states a CMSD version other than 1 | + ## 7. Session metrics §7.1 A **log** is one session's observations in `sn` order: one `sid`, `sn` strictly @@ -235,7 +254,10 @@ suppressed, not rounded. - observations every reader refuses (`invalid/`); - CMSD-Static and CMSD-Dynamic descriptions with their expected fields (`expected/cmsd.json`); -- session logs with their expected metrics (`expected/qoe.json`). +- session logs with their expected metrics (`expected/qoe.json`); +- CMSD fields to read (`cmsd-read/cases.json`), as §6.1 to §6.3 write them, in another key + order, with `v=1` and with keys of other senders, and fields each code of §6.6 refuses, with + their outcomes (`expected/cmsd-read.json`). §9.2 `wai_telemetry verify` requires every expected value byte for byte, and every `invalid/` observation refused. @@ -246,3 +268,37 @@ with an independent open-source CMCD and CMSD library at the versions expected strings back as a dictionary, as a query string and as header shards, and requires the report's members; and validates every report, and the Event-mode reports together, with no error. + +## 10. Wire names + +§10.1 The custom keys this extension puts in CMCD (CTA-5004-B) and CMSD (CTA-5006) reports, +each in full: + +| key | report | section | +|---|---|---| +| `science.transaction.wai-b` | CMCD | §4.1 | +| `science.transaction.wai-tx` | CMCD | §4.1 | +| `science.transaction.wai-ch` | CMCD, CMSD-Static | §4.1, §6.1 | +| `science.transaction.wai-cap` | CMCD | §4.1 | +| `science.transaction.wai-ri` | CMCD | §4.1 | +| `science.transaction.wai-l` | CMCD, CMSD-Static | §4.1, §6.1 | +| `science.transaction.wai-lt` | CMCD, CMSD-Static | §4.1, §6.1 | +| `science.transaction.wai-p` | CMCD | §4.1 | +| `science.transaction.wai-rcpt` | CMCD, CMSD-Static | §4.1, §6.1 | +| `science.transaction.wai-lc` | CMCD | §4.1 | +| `science.transaction.wai-lcf` | CMCD | §4.1 | +| `science.transaction.wai-coh` | CMCD | §4.1 | +| `science.transaction.wai-u` | CMCD | §4.1 | +| `science.transaction.wai-pl` | CMCD | §4.1 | +| `science.transaction.wai-late` | CMCD | §4.1 | +| `science.transaction.wai-dec` | CMCD | §4.1 | +| `science.transaction.wai-sw` | CMCD | §4.1 | +| `science.transaction.wai-co` | CMCD | §4.1 | +| `science.transaction.wai-cr` | CMCD | §4.1 | +| `science.transaction.wai-cf` | CMCD | §4.1 | +| `science.transaction.wai-clm` | CMCD | §4.1 | +| `science.transaction.wai-op` | CMSD-Static | §6.2 | +| `science.transaction.wai-drop` | CMSD-Dynamic | §6.3 | + +§10.2 The registry lists each as a wire identifier of kind `telemetry-key` +([REGISTRY](../REGISTRY.md)). diff --git a/wai/registry/capabilities.json b/wai/registry/capabilities.json index de2c469c3..16e00d251 100644 --- a/wai/registry/capabilities.json +++ b/wai/registry/capabilities.json @@ -2417,6 +2417,13 @@ "section": "§5.1", "status": "defined" }, + { + "name": "WPJ1", + "kind": "magic", + "document": "extensions/projection.md", + "section": "§3", + "status": "defined" + }, { "name": "WPK1", "kind": "magic", @@ -2613,6 +2620,167 @@ "section": "§8.1", "status": "defined" }, + { + "name": "science.transaction.wai-b", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-cap", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-cf", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-ch", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-clm", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-co", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-coh", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-cr", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-dec", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-drop", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-l", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-late", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-lc", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-lcf", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-lt", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-op", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-p", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-pl", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-rcpt", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-ri", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-sw", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-tx", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, + { + "name": "science.transaction.wai-u", + "kind": "telemetry-key", + "document": "extensions/delivery-telemetry.md", + "section": "§10", + "status": "defined" + }, { "name": "science.transaction.wai.class", "kind": "data-id", diff --git a/wai/registry/capabilities.schema.json b/wai/registry/capabilities.schema.json index 8cc578282..b0fffd48f 100644 --- a/wai/registry/capabilities.schema.json +++ b/wai/registry/capabilities.schema.json @@ -321,7 +321,8 @@ "catalog", "setup-option", "http-header", - "frame-metadata" + "frame-metadata", + "telemetry-key" ] }, "document": { diff --git a/wai/telemetry-conformance/README.md b/wai/telemetry-conformance/README.md index 6c09c2cfa..3876d4016 100644 --- a/wai/telemetry-conformance/README.md +++ b/wai/telemetry-conformance/README.md @@ -25,6 +25,12 @@ their metrics. Every value here is a string or an integer, the same on every mac `expected/refused.json` lists them. - `cmsd/` — CMSD-Static descriptions of a base and of a derived layer, and a CMSD-Dynamic field from two nodes (§6); `expected/cmsd.json`, their fields. +- `cmsd-read/cases.json` — CMSD fields to read (§6.6): fields as §6.1 to §6.3 write them, + the same with their keys in another order, with `v=1` and with a key or parameter of + another sender, and fields refused with each code of §6.6 (not a Dictionary or List, a + duration or throughput as a String, a layer not in decimal or with a leading zero, a short + hash, an operation as a String, a server as a Token, a drop count not in decimal, version + 2); `expected/cmsd-read.json`, each one's description or refusal. - `logs/` — session logs (§7.1), one observation per line: a session with stalls, one open at its end; an exit before start; a start failure. `expected/qoe.json`, their metrics (§7.2). @@ -36,6 +42,11 @@ their metrics. Every value here is a string or an integer, the same on every mac from disk, requires each observation to write back as given, recomputes every expected file and requires it byte for byte, reads every report back, and requires every `invalid/` observation refused. `wai_telemetry gen .` writes the corpus. +- `cmsd_read_verify.py` (`python3 -I cmsd_read_verify.py .`): the second implementation of + §6.6, written from that text and RFC 9651 and sharing no code with `wai-rs`: it parses + every field of `cmsd-read/` with its own RFC 9651 reader and requires each outcome, reads + every field of `expected/cmsd.json` back to its description, requires §6.6's table to be + the codes the corpus gives, and requires every mutated copy of the corpus to fail. - `cml/differential.mjs` (`npm ci && node differential.mjs ..` in `cml/`): builds each report from the extension's tables in JavaScript, not from `wai-rs`'s output; encodes it with an independent open-source CMCD and CMSD library at the versions diff --git a/wai/telemetry-conformance/cmsd-read/cases.json b/wai/telemetry-conformance/cmsd-read/cases.json new file mode 100644 index 000000000..6bd86f1c3 --- /dev/null +++ b/wai/telemetry-conformance/cmsd-read/cases.json @@ -0,0 +1,87 @@ +[ + { + "name": "static-as-written", + "field": "static", + "value": "at=1790000000000,d=40,nor=\"L01.wai\",ot=v,science.transaction.wai-ch=\"aaf90792d6ff5318e5f306fbb000dfcbe73637e244aad1a2eff6dae537cd6b5f\",science.transaction.wai-l=\"0\",science.transaction.wai-lt=\"4\",science.transaction.wai-rcpt=\"7bf0f413db62aa185969b9ccf925d40f0281601268899bb7f2e97344487f4189\",sf=o,st=v,su" + }, + { + "name": "static-another-order", + "field": "static", + "value": "v=1,su,st=v,sf=o,science.transaction.wai-rcpt=\"7bf0f413db62aa185969b9ccf925d40f0281601268899bb7f2e97344487f4189\",science.transaction.wai-lt=\"4\",science.transaction.wai-l=\"0\",science.transaction.wai-ch=\"aaf90792d6ff5318e5f306fbb000dfcbe73637e244aad1a2eff6dae537cd6b5f\",ot=v,nor=\"L01.wai\",d=40,at=1790000000000,org.example-x=\"y\"" + }, + { + "name": "static-su-false", + "field": "static", + "value": "at=1790000000000,ot=o,su=?0" + }, + { + "name": "static-derived-op", + "field": "static", + "value": "at=1790000000005,ht=20,nrr=\"0-1023\",ot=o,science.transaction.wai-ch=\"4fc1bd94bd48b2c1ded99c0053eed742f5399cec09dbd2f8694cca6a30e44875\",science.transaction.wai-l=\"1\",science.transaction.wai-lt=\"4\",science.transaction.wai-op=crop,sf=o,st=l" + }, + { + "name": "dynamic-two-nodes", + "field": "dynamic", + "value": "\"edge-7\";etp=25000;mb=8000;rd=3;rtt=40;science.transaction.wai-drop=\"2\",\"origin-1\";du;rd=12" + }, + { + "name": "dynamic-unknown-parameter", + "field": "dynamic", + "value": "\"edge-7\";rtt=40;xyz=1" + }, + { + "name": "static-not-a-dictionary", + "field": "static", + "value": "at=,d=40" + }, + { + "name": "static-duration-a-string", + "field": "static", + "value": "d=\"40\"" + }, + { + "name": "static-layer-not-decimal", + "field": "static", + "value": "science.transaction.wai-l=\"one\"" + }, + { + "name": "static-layer-leading-zero", + "field": "static", + "value": "science.transaction.wai-l=\"01\"" + }, + { + "name": "static-hash-short", + "field": "static", + "value": "science.transaction.wai-ch=\"abc\"" + }, + { + "name": "static-op-a-string", + "field": "static", + "value": "science.transaction.wai-op=\"crop\"" + }, + { + "name": "static-version-2", + "field": "static", + "value": "v=2,ot=v" + }, + { + "name": "dynamic-server-a-token", + "field": "dynamic", + "value": "edge-7;rtt=40" + }, + { + "name": "dynamic-throughput-a-string", + "field": "dynamic", + "value": "\"edge-7\";etp=\"1\"" + }, + { + "name": "dynamic-drop-not-decimal", + "field": "dynamic", + "value": "\"edge-7\";science.transaction.wai-drop=\"two\"" + }, + { + "name": "dynamic-not-a-list", + "field": "dynamic", + "value": "\"edge-7\"," + } +] diff --git a/wai/telemetry-conformance/cmsd_read_verify.py b/wai/telemetry-conformance/cmsd_read_verify.py new file mode 100644 index 000000000..965eb6ca6 --- /dev/null +++ b/wai/telemetry-conformance/cmsd_read_verify.py @@ -0,0 +1,330 @@ +"""The second implementation of reading CMSD fields (extensions/delivery-telemetry.md §6.6), +written from that text and RFC 9651, sharing no code with wai-rs. + + python3 -I cmsd_read_verify.py + +It reads every field of cmsd-read/cases.json and requires the outcome +expected/cmsd-read.json states, reads every field expected/cmsd.json states back to its +description under cmsd/, requires §6.6's table to be the codes the corpus gives, and then +requires every mutated copy of the corpus to fail. Standard library only. +""" + +import copy +import json +import os +import re +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +PREFIX = "science.transaction.wai-" + + +class Bad(Exception): + def __init__(self, code): + Exception.__init__(self, code) + self.code = code + + +# ---- RFC 9651, the subset §3.3 writes --------------------------------------------- + +class Parser: + def __init__(self, s): + self.s, self.i = s, 0 + + def peek(self): + return self.s[self.i] if self.i < len(self.s) else None + + def key(self): + c = self.peek() + if c is None or not (("a" <= c <= "z") or c == "*"): + raise Bad("cmsd_malformed") + st = self.i + while self.peek() is not None and re.fullmatch(r"[a-z0-9_.*-]", self.peek()): + self.i += 1 + return self.s[st:self.i] + + def bare(self): + c = self.peek() + if c is not None and (c == "-" or c.isdigit()): + st = self.i + if c == "-": + self.i += 1 + d = self.i + while self.peek() is not None and self.peek().isdigit(): + self.i += 1 + if self.peek() == "." or self.i == d or self.i - d > 15: + raise Bad("cmsd_malformed") + return ("int", int(self.s[st:self.i])) + if c == '"': + self.i += 1 + out = "" + while True: + c = self.peek() + if c is None: + raise Bad("cmsd_malformed") + self.i += 1 + if c == '"': + return ("str", out) + if c == "\\": + e = self.peek() + if e not in ('"', "\\"): + raise Bad("cmsd_malformed") + out += e + self.i += 1 + elif 0x20 <= ord(c) <= 0x7E: + out += c + else: + raise Bad("cmsd_malformed") + if c == "?": + self.i += 1 + v = self.peek() + if v not in ("0", "1"): + raise Bad("cmsd_malformed") + self.i += 1 + return ("bool", v == "1") + if c is not None and (c.isalpha() and c.isascii() or c == "*"): + st = self.i + while self.peek() is not None and re.fullmatch(r"[A-Za-z0-9!#$%&'*+.^_`|~:/-]", self.peek()): + self.i += 1 + return ("token", self.s[st:self.i]) + raise Bad("cmsd_malformed") + + def params(self): + out = {} + while self.peek() == ";": + self.i += 1 + while self.peek() == " ": + self.i += 1 + k = self.key() + if self.peek() == "=": + self.i += 1 + v = self.bare() + else: + v = ("bool", True) + out[k] = v + return out + + def sep(self): + while self.peek() in (" ", "\t"): + self.i += 1 + if self.i == len(self.s): + return False + if self.peek() != ",": + raise Bad("cmsd_malformed") + self.i += 1 + while self.peek() in (" ", "\t"): + self.i += 1 + if self.i == len(self.s): + raise Bad("cmsd_malformed") + return True + + +def parse_dict(s): + p = Parser(s) + while p.peek() == " ": + p.i += 1 + out = {} + if p.i == len(s): + return out + while True: + k = p.key() + if p.peek() == "=": + p.i += 1 + if p.peek() == "(": + p.i += 1 + items = [] + while True: + while p.peek() == " ": + p.i += 1 + if p.peek() == ")": + p.i += 1 + break + t, v = p.bare() + if t != "int" or p.peek() not in (" ", ")"): + raise Bad("cmsd_malformed") + items.append(v) + v = ("list", items) + else: + v = p.bare() + else: + v = ("bool", True) + if p.peek() == ";": + raise Bad("cmsd_malformed") + out[k] = v + if not p.sep(): + return out + + +def parse_list(s): + p = Parser(s) + while p.peek() == " ": + p.i += 1 + out = [] + if p.i == len(s): + return out + while True: + v = p.bare() + out.append((v, p.params())) + if not p.sep(): + return out + + +# ---- §6.6 -------------------------------------------------------------------------- + +def want(v, kind): + if v[0] != kind: + raise Bad("cmsd_type") + return v[1] + + +def nonneg(v): + x = want(v, "int") + if x < 0: + raise Bad("cmsd_type") + return x + + +def nonempty(v): + x = want(v, "str") + if x == "": + raise Bad("cmsd_type") + return x + + +def hex64(v): + x = nonempty(v) + if not re.fullmatch(r"[0-9a-f]{64}", x): + raise Bad("cmsd_type") + return x + + +def decimal(v): + x = nonempty(v) + if not re.fullmatch(r"0|[1-9][0-9]{0,14}", x): + raise Bad("cmsd_type") + return int(x) + + +def read_static(field): + d = parse_dict(field) + out = {"at": None, "d": None, "ht": None, "nor": None, "nrr": None, "ot": None, "sf": None, "st": None, + "su": False, "content_hash": None, "layer": None, "layers_total": None, "receipt": None, "op": None} + named = {"v", "at", "d", "ht", "nor", "nrr", "ot", "sf", "st", "su"} + for k, v in d.items(): + if v[0] == "list": + if k in named or k.startswith(PREFIX): + raise Bad("cmsd_type") + continue + if k == "v": + if want(v, "int") != 1: + raise Bad("cmsd_version") + elif k in ("at", "d", "ht"): + out[k] = nonneg(v) + elif k in ("nor", "nrr"): + out[k] = nonempty(v) + elif k in ("ot", "sf", "st"): + out[k] = want(v, "token") + elif k == "su": + out["su"] = want(v, "bool") + elif k == PREFIX + "ch": + out["content_hash"] = hex64(v) + elif k == PREFIX + "l": + out["layer"] = decimal(v) + elif k == PREFIX + "lt": + out["layers_total"] = decimal(v) + elif k == PREFIX + "rcpt": + out["receipt"] = hex64(v) + elif k == PREFIX + "op": + out["op"] = want(v, "token") + return out + + +def read_dynamic(field): + out = [] + for value, params in parse_list(field): + e = {"server": nonempty(value), "du": False, "etp": None, "mb": None, "rd": None, "rtt": None, "drop": None} + for k, v in params.items(): + if k == "du": + e["du"] = want(v, "bool") + elif k in ("etp", "mb", "rd", "rtt"): + e[k] = nonneg(v) + elif k == PREFIX + "drop": + e["drop"] = decimal(v) + out.append(e) + return out + + +def read(field, value): + try: + return {"read": read_static(value) if field == "static" else read_dynamic(value)} + except Bad as e: + return {"refuse": e.code} + + +def table_codes(): + text = open(os.path.join(HERE, "..", "extensions", "delivery-telemetry.md"), encoding="utf-8").read() + at = text.index("§6.6") + end = text.index("\n## ", at) + return re.findall(r"^\| `(cmsd_[a-z]+)` \|", text[at:end], re.M) + + +def check(cases, expected, written, descs): + fails = [] + given = set() + for c in cases: + got = read(c["field"], c["value"]) + if got != expected.get(c["name"]): + fails.append("%s: expected %s, read %s" % (c["name"], json.dumps(expected.get(c["name"]))[:120], json.dumps(got)[:120])) + if "refuse" in got: + given.add(got["refuse"]) + if sorted(expected) != sorted(c["name"] for c in cases): + fails.append("expected/cmsd-read.json and cmsd-read/cases.json name different cases") + for n, desc in descs.items(): + got = read("static" if n.startswith("static") else "dynamic", written[n]) + if got != {"read": desc}: + fails.append("cmsd/%s: its field does not read back to it" % n) + codes = table_codes() + if sorted(codes) != sorted(given): + fails.append("§6.6's codes %s, the corpus gives %s" % (codes, sorted(given))) + return fails + + +def main(argv): + if len(argv) != 2: + print(__doc__) + return 2 + d = argv[1] + load = lambda p: json.load(open(os.path.join(d, p), encoding="utf-8")) + cases, expected, written = load("cmsd-read/cases.json"), load("expected/cmsd-read.json"), load("expected/cmsd.json") + descs = {n[:-5]: load("cmsd/" + n) for n in sorted(os.listdir(os.path.join(d, "cmsd")))} + fails = check(cases, expected, written, descs) + for f in fails: + print("FAIL", f) + print("%d fields read, %d written fields read back: %d failures" % (len(cases), len(descs), len(fails))) + muts = [] + + def mutate(label, f): + c, e, w, ds = copy.deepcopy(cases), copy.deepcopy(expected), copy.deepcopy(written), copy.deepcopy(descs) + f(c, e, w, ds) + muts.append((label, c, e, w, ds)) + + mutate("a refusal stated with another code", lambda c, e, w, ds: e.__setitem__("static-version-2", {"refuse": "cmsd_type"})) + mutate("a field's duration changed", lambda c, e, w, ds: c[0].__setitem__("value", c[0]["value"].replace("d=40", "d=41"))) + mutate("a read field's layer stated otherwise", lambda c, e, w, ds: e["static-as-written"]["read"].__setitem__("layer", 1)) + mutate("a dynamic entry's server stated otherwise", lambda c, e, w, ds: e["dynamic-two-nodes"]["read"][0].__setitem__("server", "edge-8")) + mutate("a written field no longer its description's", lambda c, e, w, ds: w.__setitem__("static-base", w["static-base"].replace("ot=v", "ot=a"))) + def drop_malformed(c, e, w, ds): + for n in [x["name"] for x in c if e[x["name"]].get("refuse") == "cmsd_malformed"]: + c[:] = [x for x in c if x["name"] != n] + e.pop(n) + mutate("every case refused with cmsd_malformed dropped", drop_malformed) + ok = True + for label, c, e, w, ds in muts: + caught = check(c, e, w, ds) + print(("PASS" if caught else "FAIL") + " mutated copy fails: " + label) + ok &= bool(caught) + return 0 if not fails and ok else 1 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/wai/telemetry-conformance/expected/cmsd-read.json b/wai/telemetry-conformance/expected/cmsd-read.json new file mode 100644 index 000000000..2e08345f8 --- /dev/null +++ b/wai/telemetry-conformance/expected/cmsd-read.json @@ -0,0 +1,142 @@ +{ + "static-as-written": { + "read": { + "at": 1790000000000, + "d": 40, + "ht": null, + "nor": "L01.wai", + "nrr": null, + "ot": "v", + "sf": "o", + "st": "v", + "su": true, + "content_hash": "aaf90792d6ff5318e5f306fbb000dfcbe73637e244aad1a2eff6dae537cd6b5f", + "layer": 0, + "layers_total": 4, + "receipt": "7bf0f413db62aa185969b9ccf925d40f0281601268899bb7f2e97344487f4189", + "op": null + } + }, + "static-another-order": { + "read": { + "at": 1790000000000, + "d": 40, + "ht": null, + "nor": "L01.wai", + "nrr": null, + "ot": "v", + "sf": "o", + "st": "v", + "su": true, + "content_hash": "aaf90792d6ff5318e5f306fbb000dfcbe73637e244aad1a2eff6dae537cd6b5f", + "layer": 0, + "layers_total": 4, + "receipt": "7bf0f413db62aa185969b9ccf925d40f0281601268899bb7f2e97344487f4189", + "op": null + } + }, + "static-su-false": { + "read": { + "at": 1790000000000, + "d": null, + "ht": null, + "nor": null, + "nrr": null, + "ot": "o", + "sf": null, + "st": null, + "su": false, + "content_hash": null, + "layer": null, + "layers_total": null, + "receipt": null, + "op": null + } + }, + "static-derived-op": { + "read": { + "at": 1790000000005, + "d": null, + "ht": 20, + "nor": null, + "nrr": "0-1023", + "ot": "o", + "sf": "o", + "st": "l", + "su": false, + "content_hash": "4fc1bd94bd48b2c1ded99c0053eed742f5399cec09dbd2f8694cca6a30e44875", + "layer": 1, + "layers_total": 4, + "receipt": null, + "op": "crop" + } + }, + "dynamic-two-nodes": { + "read": [ + { + "server": "edge-7", + "du": false, + "etp": 25000, + "mb": 8000, + "rd": 3, + "rtt": 40, + "drop": 2 + }, + { + "server": "origin-1", + "du": true, + "etp": null, + "mb": null, + "rd": 12, + "rtt": null, + "drop": null + } + ] + }, + "dynamic-unknown-parameter": { + "read": [ + { + "server": "edge-7", + "du": false, + "etp": null, + "mb": null, + "rd": null, + "rtt": 40, + "drop": null + } + ] + }, + "static-not-a-dictionary": { + "refuse": "cmsd_malformed" + }, + "static-duration-a-string": { + "refuse": "cmsd_type" + }, + "static-layer-not-decimal": { + "refuse": "cmsd_type" + }, + "static-layer-leading-zero": { + "refuse": "cmsd_type" + }, + "static-hash-short": { + "refuse": "cmsd_type" + }, + "static-op-a-string": { + "refuse": "cmsd_type" + }, + "static-version-2": { + "refuse": "cmsd_version" + }, + "dynamic-server-a-token": { + "refuse": "cmsd_type" + }, + "dynamic-throughput-a-string": { + "refuse": "cmsd_type" + }, + "dynamic-drop-not-decimal": { + "refuse": "cmsd_type" + }, + "dynamic-not-a-list": { + "refuse": "cmsd_malformed" + } +} diff --git a/wai/wai-rs/src/bin/wai_telemetry.rs b/wai/wai-rs/src/bin/wai_telemetry.rs index 19f417637..19996aa08 100644 --- a/wai/wai-rs/src/bin/wai_telemetry.rs +++ b/wai/wai-rs/src/bin/wai_telemetry.rs @@ -18,7 +18,7 @@ use std::process::ExitCode; use serde_json::{json, Value}; use wai::telemetry::cmcd::{cmcd, cmcd_headers, from_cmcd, Report}; -use wai::telemetry::cmsd::{encode_dynamic, CmsdDynamic, CmsdStatic}; +use wai::telemetry::cmsd::{encode_dynamic, read_dynamic, read_static, CmsdDynamic, CmsdStatic}; use wai::telemetry::observation::Observation; use wai::telemetry::qoe::session_metrics; use wai::telemetry::sf::{SfBare, SfMember}; @@ -144,6 +144,43 @@ fn cmsd_cases() -> Vec<(&'static str, Value)> { ] } +/// CMSD fields to read (§6.6): what §6.1 to §6.3 write, the same with its keys in another +/// order, `v=1` and a key the extension does not name, and fields each refusal refuses. +fn cmsd_read_cases() -> Vec<(&'static str, &'static str, String)> { + let base = format!("at={T0},d=40,nor=\"L01.wai\",ot=v,science.transaction.wai-ch=\"{BASE}\",science.transaction.wai-l=\"0\",science.transaction.wai-lt=\"4\",science.transaction.wai-rcpt=\"{RCPT}\",sf=o,st=v,su"); + vec![ + ("static-as-written", "static", base.clone()), + ("static-another-order", "static", format!("v=1,su,st=v,sf=o,science.transaction.wai-rcpt=\"{RCPT}\",science.transaction.wai-lt=\"4\",science.transaction.wai-l=\"0\",science.transaction.wai-ch=\"{BASE}\",ot=v,nor=\"L01.wai\",d=40,at={T0},org.example-x=\"y\"")), + ("static-su-false", "static", format!("at={T0},ot=o,su=?0")), + ("static-derived-op", "static", format!("at={},ht=20,nrr=\"0-1023\",ot=o,science.transaction.wai-ch=\"{CH}\",science.transaction.wai-l=\"1\",science.transaction.wai-lt=\"4\",science.transaction.wai-op=crop,sf=o,st=l", T0 + 5)), + ("dynamic-two-nodes", "dynamic", "\"edge-7\";etp=25000;mb=8000;rd=3;rtt=40;science.transaction.wai-drop=\"2\",\"origin-1\";du;rd=12".into()), + ("dynamic-unknown-parameter", "dynamic", "\"edge-7\";rtt=40;xyz=1".into()), + ("static-not-a-dictionary", "static", "at=,d=40".into()), + ("static-duration-a-string", "static", "d=\"40\"".into()), + ("static-layer-not-decimal", "static", "science.transaction.wai-l=\"one\"".into()), + ("static-layer-leading-zero", "static", "science.transaction.wai-l=\"01\"".into()), + ("static-hash-short", "static", "science.transaction.wai-ch=\"abc\"".into()), + ("static-op-a-string", "static", "science.transaction.wai-op=\"crop\"".into()), + ("static-version-2", "static", "v=2,ot=v".into()), + ("dynamic-server-a-token", "dynamic", "edge-7;rtt=40".into()), + ("dynamic-throughput-a-string", "dynamic", "\"edge-7\";etp=\"1\"".into()), + ("dynamic-drop-not-decimal", "dynamic", "\"edge-7\";science.transaction.wai-drop=\"two\"".into()), + ("dynamic-not-a-list", "dynamic", "\"edge-7\",".into()), + ] +} + +fn cmsd_read(field: &str, value: &str) -> Value { + let r = if field == "static" { + read_static(value).map(|s| s.to_json()) + } else { + read_dynamic(value).map(|d| Value::Array(d.iter().map(CmsdDynamic::to_json).collect())) + }; + match r { + Ok(v) => json!({ "read": v }), + Err(e) => json!({ "refuse": e.code() }), + } +} + /// Session logs: one session each. fn logs() -> Vec<(&'static str, Vec)> { let o = |sid, sn, t, e| obs(sid, sn, Some(T0 + t), e); @@ -215,6 +252,19 @@ fn corpus() -> Result>, String> { cs.insert(n.to_string(), json!(s)); } files.insert("expected/cmsd.json".into(), pretty(&Value::Object(cs))); + let mut cases = Vec::new(); + let mut read = serde_json::Map::new(); + for (n, field, value) in cmsd_read_cases() { + cases.push(json!({ "name": n, "field": field, "value": value })); + read.insert(n.to_string(), cmsd_read(field, &value)); + } + for code in wai::telemetry::cmsd::CmsdRefusal::all().iter().map(|r| r.code()) { + if !read.values().any(|v| v["refuse"] == code) { + return Err(format!("no CMSD field is refused with {code}")); + } + } + files.insert("cmsd-read/cases.json".into(), pretty(&json!(cases))); + files.insert("expected/cmsd-read.json".into(), pretty(&Value::Object(read))); let mut q = serde_json::Map::new(); for (n, log) in logs() { let mut text = String::new(); @@ -251,7 +301,7 @@ fn verify(dir: &Path) -> Result<(), String> { fails.push(format!("{rel}: not what the generator writes")); } } - for sub in ["observations", "invalid", "cmsd", "logs"] { + for sub in ["observations", "invalid", "cmsd", "cmsd-read", "logs"] { for e in std::fs::read_dir(dir.join(sub)).map_err(|e| format!("{sub}: {e}"))?.flatten() { let rel = format!("{sub}/{}", e.file_name().to_string_lossy()); if !want.contains_key(&rel) { @@ -284,6 +334,24 @@ fn verify(dir: &Path) -> Result<(), String> { fails.push(format!("invalid/{n}: accepted")); } } + // Every CMSD field of cmsd-read/, read from disk, gets the outcome expected/cmsd-read.json + // states; and every field expected/cmsd.json states reads back to its description. + let cases: Value = serde_json::from_slice(&std::fs::read(dir.join("cmsd-read/cases.json")).map_err(|e| e.to_string())?).map_err(|e| e.to_string())?; + let read: Value = serde_json::from_slice(&std::fs::read(dir.join("expected/cmsd-read.json")).map_err(|e| e.to_string())?).map_err(|e| e.to_string())?; + for c in cases.as_array().into_iter().flatten() { + let (n, f, v) = (c["name"].as_str().unwrap_or("?"), c["field"].as_str().unwrap_or(""), c["value"].as_str().unwrap_or("")); + if cmsd_read(f, v) != read[n] { + fails.push(format!("cmsd-read/{n}: not the outcome expected/cmsd-read.json states")); + } + } + let written: Value = serde_json::from_slice(&std::fs::read(dir.join("expected/cmsd.json")).map_err(|e| e.to_string())?).map_err(|e| e.to_string())?; + for (n, _) in cmsd_cases() { + let desc: Value = serde_json::from_slice(&std::fs::read(dir.join(format!("cmsd/{n}.json"))).map_err(|e| e.to_string())?).map_err(|e| e.to_string())?; + let field = if n.starts_with("static") { "static" } else { "dynamic" }; + if cmsd_read(field, written[n].as_str().unwrap_or("")) != json!({ "read": desc }) { + fails.push(format!("cmsd/{n}: its field does not read back to it")); + } + } if fails.is_empty() { println!("telemetry: {} observations, {} refusals, {} CMSD descriptions and {} session logs verify", observations().len(), invalid().len(), cmsd_cases().len(), logs().len()); Ok(()) diff --git a/wai/wai-rs/src/codecs/registry.rs b/wai/wai-rs/src/codecs/registry.rs index adcdcc22a..6c782fd24 100644 --- a/wai/wai-rs/src/codecs/registry.rs +++ b/wai/wai-rs/src/codecs/registry.rs @@ -484,16 +484,18 @@ pub enum WireKind { HttpHeader, /// A member of a WebCodecs `VideoFrameMetadata` dictionary. FrameMetadata, + /// A custom key of a CMCD (CTA-5004-B) or CMSD (CTA-5006) report. + TelemetryKey, } impl WireKind { /// Every kind, in declaration order. - pub const fn all() -> [Self; 15] { + pub const fn all() -> [Self; 16] { [ Self::MediaType, Self::CodecsPrefix, Self::FourCc, Self::Magic, Self::DataId, Self::Scheme, Self::Path, Self::WellKnown, Self::PlaylistTag, Self::PlaylistAttribute, Self::DocumentKind, Self::Catalog, Self::SetupOption, - Self::HttpHeader, Self::FrameMetadata, + Self::HttpHeader, Self::FrameMetadata, Self::TelemetryKey, ] } /// The label the export uses. @@ -514,6 +516,7 @@ impl WireKind { Self::SetupOption => "setup-option", Self::HttpHeader => "http-header", Self::FrameMetadata => "frame-metadata", + Self::TelemetryKey => "telemetry-key", } } } @@ -590,6 +593,8 @@ const ACCESS_SERVICES: &str = "extensions/access-services.md"; const AGE_ASSURANCE: &str = "extensions/age-assurance.md"; const EXPLORATION: &str = "extensions/exploration.md"; const EXACT_UPSCALE: &str = "extensions/exact-upscale.md"; +const DELIVERY_TELEMETRY: &str = "extensions/delivery-telemetry.md"; +const PROJECTION: &str = "extensions/projection.md"; const fn wire(name: &'static str, kind: WireKind, document: &'static str, section: &'static str, status: WireStatus) -> WireIdentifier { @@ -760,6 +765,31 @@ pub const WIRE_IDENTIFIERS: &[WireIdentifier] = &[ wire("wai-taste", WireKind::DocumentKind, EXPLORATION, "§8.1", WireStatus::Defined), wire("wai-ranking", WireKind::DocumentKind, EXPLORATION, "§9.1", WireStatus::Defined), wire("wai-pick", WireKind::DocumentKind, EXPLORATION, "§10.2", WireStatus::Defined), + // The custom keys of delivery telemetry's CMCD and CMSD reports. + wire("science.transaction.wai-b", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-tx", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-ch", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-cap", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-ri", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-l", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-lt", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-p", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-rcpt", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-lc", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-lcf", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-coh", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-u", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-pl", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-late", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-dec", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-sw", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-co", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-cr", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-cf", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-clm", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-op", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("science.transaction.wai-drop", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), + wire("WPJ1", WireKind::Magic, PROJECTION, "§3", WireStatus::Defined), ]; /// A string with the shape of a capability that the reference implementation uses diff --git a/wai/wai-rs/src/telemetry/cmsd.rs b/wai/wai-rs/src/telemetry/cmsd.rs index af50aecd7..a6affbdd1 100644 --- a/wai/wai-rs/src/telemetry/cmsd.rs +++ b/wai/wai-rs/src/telemetry/cmsd.rs @@ -195,3 +195,179 @@ pub fn encode_dynamic(entries: &[CmsdDynamic]) -> Result .collect(); serialize_list(&items).map_err(|e| bad(e.to_string())) } + +// ---- Reading (§6.6) ------------------------------------------------------------------- + +/// Why a CMSD field is not read (§6.6). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CmsdRefusal { + /// The field is not an RFC 9651 Dictionary (CMSD-Static) or List (CMSD-Dynamic) of the + /// subset §3.3 writes. + Malformed, + /// A key §6.1 to §6.3 names holds a value of another type or form. + Type, + /// The field states a CMSD version other than 1. + Version, +} + +impl CmsdRefusal { + /// Every refusal, in §6.6's order. (A function, not an associated constant, so the C + /// header generator does not export it.) + pub const fn all() -> [CmsdRefusal; 3] { + [CmsdRefusal::Malformed, CmsdRefusal::Type, CmsdRefusal::Version] + } + pub fn code(self) -> &'static str { + match self { + CmsdRefusal::Malformed => "cmsd_malformed", + CmsdRefusal::Type => "cmsd_type", + CmsdRefusal::Version => "cmsd_version", + } + } +} + +fn r_int(b: &SfBare) -> Result { + match b { + SfBare::Int(i) if *i >= 0 => Ok(*i as u64), + _ => Err(CmsdRefusal::Type), + } +} + +fn r_str(b: &SfBare) -> Result { + match b { + SfBare::Str(s) if !s.is_empty() => Ok(s.clone()), + _ => Err(CmsdRefusal::Type), + } +} + +fn r_token(b: &SfBare) -> Result { + match b { + SfBare::Token(t) => Ok(t.clone()), + _ => Err(CmsdRefusal::Type), + } +} + +fn r_hex(b: &SfBare) -> Result { + let s = r_str(b)?; + if s.len() == 64 && s.bytes().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) { + Ok(s) + } else { + Err(CmsdRefusal::Type) + } +} + +/// A custom key's decimal String: digits, no sign, no leading zero but for 0 itself, at +/// most `10¹â�µ − 1`. +fn r_decimal(b: &SfBare) -> Result { + let s = r_str(b)?; + let ok = s.bytes().all(|c| c.is_ascii_digit()) && (s == "0" || !s.starts_with('0')) && s.len() <= 15; + if ok { s.parse().map_err(|_| CmsdRefusal::Type) } else { Err(CmsdRefusal::Type) } +} + +/// Reads a `CMSD-Static` field value into the description §6.1 and §6.2 write. Keys the +/// extension does not name are passed over (CTA-5006 lets a sender add keys); a key it +/// names with a value of another type is `cmsd_type`; `v` other than 1 is `cmsd_version`. +pub fn read_static(field: &str) -> Result { + use super::sf::parse_dict; + let members = parse_dict(field).map_err(|_| CmsdRefusal::Malformed)?; + let mut out = CmsdStatic::default(); + for (k, m) in &members { + let SfMember::Item(b) = m else { + if matches!(k.as_str(), "v" | "at" | "d" | "ht" | "nor" | "nrr" | "ot" | "sf" | "st" | "su") || k.starts_with(PREFIX) { + return Err(CmsdRefusal::Type); + } + continue; + }; + let custom = k.strip_prefix(PREFIX); + match (k.as_str(), custom) { + ("v", _) => { + if r_int(b)? != 1 { + return Err(CmsdRefusal::Version); + } + } + ("at", _) => out.at = Some(r_int(b)?), + ("d", _) => out.d = Some(r_int(b)?), + ("ht", _) => out.ht = Some(r_int(b)?), + ("nor", _) => out.nor = Some(r_str(b)?), + ("nrr", _) => out.nrr = Some(r_str(b)?), + ("ot", _) => out.ot = Some(r_token(b)?), + ("sf", _) => out.sf = Some(r_token(b)?), + ("st", _) => out.st = Some(r_token(b)?), + ("su", _) => match b { + SfBare::Bool(v) => out.su = *v, + _ => return Err(CmsdRefusal::Type), + }, + (_, Some("ch")) => out.content_hash = Some(r_hex(b)?), + (_, Some("l")) => out.layer = Some(r_decimal(b)?), + (_, Some("lt")) => out.layers_total = Some(r_decimal(b)?), + (_, Some("rcpt")) => out.receipt = Some(r_hex(b)?), + (_, Some("op")) => out.op = Some(r_token(b)?), + _ => {} + } + } + Ok(out) +} + +/// Reads a `CMSD-Dynamic` field value: one entry per node, in order (§6.3). +pub fn read_dynamic(field: &str) -> Result, CmsdRefusal> { + use super::sf::parse_list; + let items = parse_list(field).map_err(|_| CmsdRefusal::Malformed)?; + let mut out = Vec::with_capacity(items.len()); + for it in &items { + let mut e = CmsdDynamic { server: r_str(&it.value)?, du: false, etp: None, mb: None, rd: None, rtt: None, drop: None }; + for (k, b) in &it.params { + match k.as_str() { + "du" => match b { + SfBare::Bool(v) => e.du = *v, + _ => return Err(CmsdRefusal::Type), + }, + "etp" => e.etp = Some(r_int(b)?), + "mb" => e.mb = Some(r_int(b)?), + "rd" => e.rd = Some(r_int(b)?), + "rtt" => e.rtt = Some(r_int(b)?), + k if k.strip_prefix(PREFIX) == Some("drop") => e.drop = Some(r_decimal(b)?), + _ => {} + } + } + out.push(e); + } + Ok(out) +} + +impl CmsdStatic { + /// The description, in the form [`CmsdStatic::from_json`] reads. + pub fn to_json(&self) -> Value { + serde_json::json!({ + "at": self.at, "d": self.d, "ht": self.ht, "nor": self.nor, "nrr": self.nrr, "ot": self.ot, + "sf": self.sf, "st": self.st, "su": self.su, "content_hash": self.content_hash, "layer": self.layer, + "layers_total": self.layers_total, "receipt": self.receipt, "op": self.op, + }) + } +} + +impl CmsdDynamic { + /// The entry, in the form [`CmsdDynamic::from_json`] reads. + pub fn to_json(&self) -> Value { + serde_json::json!({ + "server": self.server, "du": self.du, "etp": self.etp, "mb": self.mb, "rd": self.rd, + "rtt": self.rtt, "drop": self.drop, + }) + } +} + +#[cfg(test)] +mod read_tests { + use super::*; + + #[test] + fn what_is_written_reads_back() { + let s = CmsdStatic { at: Some(5), d: Some(40), ot: Some("v".into()), su: true, layer: Some(0), layers_total: Some(4), content_hash: Some("ab".repeat(32)), op: Some("crop".into()), ..Default::default() }; + assert_eq!(read_static(&s.encode().unwrap()).unwrap(), s); + let d = vec![CmsdDynamic { server: "edge-7".into(), du: true, etp: Some(1), mb: None, rd: Some(3), rtt: None, drop: Some(0) }]; + assert_eq!(read_dynamic(&encode_dynamic(&d).unwrap()).unwrap(), d); + assert_eq!(read_static("v=2"), Err(CmsdRefusal::Version)); + assert_eq!(read_static("d=\"4\""), Err(CmsdRefusal::Type)); + assert_eq!(read_static("at=,d=4"), Err(CmsdRefusal::Malformed)); + assert_eq!(read_static("science.transaction.wai-l=\"01\""), Err(CmsdRefusal::Type)); + assert_eq!(read_dynamic("edge;rtt=4"), Err(CmsdRefusal::Type)); + } +} -- 2.54.0 (Apple Git-157) From 6ccdecd8dbd0885a909894fdb890b1080a7cb9fb Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 10:38:24 -0400 Subject: [PATCH 017/242] =?UTF-8?q?scene=20corpus:=20a=20world=20at=20the?= =?UTF-8?q?=20edges=20of=20=C2=A73's=20numbers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Positions where JSON and USD switch to exponent form, binary32 and binary64 ties in both directions, the least step and the largest value, so a verifier that spells or rounds one of them otherwise fails. Co-Authored-By: Claude Opus 5.5 --- wai/live-conformance/scene/inputs/edges.wwld | Bin 0 -> 601 bytes wai/live-conformance/scene/out/edges.gltf | 1 + wai/live-conformance/scene/out/edges.usda | 35 ++++++++++ wai/live-conformance/scene/vectors.json | 64 +++++++++++++++++++ wai/wai-rs/src/scene_map/corpus.rs | 11 ++++ 5 files changed, 111 insertions(+) create mode 100644 wai/live-conformance/scene/inputs/edges.wwld create mode 100644 wai/live-conformance/scene/out/edges.gltf create mode 100644 wai/live-conformance/scene/out/edges.usda diff --git a/wai/live-conformance/scene/inputs/edges.wwld b/wai/live-conformance/scene/inputs/edges.wwld new file mode 100644 index 0000000000000000000000000000000000000000..1356db90c8fe83acaf3afb6e03dcd2166f25a08e GIT binary patch literal 601 zcmWF!_i zMGq>ims4J@R0~v{mkv^ym;t$!=rRFAL zgA7RjM+NmzZ8)i;p&QxRO;0;^tj_bTbzxBknZ+OgRo(!lp?b~^}uhMEP|3N#B$1HJkW K Vec<(&'static str, Vec)> { v }; let mut t = vec![(p1, particle([0, fx(2.0), 0])), (p2, particle([fx(2.0), fx(2.0), 0])), (node_id(8), member)]; + // Positions at the edges of §3: JSON's switch to exponent form (2^-20 below it, 2^-17 + // above), USD's (2^-23), binary32 ties in both directions (2^24 + 1, 2^24 + 3), binary64 + // ties (2^53 + 1, 2^53 + 3), the least step and the largest value. + let mut e = vec![ + (node_id(9), kinematic_state([1 << 12, 1 << 15, 1 << 9])), + (node_id(10), kinematic_state([(1 << 24) + 1, (1 << 24) + 3, -((1 << 24) + 1)])), + (node_id(11), kinematic_state([(1 << 53) + 1, (1 << 53) + 3, fx(1.0)])), + (node_id(12), kinematic_state([1, -1, i64::MAX])), + ]; vec![ + ("edges", world_bytes(&contract("wai.world.sim.kinematic0"), &mut e, &[])), ("kinematic", world_bytes(&contract("wai.world.sim.kinematic0"), &mut k, &assets)), ("ballistic", world_bytes(&contract("wai.world.sim.ballistic0"), &mut b, &[])), ("truss", world_bytes(&contract("wai.world.sim.truss0"), &mut t, &[])), @@ -250,6 +260,7 @@ pub fn generate(inputs: &BTreeMap>) -> BTreeMap> let bundles: Vec> = vec![ ("kinematic", Some("inputs/kinematic.wwld".into()), None, vec![screen.clone(), media("ambience", amb, "wai.audio.flac", None)]), ("ballistic", Some("inputs/ballistic.wwld".into()), None, vec![]), + ("edges", Some("inputs/edges.wwld".into()), None, vec![]), ("truss", Some("inputs/truss.wwld".into()), None, vec![]), ("empty_scene", Some(path_of("scene.wwld")), None, vec![]), ("replay", Some(path_of("basic_kinematics.wwld")), None, vec![]), -- 2.54.0 (Apple Git-157) From 6064a35c825bb06a1c7edb8e697d36fa417a09d1 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 10:39:41 -0400 Subject: [PATCH 018/242] scene corpus: a transform operation the layer's order does not list is refused too Co-Authored-By: Claude Opus 5.5 --- .../scene/docs/foreign_rotate_unlisted.usda | 41 +++++++++++++++++++ wai/live-conformance/scene/vectors.json | 17 ++++++++ wai/wai-rs/src/scene_map/corpus.rs | 1 + 3 files changed, 59 insertions(+) create mode 100644 wai/live-conformance/scene/docs/foreign_rotate_unlisted.usda diff --git a/wai/live-conformance/scene/docs/foreign_rotate_unlisted.usda b/wai/live-conformance/scene/docs/foreign_rotate_unlisted.usda new file mode 100644 index 000000000..2b0b4d70b --- /dev/null +++ b/wai/live-conformance/scene/docs/foreign_rotate_unlisted.usda @@ -0,0 +1,41 @@ +#usda 1.0 +( + "A layer written outside WAI" + defaultPrim = "Set" + metersPerUnit = 1 + upAxis = "Y" +) + +def Xform "Set" ( + kind = "group" +) +{ + double3 xformOp:translate = (10, 0, 0) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Pillar" # a pillar + { + double3 xformOp:translate = (0.5, 3, -0.25) + uniform token[] xformOpOrder = ["xformOp:translate"] + + def Xform "Cap" + { + double3 xformOp:translate = (0, 1.5, 0) + float3 xformOp:rotateXYZ = (0, 90, 0) + } + } + + def Mesh "Floor" + { + int[] faceVertexCounts = [3] + int[] faceVertexIndices = [0, 1, 2] + point3f[] points = [(0, 0, 0), (1, 0, 0), (0, 0, 1)] + } + + /* an asset by its content hash */ + def "Statue" ( + prepend references = @urn:wai:asset:f3fdba94a88739184ad0f417656a62214dd1aa7138d8b21e3efabe4b38034461@ + ) + { + } +} diff --git a/wai/live-conformance/scene/vectors.json b/wai/live-conformance/scene/vectors.json index ce8a163d9..83606d3df 100644 --- a/wai/live-conformance/scene/vectors.json +++ b/wai/live-conformance/scene/vectors.json @@ -107,6 +107,10 @@ "file": "docs/foreign_rotate.usda", "sha256": "0d50a87198eebd914a4aa552b0ba1e408484fe057655436c07f11d6a14bcae8c" }, + { + "file": "docs/foreign_rotate_unlisted.usda", + "sha256": "47b733db4a7c08b8a8c781616ba9b5c7937c343ac6855bafff230473f8dc3e88" + }, { "file": "docs/foreign_rotation.gltf", "sha256": "878d1c2525d67dba471e657d7a1e266dc386cfdc50d4e4e97c25b69eec108f9c" @@ -1299,6 +1303,19 @@ "outcome": "refuse", "code": "transform_not_carried" }, + { + "name": "foreign_rotate_unlisted_usda", + "op": "import", + "format": "usda", + "file": "docs/foreign_rotate_unlisted.usda", + "params": { + "actor": "b7ac7c42e968097e3684375130618756034d7d89e50c7876f47c21163b5f32bb", + "world_contract": "{\"checkpoint_every\":600,\"numeric\":\"wai.det.fixed64\",\"resolve\":{\"default\":\"wai.world.resolve.lww\"},\"rng\":{\"alg\":\"xoshiro256++\",\"seed\":\"0000000000000000000000000000000000000000000000000000000000000000\"},\"sim\":\"wai.world.sim.kinematic0\",\"tick_rate\":60}", + "audio_contract": null + }, + "outcome": "refuse", + "code": "transform_not_carried" + }, { "name": "foreign_inexact_usda", "op": "import", diff --git a/wai/wai-rs/src/scene_map/corpus.rs b/wai/wai-rs/src/scene_map/corpus.rs index 5f4707867..a76354154 100644 --- a/wai/wai-rs/src/scene_map/corpus.rs +++ b/wai/wai-rs/src/scene_map/corpus.rs @@ -226,6 +226,7 @@ def Xform "Set" ( vec![ ("layer", layer.clone()), ("rotate", layer.replacen(" uniform token[] xformOpOrder = [\"xformOp:translate\"]\n\n def Xform \"Cap\"", " float3 xformOp:rotateXYZ = (0, 90, 0)\n uniform token[] xformOpOrder = [\"xformOp:translate\", \"xformOp:rotateXYZ\"]\n\n def Xform \"Cap\"", 1)), + ("rotate_unlisted", layer.replacen(" double3 xformOp:translate = (0, 1.5, 0)\n", " double3 xformOp:translate = (0, 1.5, 0)\n float3 xformOp:rotateXYZ = (0, 90, 0)\n", 1)), ("inexact", layer.replacen("(0, 1.5, 0)", "(0, 0.1, 0)", 1)), ("asset_path", layer.replacen(&format!("@urn:wai:asset:{}@", hex(&mesh)), "@./statue.usda@", 1)), ("variant", layer.replacen(" def Mesh \"Floor\"", " variantSet \"look\" = {\n \"plain\" {\n }\n }\n\n def Mesh \"Floor\"", 1)), -- 2.54.0 (Apple Git-157) From cbf58297e23dc83752e70b00598deeae96fc8774 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 10:43:34 -0400 Subject: [PATCH 019/242] LOC carriage: WAI envelopes on LOC objects, a writer and reader in joule-moq, the corpus live-conformance/loc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit extensions/loc-carriage.md maps an envelope whose payload is one WebCodecs chunk (an Opus packet, an AVC or HEVC access unit in Annex B, an ADTS frame) onto one object of a track of packaging loc (draft-ietf-moq-loc-04): the payload as the LOC Payload, the manifest as the Immutable Property WAI_MANIFEST (0x3A61), the receipt commitment as WAI_RECEIPT. A reader rebuilds the envelope byte for byte and checks the receipt against it, refusing with the extension's codes. joule-moq's loc module writes and reads; its example writes the corpus and tests/loc_vectors.rs checks it; loc_verify.py is the second implementation. WAI_MANIFEST is registered as a wire identifier; moq-streaming-format §6 points to the carriage. Co-Authored-By: Claude Opus 5.5 --- joule-code/CHANGELOG.md | 12 + .../joule-code-rs/crates/joule-moq/README.md | 11 + .../crates/joule-moq/examples/loc_vectors.rs | 42 ++ .../joule-code-rs/crates/joule-moq/src/lib.rs | 5 + .../joule-code-rs/crates/joule-moq/src/loc.rs | 624 ++++++++++++++++++ .../crates/joule-moq/tests/loc_vectors.rs | 37 ++ wai/REGISTRY.md | 5 +- wai/extensions/loc-carriage.md | 172 +++++ wai/extensions/moq-streaming-format.md | 3 + wai/live-conformance/README.md | 1 + wai/live-conformance/loc/README.md | 34 + wai/live-conformance/loc/vectors.json | 239 +++++++ wai/live-conformance/loc_verify.py | 367 ++++++++++ wai/registry/capabilities.json | 7 + wai/wai-rs/src/codecs/registry.rs | 4 + 15 files changed, 1561 insertions(+), 2 deletions(-) create mode 100644 joule-code/joule-code-rs/crates/joule-moq/examples/loc_vectors.rs create mode 100644 joule-code/joule-code-rs/crates/joule-moq/src/loc.rs create mode 100644 joule-code/joule-code-rs/crates/joule-moq/tests/loc_vectors.rs create mode 100644 wai/extensions/loc-carriage.md create mode 100644 wai/live-conformance/loc/README.md create mode 100644 wai/live-conformance/loc/vectors.json create mode 100644 wai/live-conformance/loc_verify.py diff --git a/joule-code/CHANGELOG.md b/joule-code/CHANGELOG.md index c406fe3dd..b225b1226 100644 --- a/joule-code/CHANGELOG.md +++ b/joule-code/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to the Joule Code standard are recorded here. The format fol ## [Unreleased] +### Added — `joule-moq`: WAI envelopes on LOC objects + +- **`joule-moq`**: module `loc` (WAI `extensions/loc-carriage.md`). An envelope whose payload + is one WebCodecs chunk (`wai.audio.opus.packet`, `wai.video.avc`, `wai.video.hevc`, + `wai.audio.aac`) is written as one object of a track of packaging `loc` + (`draft-ietf-moq-loc-04`): its payload as the LOC Payload, and in the Immutable Properties + the LOC properties, the manifest as `WAI_MANIFEST` (`0x3A61`) and the `WAI_RECEIPT` + property. `from_loc` reads an object back into the envelope, byte for byte, refusing with + the extension's codes. `examples/loc_vectors.rs` writes WAI's corpus `live-conformance/loc/` + and `tests/loc_vectors.rs` regenerates and checks it; WAI's `loc_verify.py` is its second + implementation. + ### Added — `joule-moq`: `draft-ietf-moq-transport-18` and `-16` beside `-22`, and runs against other implementations' relays - **`joule-moq`**: codec modules `draft18` and `draft16` (control messages, parameters, diff --git a/joule-code/joule-code-rs/crates/joule-moq/README.md b/joule-code/joule-code-rs/crates/joule-moq/README.md index e813ef9d9..fad12afb7 100644 --- a/joule-code/joule-code-rs/crates/joule-moq/README.md +++ b/joule-code/joule-code-rs/crates/joule-moq/README.md @@ -169,6 +169,17 @@ the client's the grant as an AUTHORIZATION TOKEN beside the proof (`GRANT_PROOF` checks, given the grant's digest and principal by whatever read the grant; over HTTP the same record rides the `WAI-Room-Proof` header and the challenge `WAI-Room-Challenge`. +**LOC objects** (WAI `extensions/loc-carriage.md`, `loc`): a WAI envelope whose payload is +one WebCodecs chunk (an Opus packet, an AVC or HEVC access unit in Annex B, an ADTS frame) +rides a track of packaging `loc` (`draft-ietf-moq-loc-04`) as one object: the payload is the +LOC Payload, and the Immutable Properties hold the LOC properties (Timestamp `0x10`, +Timescale `0x08`, Video Frame Marking `0x09`, Audio Level `0x0C`, Video Config `0x0D`, +Audio Config `0x0F`), the envelope's manifest as `WAI_MANIFEST` (`0x3A61`, bytes) and +`WAI_RECEIPT`. `loc::to_loc` writes one and `loc::from_loc` reads it back into the envelope, +byte for byte, its properties and its receipt property, refusing with the codes of the +extension's §6. `tests/loc_vectors.rs` regenerates WAI's corpus `live-conformance/loc/` +(`examples/loc_vectors.rs gen`) and checks every vector in it. + The receipt extension is declared by the Setup Option `0x3A46`, a varint holding the extension's version. -22 has no unregistered range for Setup Options and asks for a provisional registration; until one is made the code point is this crate's choice, diff --git a/joule-code/joule-code-rs/crates/joule-moq/examples/loc_vectors.rs b/joule-code/joule-code-rs/crates/joule-moq/examples/loc_vectors.rs new file mode 100644 index 000000000..7b2fd510b --- /dev/null +++ b/joule-code/joule-code-rs/crates/joule-moq/examples/loc_vectors.rs @@ -0,0 +1,42 @@ +//! The LOC carriage corpus (WAI `live-conformance/loc/`, loc-carriage §8): +//! +//! cargo run -p joule-moq --example loc_vectors -- gen writes vectors.json (never in CI) +//! cargo run -p joule-moq --example loc_vectors -- verify checks it +//! +//! Its HEVC objects carry access units of WAI's `format-probe-conformance/ext/hevc/base_64x64.265`. + +use joule_moq::loc::corpus; + +const WAI: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../../wai/"); + +fn main() -> std::process::ExitCode { + let hevc = std::fs::read(format!("{WAI}format-probe-conformance/ext/hevc/base_64x64.265")).expect("the H.265 vector"); + let path = format!("{WAI}live-conformance/loc/vectors.json"); + match std::env::args().nth(1).as_deref() { + Some("gen") => match corpus::generate(&hevc) { + Ok(s) => { + std::fs::create_dir_all(format!("{WAI}live-conformance/loc")).expect("the corpus directory"); + std::fs::write(&path, s).expect("write vectors.json"); + println!("wrote {path}"); + std::process::ExitCode::SUCCESS + } + Err(e) => { + eprintln!("{e}"); + std::process::ExitCode::FAILURE + } + }, + Some("verify") => { + let doc: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(&path).expect("vectors.json")).expect("JSON"); + let fails = corpus::verify(&doc); + for f in &fails { + eprintln!("FAIL {f}"); + } + println!("{} failures", fails.len()); + if fails.is_empty() { std::process::ExitCode::SUCCESS } else { std::process::ExitCode::FAILURE } + } + _ => { + eprintln!("usage: loc_vectors gen | verify"); + std::process::ExitCode::from(2) + } + } +} diff --git a/joule-code/joule-code-rs/crates/joule-moq/src/lib.rs b/joule-code/joule-code-rs/crates/joule-moq/src/lib.rs index 549a01536..f10bafa81 100644 --- a/joule-code/joule-code-rs/crates/joule-moq/src/lib.rs +++ b/joule-code/joule-code-rs/crates/joule-moq/src/lib.rs @@ -40,6 +40,10 @@ //! and requests pass through the -22 codec; its relay also forwards staged //! tracks on a caller-driven clock: per-hop delivery timeouts, capping by //! subscription, and the receipts of dropped objects forwarded all the same +//! - LOC carriage of WAI envelopes ([`loc`]): an envelope whose payload is one WebCodecs +//! chunk as one object of a track of packaging `loc` (`draft-ietf-moq-loc-04`), its +//! manifest and receipt in the object's Immutable Properties, written and read back byte +//! for byte (WAI `extensions/loc-carriage.md`) //! - Staged tracks, one per layer of a WAI stage set ([`staged`]), and receipt chains //! kept per publisher key and track ([`ReceiptChains`]) //! - A native QUIC transport behind the `quic` feature (`transport_quic`): -22 @@ -88,6 +92,7 @@ pub mod draft22; pub mod endpoint; pub mod error; pub mod grant_proof; +pub mod loc; pub mod messages; #[cfg(feature = "runtime")] pub mod mock; diff --git a/joule-code/joule-code-rs/crates/joule-moq/src/loc.rs b/joule-code/joule-code-rs/crates/joule-moq/src/loc.rs new file mode 100644 index 000000000..1c5473b7c --- /dev/null +++ b/joule-code/joule-code-rs/crates/joule-moq/src/loc.rs @@ -0,0 +1,624 @@ +//! LOC carriage of WAI envelopes (WAI `extensions/loc-carriage.md`): a WAI envelope whose +//! capability's payload is one chunk of a codec the WebCodecs registry registers travels as +//! one object of a track of packaging `loc` (`draft-ietf-moq-msf-01` §4.1), in the Low +//! Overhead Media Container of `draft-ietf-moq-loc-04`: the envelope's payload is the LOC +//! Payload, its manifest an Immutable Property `WAI_MANIFEST`, its receipt commitment the +//! `WAI_RECEIPT` property of moq-streaming-format §5. [`to_loc`] writes an object; +//! [`from_loc`] reads one back into the envelope's bytes, its LOC properties and its +//! receipt property, and refuses with one of [`LocRefusal`]'s codes. +//! +//! No runtime and no new dependency, so it builds for `wasm32-unknown-unknown` with the codec. + +use crate::carriage::{prop as wai_prop, WaiReceiptProperty}; +use crate::draft22::kvp::{prop as wire_prop, KeyValuePair, Properties}; +use crate::receipt::ObjectReceipt; +use serde_json::Value; + +/// The property types of LOC (`draft-ietf-moq-loc-04` §2.3, Table 1) and the one this +/// extension adds. +pub mod prop { + pub const TIMESCALE: u64 = 0x08; + pub const VIDEO_FRAME_MARKING: u64 = 0x09; + pub const AUDIO_LEVEL: u64 = 0x0c; + pub const VIDEO_CONFIG: u64 = 0x0d; + pub const AUDIO_CONFIG: u64 = 0x0f; + pub const TIMESTAMP: u64 = 0x10; + /// The envelope's manifest bytes (loc-carriage §4.1), in the range MOQT §8.4 leaves to + /// an application; odd, so bytes. + pub const WAI_MANIFEST: u64 = 0x3a61; +} + +/// The capabilities this extension maps, each with the WebCodecs codec family whose chunk +/// its payload is and the form that payload takes (loc-carriage §3). +pub const MAPPED: &[(&str, &str)] = &[ + ("wai.audio.opus.packet", "opus"), + ("wai.video.avc", "avc1, avc3 (Annex B)"), + ("wai.video.hevc", "hev1, hvc1 (Annex B)"), + ("wai.audio.aac", "mp4a.40 (ADTS)"), +]; + +/// Why an object is not read, or an envelope not written (loc-carriage §6). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LocRefusal { + PropertyInvalid, + ManifestAbsent, + ManifestInvalid, + CapabilityUnmapped, + CapabilityMismatch, + PayloadForm, + ReceiptMismatch, + /// The `WAI_RECEIPT` property itself is refused (moq-streaming-format §5). + WaiReceiptInvalid, +} + +impl LocRefusal { + pub const fn all() -> [LocRefusal; 8] { + [ + LocRefusal::PropertyInvalid, LocRefusal::ManifestAbsent, LocRefusal::ManifestInvalid, + LocRefusal::CapabilityUnmapped, LocRefusal::CapabilityMismatch, LocRefusal::PayloadForm, + LocRefusal::ReceiptMismatch, LocRefusal::WaiReceiptInvalid, + ] + } + pub fn code(self) -> &'static str { + match self { + LocRefusal::PropertyInvalid => "loc_property_invalid", + LocRefusal::ManifestAbsent => "loc_manifest_absent", + LocRefusal::ManifestInvalid => "loc_manifest_invalid", + LocRefusal::CapabilityUnmapped => "loc_capability_unmapped", + LocRefusal::CapabilityMismatch => "loc_capability_mismatch", + LocRefusal::PayloadForm => "loc_payload_form", + LocRefusal::ReceiptMismatch => "loc_receipt_mismatch", + LocRefusal::WaiReceiptInvalid => "property_invalid", + } + } +} + +/// The LOC properties of one object (`draft-ietf-moq-loc-04` §2.3). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct LocProperties { + pub timestamp: Option, + pub timescale: Option, + pub video_frame_marking: Option>, + pub audio_level: Option, + pub video_config: Option>, + pub audio_config: Option>, +} + +impl LocProperties { + fn check(&self) -> Result<(), LocRefusal> { + let bad = self.timescale == Some(0) + || self.audio_level.is_some_and(|l| l > 255) + || self.video_frame_marking.as_ref().is_some_and(|m| !(1..=4).contains(&m.len())) + || self.video_config.as_ref().is_some_and(Vec::is_empty) + || self.audio_config.as_ref().is_some_and(Vec::is_empty); + if bad { Err(LocRefusal::PropertyInvalid) } else { Ok(()) } + } + + fn pairs(&self) -> Vec { + let mut p = Vec::new(); + if let Some(v) = self.timescale { + p.push(KeyValuePair::varint(prop::TIMESCALE, v)); + } + if let Some(v) = &self.video_frame_marking { + p.push(KeyValuePair::bytes(prop::VIDEO_FRAME_MARKING, v.clone())); + } + if let Some(v) = self.audio_level { + p.push(KeyValuePair::varint(prop::AUDIO_LEVEL, v)); + } + if let Some(v) = &self.video_config { + p.push(KeyValuePair::bytes(prop::VIDEO_CONFIG, v.clone())); + } + if let Some(v) = &self.audio_config { + p.push(KeyValuePair::bytes(prop::AUDIO_CONFIG, v.clone())); + } + if let Some(v) = self.timestamp { + p.push(KeyValuePair::varint(prop::TIMESTAMP, v)); + } + p + } +} + +/// A WAI1 envelope's manifest and payload (SPEC §2), or `None` if the bytes are not one. +pub fn envelope_parts(e: &[u8]) -> Option<(&[u8], &[u8])> { + if e.len() < 8 || &e[..4] != b"WAI1" { + return None; + } + let ml = u32::from_le_bytes([e[4], e[5], e[6], e[7]]) as usize; + let man = e.get(8..8usize.checked_add(ml)?)?; + let at = 8 + ml; + let pl = u32::from_le_bytes(e.get(at..at + 4)?.try_into().ok()?) as usize; + let payload = e.get(at + 4..(at + 4).checked_add(pl)?)?; + if at + 4 + pl != e.len() { + return None; + } + Some((man, payload)) +} + +/// The envelope of a manifest and a payload (SPEC §2). +pub fn envelope(manifest: &[u8], payload: &[u8]) -> Vec { + let mut e = Vec::with_capacity(12 + manifest.len() + payload.len()); + e.extend_from_slice(b"WAI1"); + e.extend_from_slice(&(manifest.len() as u32).to_le_bytes()); + e.extend_from_slice(manifest); + e.extend_from_slice(&(payload.len() as u32).to_le_bytes()); + e.extend_from_slice(payload); + e +} + +/// The capability a manifest names, after the checks of loc-carriage §5 step 4: a JSON +/// object, `wai` of major version 1, `model_requirement.capability` a string, and no +/// encryption. +pub fn manifest_capability(manifest: &[u8]) -> Result { + let v: Value = serde_json::from_slice(manifest).map_err(|_| LocRefusal::ManifestInvalid)?; + let o = v.as_object().ok_or(LocRefusal::ManifestInvalid)?; + let major_one = o.get("wai").and_then(Value::as_str).is_some_and(|w| w.split('.').next() == Some("1")); + let cap = o.get("model_requirement").and_then(|m| m.get("capability")).and_then(Value::as_str); + let enc = o.get("security").and_then(|s| s.get("encryption")); + let plaintext = match enc { + None | Some(Value::Null) => true, + Some(Value::String(s)) => s == "none", + _ => false, + }; + match (major_one, cap, plaintext) { + (true, Some(c), true) => Ok(c.to_string()), + _ => Err(LocRefusal::ManifestInvalid), + } +} + +fn units(b: &[u8]) -> Option> { + let mut i = 0; + while i < b.len() && b[i] == 0 { + i += 1; + } + if i < 2 || i >= b.len() || b[i] != 1 { + return None; + } + let mut starts = Vec::new(); + let mut j = 0; + while j + 2 < b.len() { + if b[j] == 0 && b[j + 1] == 0 && b[j + 2] == 1 { + starts.push(j + 3); + j += 3; + } else { + j += 1; + } + } + let mut out = Vec::new(); + for (k, &s) in starts.iter().enumerate() { + let mut e = if k + 1 < starts.len() { starts[k + 1] - 3 } else { b.len() }; + while e > s && b[e - 1] == 0 { + e -= 1; + } + out.push(&b[s..e]); + } + Some(out) +} + +/// Whether the leading Exp-Golomb code of `b` (after emulation prevention, which cannot +/// occur in its first bytes here) is 0: its first bit is 1. +fn first_bit(b: &[u8]) -> Option { + b.first().map(|x| x & 0x80 != 0) +} + +/// loc-carriage §3.2: the payload is one chunk of the mapped codec. +pub fn payload_form(capability: &str, p: &[u8]) -> Result<(), LocRefusal> { + let ok = match capability { + "wai.audio.opus.packet" => !p.is_empty(), + "wai.video.avc" => units(p).is_some_and(|us| { + // A VCL NAL unit (types 1 to 5) begins a picture when its first_mb_in_slice, + // the slice header's first ue(v), is 0. + let starts = us.iter().filter(|u| !u.is_empty() && (1..=5).contains(&(u[0] & 0x1f)) && first_bit(&u[1..]) == Some(true)).count(); + us.iter().all(|u| !u.is_empty() && u[0] & 0x80 == 0) && starts == 1 + }), + "wai.video.hevc" => units(p).is_some_and(|us| { + let ok_headers = us.iter().all(|u| u.len() >= 2 && u[0] & 0x80 == 0); + let base_only = us.iter().all(|u| u.len() >= 2 && (((u[0] & 1) << 5) | (u[1] >> 3)) == 0); + // first_slice_segment_in_pic_flag: the first bit after the two header bytes. + let starts = us.iter().filter(|u| u.len() >= 3 && (u[0] >> 1) & 0x3f < 32 && u[2] & 0x80 != 0).count(); + ok_headers && base_only && starts == 1 + }), + "wai.audio.aac" => { + p.len() >= 7 && p[0] == 0xff && p[1] & 0xf6 == 0xf0 + && ((usize::from(p[3] & 3) << 11) | (usize::from(p[4]) << 3) | (usize::from(p[5]) >> 5)) == p.len() + } + _ => return Err(LocRefusal::CapabilityUnmapped), + }; + if ok { Ok(()) } else { Err(LocRefusal::PayloadForm) } +} + +/// One object of a LOC track: its MOQ Object Properties and its payload. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LocObject { + pub properties: Properties, + pub payload: Vec, +} + +/// Writes `envelope` as a LOC object (loc-carriage §4): its payload as the object's payload, +/// and in the Immutable Properties its manifest, the LOC properties and the receipt property. +pub fn to_loc(envelope_bytes: &[u8], loc: &LocProperties, receipt: Option<&WaiReceiptProperty>) -> Result { + let (man, payload) = envelope_parts(envelope_bytes).ok_or(LocRefusal::ManifestInvalid)?; + if man.len() > 0xffff { + return Err(LocRefusal::ManifestInvalid); + } + let cap = manifest_capability(man)?; + payload_form(&cap, payload)?; + loc.check()?; + let mut inner = Properties::new(); + for kv in loc.pairs() { + inner.push(kv); + } + inner.push(KeyValuePair::bytes(prop::WAI_MANIFEST, man.to_vec())); + if let Some(r) = receipt { + if r.content_hash != ObjectReceipt::hash_payload(envelope_bytes) { + return Err(LocRefusal::ReceiptMismatch); + } + inner.push(KeyValuePair::bytes(wai_prop::WAI_RECEIPT, r.to_bytes())); + } + let mut properties = Properties::new(); + properties.set_immutable(&inner).map_err(|_| LocRefusal::PropertyInvalid)?; + Ok(LocObject { properties, payload: payload.to_vec() }) +} + +/// What [`from_loc`] reads: the envelope, rebuilt byte for byte, its LOC properties, and its +/// receipt property where the object carries one. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LocRead { + pub envelope: Vec, + pub loc: LocProperties, + pub receipt: Option, +} + +/// Reads an object of a LOC track that carries `track_capability` (its catalog's +/// `wai_capabilities`), in loc-carriage §5's order. +pub fn from_loc(properties: &Properties, payload: &[u8], track_capability: &str) -> Result { + // 1. At most one Immutable Properties, which parses and nests none. + let pi = |_| LocRefusal::PropertyInvalid; + if properties.0.iter().filter(|p| p.ty == wire_prop::IMMUTABLE_PROPERTIES).count() > 1 { + return Err(LocRefusal::PropertyInvalid); + } + let inner = properties.immutable().map_err(pi)?.unwrap_or_default(); + if inner.0.iter().any(|p| p.ty == wire_prop::IMMUTABLE_PROPERTIES) { + return Err(LocRefusal::PropertyInvalid); + } + // 2. Each LOC property at most once, with a value of its kind. + let all = properties.flattened().map_err(pi)?; + let one = |ty: u64| -> Result, LocRefusal> { + let mut it = all.iter().filter(|p| p.ty == ty); + let first = it.next(); + if it.next().is_some() { Err(LocRefusal::PropertyInvalid) } else { Ok(first) } + }; + let v = |p: Option<&KeyValuePair>| p.and_then(KeyValuePair::as_varint); + let b = |p: Option<&KeyValuePair>| p.and_then(KeyValuePair::as_bytes).map(<[u8]>::to_vec); + let loc = LocProperties { + timestamp: v(one(prop::TIMESTAMP)?), + timescale: v(one(prop::TIMESCALE)?), + video_frame_marking: b(one(prop::VIDEO_FRAME_MARKING)?), + audio_level: v(one(prop::AUDIO_LEVEL)?), + video_config: b(one(prop::VIDEO_CONFIG)?), + audio_config: b(one(prop::AUDIO_CONFIG)?), + }; + loc.check()?; + // 3. WAI_MANIFEST and WAI_RECEIPT only inside the Immutable Properties, once. + for ty in [prop::WAI_MANIFEST, wai_prop::WAI_RECEIPT] { + if properties.0.iter().any(|p| p.ty == ty) || inner.0.iter().filter(|p| p.ty == ty).count() > 1 { + return Err(LocRefusal::PropertyInvalid); + } + } + let man = inner.0.iter().find(|p| p.ty == prop::WAI_MANIFEST).and_then(KeyValuePair::as_bytes).ok_or(LocRefusal::ManifestAbsent)?; + // 4. The manifest, and the capability it names. + let cap = manifest_capability(man)?; + if !MAPPED.iter().any(|(c, _)| *c == cap) { + return Err(LocRefusal::CapabilityUnmapped); + } + if cap != track_capability { + return Err(LocRefusal::CapabilityMismatch); + } + // 5. The payload is one chunk of the codec. + payload_form(&cap, payload)?; + // 6. The envelope, and 7. the receipt property's commitment to it. + let env = envelope(man, payload); + let receipt = match inner.0.iter().find(|p| p.ty == wai_prop::WAI_RECEIPT).and_then(KeyValuePair::as_bytes) { + None => None, + Some(bytes) => { + let r = WaiReceiptProperty::from_bytes(bytes).map_err(|_| LocRefusal::WaiReceiptInvalid)?; + r.check().map_err(|_| LocRefusal::WaiReceiptInvalid)?; + if r.content_hash != ObjectReceipt::hash_payload(&env) { + return Err(LocRefusal::ReceiptMismatch); + } + Some(r) + } + }; + Ok(LocRead { envelope: env, loc, receipt }) +} + +/// The corpus `wai/live-conformance/loc/` (loc-carriage §8). +pub mod corpus { + use super::*; + use serde_json::json; + + fn hex(b: &[u8]) -> String { + b.iter().map(|x| format!("{x:02x}")).collect() + } + + fn manifest(cap: &str, media: &str, extra: &str) -> Vec { + format!(r#"{{"wai":"1.0","media":"{media}","intent":"replicate","model_requirement":{{"capability":"{cap}","fallback":null}}{extra}}}"#).into_bytes() + } + + /// A group receipt link hash for the corpus: fixed, from a label. + fn group(label: &str) -> [u8; 32] { + *blake3::hash(label.as_bytes()).as_bytes() + } + + fn receipt_for(env: &[u8], joules: u64, class: &str, label: &str) -> WaiReceiptProperty { + WaiReceiptProperty { + content_hash: ObjectReceipt::hash_payload(env), + joules_micro: joules, + acquisition: class.to_string(), + acquisition_evidence: Vec::new(), + group_receipt: group(label), + } + } + + /// The HEVC access units of a byte stream: each from a NAL unit that begins a base-layer + /// picture (or precedes one) to the next. + pub fn hevc_access_units(stream: &[u8]) -> Vec> { + let us = units(stream).unwrap_or_default(); + let mut out: Vec> = Vec::new(); + let mut cur: Vec = Vec::new(); + let mut has_pic = false; + for u in us { + let t = (u[0] >> 1) & 0x3f; + let first_slice = t < 32 && u.len() >= 3 && u[2] & 0x80 != 0; + // A parameter set, an AUD or a prefix SEI after a picture, or a picture's first + // slice after one, starts the next access unit. + let starts = has_pic && (first_slice || (32..=35).contains(&t) || t == 39); + if starts { + out.push(std::mem::take(&mut cur)); + has_pic = false; + } + cur.extend_from_slice(&[0, 0, 0, 1]); + cur.extend_from_slice(u); + if t < 32 { + has_pic = true; + } + } + if !cur.is_empty() { + out.push(cur); + } + out + } + + /// Builds `vectors.json` from the H.265 byte stream whose access units the HEVC + /// vectors carry. Every other byte is a fixed value of this function. + pub fn generate(hevc_stream: &[u8]) -> Result { + let aus = hevc_access_units(hevc_stream); + if aus.len() < 2 { + return Err("the H.265 stream holds fewer than two access units".into()); + } + // An Opus packet: one 20 ms CELT frame of silence (TOC 0xF8: config 31, mono, one frame). + let opus = vec![0xf8u8, 0xff, 0xfe]; + // An ADTS frame: the header (AAC LC, 48 kHz, two channels, no CRC) and a raw data + // block of fixed bytes. Only its form is checked; it is not a decodable frame. + let mut adts = vec![0xff, 0xf1, 0x4c, 0x80, 0x00, 0x00, 0xfc]; + adts.extend_from_slice(&[0x21, 0x10, 0x04, 0x60, 0x8c, 0x1c]); + let n = adts.len(); + adts[3] = (adts[3] & 0xfc) | ((n >> 11) & 3) as u8; + adts[4] = ((n >> 3) & 0xff) as u8; + adts[5] = (((n & 7) << 5) as u8) | 0x1f; + // An H.264 access unit: an SPS, a PPS and an IDR slice of fixed bytes (first_mb_in_slice + // 0). Only its form is checked; it is not a decodable picture. + let avc = [&[0u8, 0, 0, 1, 0x67, 0x42, 0xc0, 0x0a, 0x8c, 0x8d, 0x40][..], &[0, 0, 0, 1, 0x68, 0xce, 0x3c, 0x80], &[0, 0, 0, 1, 0x65, 0x88, 0x84, 0x00, 0x33, 0xff]].concat(); + + let opus_env = envelope(&manifest("wai.audio.opus.packet", "audio", ""), &opus); + let hevc_env = envelope(&manifest("wai.video.hevc", "video", r#","target":{"width":64,"height":64}"#), &aus[0]); + let hevc_env2 = envelope(&manifest("wai.video.hevc", "video", ""), &aus[1]); + let aac_env = envelope(&manifest("wai.audio.aac", "audio", ""), &adts); + let avc_env = envelope(&manifest("wai.video.avc", "video", ""), &avc); + + let audio = LocProperties { timestamp: Some(960), timescale: Some(48_000), audio_level: Some(127), ..Default::default() }; + let video = LocProperties { timestamp: Some(1_790_000_000_000_000), video_frame_marking: Some(vec![0xa0]), ..Default::default() }; + let mut objects = Vec::new(); + let mut written: Vec<(String, String, LocObject)> = Vec::new(); + for (name, cap, env, loc, rc) in [ + ("opus_unmetered", "wai.audio.opus.packet", &opus_env, audio.clone(), Some(receipt_for(&opus_env, 0, "", "group-0"))), + ("opus_no_receipt", "wai.audio.opus.packet", &opus_env, LocProperties::default(), None), + ("hevc_key_metered", "wai.video.hevc", &hevc_env, video.clone(), Some(receipt_for(&hevc_env, 1_250_000, "HwShunt", "group-1"))), + ("hevc_delta", "wai.video.hevc", &hevc_env2, LocProperties { video_config: Some(vec![1, 2, 3]), ..video.clone() }, Some(receipt_for(&hevc_env2, 0, "", "group-1"))), + ("aac_frame", "wai.audio.aac", &aac_env, LocProperties { audio_config: Some(vec![0x11, 0x90]), ..audio.clone() }, None), + ("avc_access_unit", "wai.video.avc", &avc_env, LocProperties { timestamp: Some(0), timescale: Some(90_000), ..Default::default() }, None), + ] { + let o = to_loc(env, &loc, rc.as_ref()).map_err(|e| format!("{name}: {}", e.code()))?; + let back = from_loc(&o.properties, &o.payload, cap).map_err(|e| format!("{name}: {}", e.code()))?; + if back.envelope != *env || back.loc != loc || back.receipt != rc { + return Err(format!("{name}: does not read back")); + } + objects.push(json!({ + "name": name, "capability": cap, "envelope": hex(env), + "properties": hex(&o.properties.to_bytes().map_err(|e| e.to_string())?), "payload": hex(&o.payload), + "loc": {"timestamp": loc.timestamp, "timescale": loc.timescale, "video_frame_marking": loc.video_frame_marking.as_deref().map(hex), "audio_level": loc.audio_level, "video_config": loc.video_config.as_deref().map(hex), "audio_config": loc.audio_config.as_deref().map(hex)}, + "receipt": rc.as_ref().map(|r| hex(&r.to_bytes())), + })); + written.push((name.to_string(), cap.to_string(), o)); + } + + // Envelopes the writer refuses. + let vp9 = envelope(&manifest("wai.video.vp9", "video", ""), b"DKIF"); + let two_pics = envelope(&manifest("wai.video.hevc", "video", ""), &[aus[0].clone(), aus[1].clone()].concat()); + let sealed = envelope(&manifest("wai.audio.opus.packet", "audio", r#","security":{"encryption":"mls","kid":7}"#), &opus); + let mut writer_refused = Vec::new(); + for (name, env, want) in [ + ("unmapped_vp9", &vp9, LocRefusal::CapabilityUnmapped), + ("two_pictures", &two_pics, LocRefusal::PayloadForm), + ("encrypted_manifest", &sealed, LocRefusal::ManifestInvalid), + ("not_an_envelope", &b"WAI2\0\0\0\0".to_vec(), LocRefusal::ManifestInvalid), + ] { + let got = to_loc(env, &LocProperties::default(), None).err(); + if got != Some(want) { + return Err(format!("writer {name}: {got:?}, expected {want:?}")); + } + writer_refused.push(json!({"name": name, "envelope": hex(env), "refuse": want.code()})); + } + + // Objects the reader refuses: each a change to one written object. + let base = &written[0].2; + let hevc = &written[2].2; + let inner_of = |o: &LocObject| o.properties.immutable().expect("parses").expect("present"); + let with_inner = |inner: Properties| { + let mut p = Properties::new(); + p.set_immutable(&inner).expect("encodes"); + p + }; + let edit = |o: &LocObject, f: &dyn Fn(&mut Vec)| { + let mut i = inner_of(o).0; + f(&mut i); + with_inner(Properties(i)) + }; + let mut refused = Vec::new(); + let mut cases: Vec<(&str, &str, Properties, Vec)> = vec![ + ("manifest_absent", "wai.audio.opus.packet", edit(base, &|i| i.retain(|p| p.ty != prop::WAI_MANIFEST)), base.payload.clone()), + ("manifest_twice", "wai.audio.opus.packet", edit(base, &|i| { + let m = i.iter().find(|p| p.ty == prop::WAI_MANIFEST).unwrap().clone(); + i.push(m); + }), base.payload.clone()), + ("manifest_not_json", "wai.audio.opus.packet", edit(base, &|i| { + for p in i.iter_mut().filter(|p| p.ty == prop::WAI_MANIFEST) { + *p = KeyValuePair::bytes(prop::WAI_MANIFEST, b"{\"wai\":".to_vec()); + } + }), base.payload.clone()), + ("manifest_major_2", "wai.audio.opus.packet", edit(base, &|i| { + for p in i.iter_mut().filter(|p| p.ty == prop::WAI_MANIFEST) { + *p = KeyValuePair::bytes(prop::WAI_MANIFEST, manifest("wai.audio.opus.packet", "audio", "").iter().map(|&c| if c == b'1' { b'2' } else { c }).collect::>()); + } + }), base.payload.clone()), + ("timescale_zero", "wai.audio.opus.packet", edit(base, &|i| { + for p in i.iter_mut().filter(|p| p.ty == prop::TIMESCALE) { + *p = KeyValuePair::varint(prop::TIMESCALE, 0); + } + }), base.payload.clone()), + ("audio_level_256", "wai.audio.opus.packet", edit(base, &|i| { + for p in i.iter_mut().filter(|p| p.ty == prop::AUDIO_LEVEL) { + *p = KeyValuePair::varint(prop::AUDIO_LEVEL, 256); + } + }), base.payload.clone()), + ("timestamp_twice", "wai.audio.opus.packet", { + let mut p = base.properties.clone(); + p.push(KeyValuePair::varint(prop::TIMESTAMP, 5)); + p + }, base.payload.clone()), + ("manifest_outside_immutable", "wai.audio.opus.packet", { + let mut p = edit(base, &|i| i.retain(|p| p.ty != prop::WAI_MANIFEST)); + p.push(KeyValuePair::bytes(prop::WAI_MANIFEST, manifest("wai.audio.opus.packet", "audio", ""))); + p + }, base.payload.clone()), + ("track_carries_aac", "wai.audio.aac", base.properties.clone(), base.payload.clone()), + ("payload_empty", "wai.audio.opus.packet", base.properties.clone(), Vec::new()), + ("payload_changed", "wai.audio.opus.packet", base.properties.clone(), vec![0xf8, 0xff, 0xfd]), + ("receipt_cut_short", "wai.video.hevc", edit(hevc, &|i| { + for p in i.iter_mut().filter(|p| p.ty == wai_prop::WAI_RECEIPT) { + let b = p.as_bytes().unwrap()[..40].to_vec(); + *p = KeyValuePair::bytes(wai_prop::WAI_RECEIPT, b); + } + }), hevc.payload.clone()), + ("receipt_class_beside_zero", "wai.audio.opus.packet", edit(base, &|i| { + for p in i.iter_mut().filter(|p| p.ty == wai_prop::WAI_RECEIPT) { + let mut r = WaiReceiptProperty::from_bytes(p.as_bytes().unwrap()).unwrap(); + r.acquisition = "HwShunt".into(); + *p = KeyValuePair::bytes(wai_prop::WAI_RECEIPT, r.to_bytes()); + } + }), base.payload.clone()), + ("hevc_second_layer", "wai.video.hevc", hevc.properties.clone(), { + let mut pl = hevc.payload.clone(); + // a NAL unit header of nuh_layer_id 1 appended + pl.extend_from_slice(&[0, 0, 0, 1, 0x02, 0x09, 0xaa]); + pl + }), + ]; + // An unmapped capability named by the manifest itself. + cases.push(("manifest_names_vp9", "wai.video.vp9", edit(base, &|i| { + for p in i.iter_mut().filter(|p| p.ty == prop::WAI_MANIFEST) { + *p = KeyValuePair::bytes(prop::WAI_MANIFEST, manifest("wai.video.vp9", "video", "")); + } + }), base.payload.clone())); + for (name, cap, props, payload) in cases { + let got = from_loc(&props, &payload, cap).err().ok_or_else(|| format!("reader {name}: accepted"))?; + refused.push(json!({"name": name, "capability": cap, "properties": hex(&props.to_bytes().map_err(|e| e.to_string())?), "payload": hex(&payload), "refuse": got.code()})); + } + for code in LocRefusal::all().iter().map(|r| r.code()) { + let given = refused.iter().chain(&writer_refused).any(|v| v["refuse"] == code); + if !given { + return Err(format!("no vector is refused with {code}")); + } + } + let doc = json!({ + "property_types": {"timestamp": prop::TIMESTAMP, "timescale": prop::TIMESCALE, "video_frame_marking": prop::VIDEO_FRAME_MARKING, "audio_level": prop::AUDIO_LEVEL, "video_config": prop::VIDEO_CONFIG, "audio_config": prop::AUDIO_CONFIG, "wai_manifest": prop::WAI_MANIFEST, "wai_receipt": wai_prop::WAI_RECEIPT}, + "objects": objects, + "writer_refused": writer_refused, + "refused": refused, + }); + let mut s = serde_json::to_string_pretty(&doc).map_err(|e| e.to_string())?; + s.push('\n'); + Ok(s) + } + + fn unhex(s: &str) -> Vec { + (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap_or(0)).collect() + } + + /// Checks `vectors.json`: every object reads back to its envelope, LOC properties and + /// receipt, and the writer writes it from them; every refused object and envelope gets + /// its code. + pub fn verify(doc: &Value) -> Vec { + let mut fails = Vec::new(); + let opt_hex = |v: &Value| v.as_str().map(unhex); + for o in doc["objects"].as_array().into_iter().flatten() { + let name = o["name"].as_str().unwrap_or("?"); + let cap = o["capability"].as_str().unwrap_or(""); + let Ok(props) = Properties::decode(&unhex(o["properties"].as_str().unwrap_or("")), "properties") else { + fails.push(format!("{name}: properties do not decode")); + continue; + }; + let payload = unhex(o["payload"].as_str().unwrap_or("")); + let l = &o["loc"]; + let loc = LocProperties { + timestamp: l["timestamp"].as_u64(), timescale: l["timescale"].as_u64(), + video_frame_marking: opt_hex(&l["video_frame_marking"]), audio_level: l["audio_level"].as_u64(), + video_config: opt_hex(&l["video_config"]), audio_config: opt_hex(&l["audio_config"]), + }; + let env = unhex(o["envelope"].as_str().unwrap_or("")); + let rc = o["receipt"].as_str().map(|r| WaiReceiptProperty::from_bytes(&unhex(r))); + let rc = match rc { + None => None, + Some(Ok(r)) => Some(r), + Some(Err(_)) => { + fails.push(format!("{name}: its receipt does not read")); + continue; + } + }; + match from_loc(&props, &payload, cap) { + Ok(r) if r.envelope == env && r.loc == loc && r.receipt == rc => {} + Ok(_) => fails.push(format!("{name}: reads back to another envelope, LOC properties or receipt")), + Err(e) => fails.push(format!("{name}: refused {}", e.code())), + } + match to_loc(&env, &loc, rc.as_ref()) { + Ok(w) if w.properties == props && w.payload == payload => {} + _ => fails.push(format!("{name}: the writer does not write it")), + } + } + for c in doc["writer_refused"].as_array().into_iter().flatten() { + let got = to_loc(&unhex(c["envelope"].as_str().unwrap_or("")), &LocProperties::default(), None).err().map(|e| e.code()); + if got != c["refuse"].as_str() { + fails.push(format!("writer {}: {got:?}", c["name"])); + } + } + for c in doc["refused"].as_array().into_iter().flatten() { + let got = Properties::decode(&unhex(c["properties"].as_str().unwrap_or("")), "properties") + .ok() + .and_then(|p| from_loc(&p, &unhex(c["payload"].as_str().unwrap_or("")), c["capability"].as_str().unwrap_or("")).err()) + .map(|e| e.code()); + if got != c["refuse"].as_str() { + fails.push(format!("reader {}: {got:?}", c["name"])); + } + } + fails + } +} diff --git a/joule-code/joule-code-rs/crates/joule-moq/tests/loc_vectors.rs b/joule-code/joule-code-rs/crates/joule-moq/tests/loc_vectors.rs new file mode 100644 index 000000000..c46d83ccd --- /dev/null +++ b/joule-code/joule-code-rs/crates/joule-moq/tests/loc_vectors.rs @@ -0,0 +1,37 @@ +//! LOC carriage (WAI `extensions/loc-carriage.md`): the committed corpus +//! `wai/live-conformance/loc/vectors.json` regenerates byte for byte from the H.265 stream it +//! takes its access units from, every object and refusal in it holds, and the extension's §6 +//! lists exactly the codes [`LocRefusal::all`] gives. The corpus lives beside this crate in +//! the monorepo; if it cannot be read the test fails, it never passes on nothing. + +use joule_moq::loc::{corpus, LocRefusal}; + +const WAI: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../../../wai/"); + +fn committed() -> String { + std::fs::read_to_string(format!("{WAI}live-conformance/loc/vectors.json")).expect("the LOC corpus") +} + +#[test] +fn the_corpus_regenerates() { + let hevc = std::fs::read(format!("{WAI}format-probe-conformance/ext/hevc/base_64x64.265")).expect("the H.265 vector"); + assert_eq!(corpus::generate(&hevc).expect("generates"), committed(), "run `cargo run -p joule-moq --example loc_vectors -- gen`"); +} + +#[test] +fn every_vector_holds() { + let doc: serde_json::Value = serde_json::from_str(&committed()).expect("JSON"); + assert!(doc["objects"].as_array().is_some_and(|a| a.len() >= 6)); + let fails = corpus::verify(&doc); + assert!(fails.is_empty(), "{}", fails.join("\n")); +} + +#[test] +fn the_extension_lists_the_codes() { + let text = std::fs::read_to_string(format!("{WAI}extensions/loc-carriage.md")).expect("the extension"); + let at = text.find("\n## 6. Refusals").expect("a §6"); + let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); + let listed: Vec<&str> = text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1)).collect(); + let want: Vec<&str> = LocRefusal::all().iter().map(|r| r.code()).collect(); + assert_eq!(listed, want); +} diff --git a/wai/REGISTRY.md b/wai/REGISTRY.md index e9ebbe95b..83fb3651d 100644 --- a/wai/REGISTRY.md +++ b/wai/REGISTRY.md @@ -65,8 +65,8 @@ they serve ([jwp-receipts](extensions/jwp-receipts.md) §7), [live-compose](extensions/live-compose.md), [live-recording](extensions/live-recording.md), [timed-text](extensions/timed-text.md), [ai-usage](extensions/ai-usage.md), [exploration](extensions/exploration.md), [browser-sink](extensions/browser-sink.md), -[delivery-telemetry](extensions/delivery-telemetry.md) and [projection](extensions/projection.md) -put on the wire outside a manifest — magic numbers, a media type, `codecs` prefixes, paths, playlist tags and +[delivery-telemetry](extensions/delivery-telemetry.md), [projection](extensions/projection.md) +and [loc-carriage](extensions/loc-carriage.md) put on the wire outside a manifest — magic numbers, a media type, `codecs` prefixes, paths, playlist tags and attributes, document kinds, catalog fields, MOQT Setup Options, HTTP fields, a member of a decoded frame's metadata and the custom keys of CMCD and CMSD reports — each with its kind, the section that defines it, and whether it is defined, provisional (test material only until @@ -331,6 +331,7 @@ and a role are fixed once registered. | `WAI1` | magic | defined | SPEC §2 | | `WAI2` | magic | defined | SPEC §7.1 | | `WAI_INNER_DIGEST` | catalog | defined | confidentiality §4.4 | +| `WAI_MANIFEST` | catalog | defined | loc-carriage §4 | | `WAI_RATING` | catalog | defined | age-assurance §5.7 | | `WAI_RECEIPT` | catalog | defined | moq-streaming-format §5 | | `WAP1` | magic | defined | live-ads §3.1 | diff --git a/wai/extensions/loc-carriage.md b/wai/extensions/loc-carriage.md new file mode 100644 index 000000000..19cd1bff5 --- /dev/null +++ b/wai/extensions/loc-carriage.md @@ -0,0 +1,172 @@ +# WAI Extension: LOC Carriage + +> Status: Draft, tracking its drafts as [moq-streaming-format](moq-streaming-format.md) does. +> Maps a WAI envelope and its receipt onto one object of a track of packaging `loc`: the Low +> Overhead Media Container of `draft-ietf-moq-loc-04` (20 July 2026), the packaging +> `draft-ietf-moq-msf-01` §4.1 names, over `draft-ietf-moq-transport-22`. A WAI envelope whose +> capability's payload is one chunk of a codec the WebCodecs Codec Registry registers travels as +> one LOC object: its payload is the LOC Payload, unchanged; its manifest is an Immutable +> Property, `WAI_MANIFEST`; its receipt commitment is the `WAI_RECEIPT` property of +> moq-streaming-format §5. A subscriber that reads LOC and nothing else hands the payload to its +> decoder as a chunk; a subscriber that reads WAI rebuilds the envelope byte for byte and checks +> its receipt against it. No new transport. The reference implementation is the `loc` module of +> `joule-moq` (`joule-code/joule-code-rs/crates/joule-moq`); the corpus is +> [`live-conformance/loc/`](../live-conformance/loc/), and its second implementation is +> `live-conformance/loc_verify.py`. +> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. +> +> A section number of LOC, MSF or MOQT here is one of those revisions. Where a later revision +> renumbers or renames what this text cites, the citation is what changes (moq-streaming-format +> §6). + +## 1. Scope + +§1.1 Packaging `wai` (moq-streaming-format §3) carries whole envelopes, and a subscriber that +does not read WAI cannot use its objects. Packaging `loc` carries a codec's chunks, which any +LOC subscriber can decode. This extension lets one track be both: its objects are LOC objects, +and a WAI subscriber reads each as the envelope it came from. + +§1.2 It maps the capabilities of §3 and no others. A capability whose payload is not one chunk +of a registered WebCodecs codec travels as packaging `wai`. + +§1.3 It changes no envelope and no receipt: the envelope a subscriber rebuilds is the one the +publisher wrote, byte for byte, and the receipt commits to it exactly as jwp-receipts §2.1 says. + +## 2. LOC, as this extension reads it + +§2.1 A LOC object's MOQ Object Properties hold the LOC Public Properties, and its payload holds +the LOC Private Properties followed by the LOC Payload, the "internal data" of a WebCodecs +`EncodedAudioChunk` or `EncodedVideoChunk` (LOC §2, §2.2). LOC defines Private Properties only +with Secure Objects (LOC §3.1.3): an object this extension writes carries none, and a sealed +object ([confidentiality](confidentiality.md) §4) is opened first and then read as here. + +§2.2 The LOC properties (LOC §2.3, §6.1), each at most once on an object: + +| type | name | value | rule | +|---|---|---|---| +| `0x10` | Timestamp | varint | | +| `0x08` | Timescale | varint | not 0 | +| `0x09` | Video Frame Marking | bytes | 1 to 4 bytes (RFC 9626) | +| `0x0C` | Audio Level | varint | at most 255 (RFC 6464 §3) | +| `0x0D` | Video Config | bytes | not empty | +| `0x0F` | Audio Config | bytes | not empty | + +§2.3 MSF §4.1 places one chunk in each object and a group of pictures in one group; MSF §5.2 +names the track's `codec` by the WebCodecs codec string. + +## 3. Mapped capabilities + +§3.1 The capabilities this extension maps, each with the WebCodecs codec whose chunk its payload +is: + +| capability | WebCodecs codec | the payload, one chunk | +|---|---|---| +| `wai.audio.opus.packet` | `opus`, format `opus` | one Opus packet (RFC 6716 §3) | +| `wai.video.avc` | `avc1` or `avc3`, Annex B (no `description`) | one access unit as an Annex B byte stream | +| `wai.video.hevc` | `hev1` or `hvc1`, Annex B (no `description`) | one access unit as an Annex B byte stream, of the base layer only | +| `wai.audio.aac` | `mp4a.40`, format `adts` | one ADTS frame | + +Not mapped: `wai.video.vp8` and `wai.video.vp9` (their payload is an IVF stream, a chunk is one +frame), `wai.video.av1` (its payload carries a framing header), `wai.audio.flac` (a FLAC stream +with its `STREAMINFO`, where a chunk is one frame), `wai.video.vvc` (the WebCodecs registry does +not register VVC), `wai.video.mvhevc` (a WebCodecs HEVC access unit holds exactly one base-layer +picture), and every capability with no WebCodecs codec. + +§3.2 **One chunk.** A writer and a reader hold the payload to the form of its capability, and +refuse another with `loc_payload_form`: + +- `wai.audio.opus.packet`: at least one byte; +- `wai.video.avc`: an Annex B byte stream (after at least two zero bytes, the prefix + `0x000001`) whose NAL units each have a `forbidden_zero_bit` of 0, of which exactly one is a + VCL NAL unit (types 1 to 5) whose slice header begins with a `first_mb_in_slice` of 0; +- `wai.video.hevc`: an Annex B byte stream whose NAL units each have a `forbidden_zero_bit` of 0 + and a `nuh_layer_id` of 0, of which exactly one is a VCL NAL unit (types 0 to 31) whose + `first_slice_segment_in_pic_flag` is 1; +- `wai.audio.aac`: at least 7 bytes, the 12-bit sync word, layer 0, and a `frame_length` equal to + the payload's length. + +## 4. Writing an object + +§4.1 A publisher writes an envelope (SPEC §2) of a mapped capability as one object: + +- its **payload** is the envelope's payload, with nothing before or after it; +- its **Immutable Properties** (MOQT §10.7) hold `WAI_MANIFEST`, the envelope's manifest bytes + exactly as the envelope holds them; the LOC properties the publisher states (LOC §3.1.2 asks + for them to be immutable); and, where the publisher states a receipt, `WAI_RECEIPT`, its value + laid out as moq-streaming-format §5 lays it out, whose `content_hash` is the envelope's + (jwp-receipts §2.1). + +`WAI_MANIFEST` has type `0x3A61`, from the range MOQT §8.4 leaves to an application's own use +(odd, so its value is bytes). Like `WAI_RECEIPT`'s `0x3A41`, a subscriber reads it only on a +track that §4.2 declares. A manifest longer than 65 535 bytes, the largest value MOQT §8.3 +allows, is not written (`loc_manifest_invalid`), and neither is an envelope that is not a `WAI1` +envelope. + +§4.2 The track's catalog entry declares `packaging` `"loc"` and `codec` (MSF §5.2), and the +fields of moq-streaming-format §4: `wai_capabilities`, holding exactly the mapped capability, +and `wai_receipts` where its objects carry `WAI_RECEIPT`. A subscriber that reads WAI recognises +such a track by `wai_capabilities` on a `loc` track. + +§4.3 The LOC Timestamp and Timescale are the publisher's, and nothing in the envelope fixes +them; the receipt commits to the envelope, so it does not cover them. A subscriber that needs a +signed time for an object reads it from a claim (jwp-receipts §2.4.3). + +## 5. Reading an object + +§5.1 A subscriber reading an object of such a track, whose catalog names the capability `C`, +checks, in this order: + +1. at most one Immutable Properties, which parses and holds none of its own: + `loc_property_invalid`; +2. each LOC property of §2.2 at most once, across both property lists, with its rule: + `loc_property_invalid`; +3. `WAI_MANIFEST` and `WAI_RECEIPT` only inside the Immutable Properties, each at most once: + `loc_property_invalid`; no `WAI_MANIFEST`: `loc_manifest_absent`; +4. the manifest is a JSON object whose `wai` is of major version 1, whose + `model_requirement.capability` is a string, and whose `security.encryption` is absent, + `null` or `"none"`: `loc_manifest_invalid`; that capability is mapped (§3.1): + `loc_capability_unmapped`; and it is `C`: `loc_capability_mismatch`; +5. the payload is one chunk (§3.2): `loc_payload_form`; +6. the envelope is `WAI1`, the manifest's length as a `u32` little-endian value, the manifest, + the payload's length the same way, and the payload (SPEC §2); +7. where `WAI_RECEIPT` is present: its value reads and holds to moq-streaming-format §5's rules + on the property alone (`property_invalid`), and its `content_hash` is the envelope's + (jwp-receipts §2.1): `loc_receipt_mismatch`. + +§5.2 The envelope a reader rebuilds is then read as any envelope is (SPEC §2 to §4), and its +receipt verified as jwp-receipts §3 says, against the group receipt the property names. This +extension checks only what §5.1 lists. + +## 6. Refusals + +| code | meaning | +|---|---| +| `loc_property_invalid` | the properties do not parse, a LOC property breaks its rule or is given twice, or `WAI_MANIFEST` or `WAI_RECEIPT` is outside the Immutable Properties or given twice | +| `loc_manifest_absent` | no `WAI_MANIFEST` | +| `loc_manifest_invalid` | the manifest is not a plaintext `WAI1` manifest of major version 1 naming a capability, or (writing) the envelope is not a `WAI1` envelope or its manifest is longer than 65 535 bytes | +| `loc_capability_unmapped` | the manifest's capability is not one of §3.1 | +| `loc_capability_mismatch` | the manifest's capability is not the track's | +| `loc_payload_form` | the payload is not one chunk of its codec (§3.2) | +| `loc_receipt_mismatch` | `WAI_RECEIPT`'s `content_hash` is not the rebuilt envelope's | +| `property_invalid` | `WAI_RECEIPT` itself is refused (moq-streaming-format §5) | + +## 7. What this extension does not do + +§7.1 It does not change LOC's payload, define a LOC property beyond `WAI_MANIFEST`, or restate +MOQT's encoding of properties. It does not map a `WAI2` envelope: a multi-rendition envelope is +several payloads, and each rendition a publisher sends over LOC is its own track. + +## 8. Conformance + +§8.1 [`live-conformance/loc/vectors.json`](../live-conformance/loc/vectors.json) holds objects of +every mapped capability, each with the envelope it carries, its MOQ Object Properties as MOQT +-22 encodes them, its payload, its LOC properties and its receipt property; envelopes the writer +refuses; and objects the reader refuses, each with its code, every code of §6 given. Its HEVC +objects carry access units of `format-probe-conformance/ext/hevc/base_64x64.265`; its Opus +object a 20 ms packet of silence; its AVC and AAC objects fixed bytes in the form of §3.2, which +are not decodable. + +§8.2 `joule-moq`'s `tests/loc_vectors.rs` regenerates the corpus byte for byte and requires +every outcome; `loc_verify.py`, written from this text and sharing no code with `joule-moq`, +decodes every object's properties, rebuilds every envelope, computes every content hash with +BLAKE3 itself, requires every outcome, and requires every mutated copy of the corpus to fail. diff --git a/wai/extensions/moq-streaming-format.md b/wai/extensions/moq-streaming-format.md index b95d6cd6e..0ee8f0b79 100644 --- a/wai/extensions/moq-streaming-format.md +++ b/wai/extensions/moq-streaming-format.md @@ -525,6 +525,9 @@ Stated so it is not re-argued: paraphrase and MOQT differ, MOQT holds. - It does **not** replace CMSF or LOC. A publisher with CMAF-packaged media should use CMSF; this packaging is for tracks whose objects are WAI envelopes. + An envelope whose payload is one chunk of a WebCodecs codec may instead travel on + a track of packaging `loc`, its manifest and receipt in properties of the object + ([loc-carriage](loc-carriage.md)). - It does **not** make MoQ WAI's transport. WAI remains carrier-agnostic; this is one binding among the several `jwp-receipts` §1 anticipates. - It is not the only binding. [http-delivery](http-delivery.md) binds the same diff --git a/wai/live-conformance/README.md b/wai/live-conformance/README.md index 59444a2af..119abc38d 100644 --- a/wai/live-conformance/README.md +++ b/wai/live-conformance/README.md @@ -34,6 +34,7 @@ and the independent verifier of each, beside it. | [`exploration/`](exploration/) | [exploration](../extensions/exploration.md) (`wai.media.exploration`) | `wai_exploration_vectors gen` | `exploration_verify.py` | | [`synthesis-mark/`](synthesis-mark/) | [synthesis](../extensions/synthesis.md) §7.4 to §7.8 (the watermark scheme `wai-mark:tent64`) | `wai_synthesis_mark_vectors gen` | `synthesis_mark_verify.py` | | [`upscale/`](upscale/) | [exact-upscale](../extensions/exact-upscale.md) (`wai.media.upscale`) | `wai_upscale_vectors gen` | `upscale_verify.py` | +| [`loc/`](loc/) | [loc-carriage](../extensions/loc-carriage.md) (WAI envelopes on LOC objects) | `loc_vectors gen` (a joule-moq example) | `loc_verify.py` | **Provenance.** Generated here: each directory's corpus is written by its row's generator (`gen`, in `wai/wai-rs`), from records authored in code and from fixed seeds, and each diff --git a/wai/live-conformance/loc/README.md b/wai/live-conformance/loc/README.md new file mode 100644 index 000000000..ec88f3882 --- /dev/null +++ b/wai/live-conformance/loc/README.md @@ -0,0 +1,34 @@ +# live-conformance/loc — WAI envelopes on LOC objects + +The corpus of [`extensions/loc-carriage.md`](../../extensions/loc-carriage.md) §8, in +`vectors.json`: + +- `objects`: one LOC object per case, of every mapped capability (§3.1): the envelope it + carries (`envelope`, hexadecimal), its MOQ Object Properties as MOQT -22 encodes them + (`properties`: one Immutable Properties pair holding the LOC properties, `WAI_MANIFEST` and, + where stated, `WAI_RECEIPT`), its payload, its LOC properties (`loc`) and its receipt + property's value (`receipt`, or `null`). A conforming reader reads the object back to exactly + that envelope, those properties and that receipt, and a conforming writer writes exactly + those bytes from them. +- `writer_refused`: envelopes a writer refuses (an unmapped capability, two pictures in one + payload, an encrypted manifest, bytes that are not a `WAI1` envelope), each with its code. +- `refused`: objects a reader refuses, each a change to one written object, with its code; + every code of §6 is given by one of these or of `writer_refused`. +- `property_types`: the type of each property, as §2.2 and §4.1 give them. + +**Provenance.** Written by `cargo run -p joule-moq --example loc_vectors -- gen` (from +`joule-code/joule-code-rs`), from fixed values. The HEVC objects carry the first two access +units of [`../../format-probe-conformance/ext/hevc/base_64x64.265`](../../format-probe-conformance/ext/hevc/base_64x64.265), +a real encoder's output of a synthetic picture (its provenance is that corpus's). The Opus +object carries one 20 ms packet of silence (TOC `0xF8`). The AVC and AAC objects carry fixed +bytes in the form §3.2 checks, which are not decodable pictures or sounds. Group receipts are +named by the BLAKE3 hash of a fixed label; no signature is in the corpus. + +**Checks.** `joule-moq`'s `tests/loc_vectors.rs` regenerates the file byte for byte, requires +every outcome, and requires §6's table to list exactly the codes the reader and writer give. +`../loc_verify.py`, written from the extension's text and sharing no code with `joule-moq`, +does the same with its own MOQT -22 decoder and BLAKE3, writes every object again, and requires +every mutated copy of the corpus, and the corpus under a copy of itself without one of its +checks, to fail: + + python3 -I live-conformance/loc_verify.py live-conformance/loc diff --git a/wai/live-conformance/loc/vectors.json b/wai/live-conformance/loc/vectors.json new file mode 100644 index 000000000..2e12b76b4 --- /dev/null +++ b/wai/live-conformance/loc/vectors.json @@ -0,0 +1,239 @@ +{ + "objects": [ + { + "capability": "wai.audio.opus.packet", + "envelope": "574149317d0000007b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d03000000f8fffe", + "loc": { + "audio_config": null, + "audio_level": 127, + "timescale": 48000, + "timestamp": 960, + "video_config": null, + "video_frame_marking": null + }, + "name": "opus_unmetered", + "payload": "f8fffe", + "properties": "0b80ce08c0bb80047f0483c0ba3143aaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f0000004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec06207d7b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d", + "receipt": "aaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f0000004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec06" + }, + { + "capability": "wai.audio.opus.packet", + "envelope": "574149317d0000007b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d03000000f8fffe", + "loc": { + "audio_config": null, + "audio_level": null, + "timescale": null, + "timestamp": null, + "video_config": null, + "video_frame_marking": null + }, + "name": "opus_no_receipt", + "payload": "f8fffe", + "properties": "0b8080ba617d7b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d", + "receipt": null + }, + { + "capability": "wai.video.hevc", + "envelope": "57414931980000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e68657663222c2266616c6c6261636b223a6e756c6c7d2c22746172676574223a7b227769647468223a36342c22686569676874223a36347d7d6c0300000000000140010c11ffff01600000030000030000030000030000949056ff002000211a5c1808000003003e20000003000003023401000102807e14a4f4800000000142010101600000030000030000030000030000a0208107f96526491b61a588aa93130cbecfaf37e59f5e1446272eda8080000000014401c5a55811500a000000012601a3c2d859f8f15a33ef3e13ff97cdab22d80d8ef89f985f7a052c9540af109e7019a6b2a96a0f641b7fa559fcf1284af10d2ba8893c420a334955018fda6c2ec25a1dabd35e88d59a175f53ffc3921c52d6b1d42d221d98405c35fcda8a262fa3f2d75a08426926b76f952a5b9b9458b55db902378227da3296b9bf6cd3f60db3311972874228507b7e3c25bba727b624b2ffff6902213722de87d42e0c5c495e69e70e0d456461019692b4adbe4986149a4f1f21f477d0c574853faeeeb6f85db6d49964c67c829541145d5eb60d058f17473275d8d9667d62255451a41a2182f4258c26093ac4012ee1f05b38bfeab4b222ee4b2a506ea2898cff2fb0ce203b3787d66c857cc90e08ba47e8bffe5723c2462034ee09deaf1c776dc38c68004529feb19bd6db65b78c21c2afc772fc6c0639080fc122bc732f6a5fb3b25578e78f2400f97f2590b8ace49ec78b6f32922a10ddef55fe01e45d0676eb0567b6dc40e0e7551d6fc478acddce6563cf80234dc7b35f79e871798fd856d44c8a09878df9cc0d352801d12fbfa208ae3271e4be323248e864b744cfdb5fa3dab7c2271345b61cf44ca3ee2d0c233d12faa16dfef15fa6a75ce6f493a823d1be330516bac5f4c15636626ffaa61393c5fd6709bff461fd27e110c01a3604879af535219a7ab732a12c83ed34dc2369be796086430a3ca5312945306be4ece6ab8cc519f69e66bca74b23bf45a060b99476c26c1a5019908cf21d0bc844bb500ca2b74cb68adbad4757eac9fe6f33ab8d3771d41576de50f9e3d1eb71aa1aacea47c1bef3fd693e82686e040bb228fe117b03060f490ae55f6f41d8ee2a5575914c431c675ab456108c8252c7c2a08fb03b41b5ade444b1b2323dfb704095887e04931876171640cf6895a257e4b6c341e56aebce38109a4b197eb43cc4b6aa05e63a693ba5bc524438f514fab2b83956c5d69d08901f42ca2aec8b384f7c180b61b52324c46d727af8dc19a06bd0b2bba4c01b50e8b3716558f65f596d86b6cbee250a62f4e3cbbb5f35", + "loc": { + "audio_config": null, + "audio_level": null, + "timescale": null, + "timestamp": 1790000000000000, + "video_config": null, + "video_frame_marking": "a0" + }, + "name": "hevc_key_metered", + "payload": "0000000140010c11ffff01600000030000030000030000030000949056ff002000211a5c1808000003003e20000003000003023401000102807e14a4f4800000000142010101600000030000030000030000030000a0208107f96526491b61a588aa93130cbecfaf37e59f5e1446272eda8080000000014401c5a55811500a000000012601a3c2d859f8f15a33ef3e13ff97cdab22d80d8ef89f985f7a052c9540af109e7019a6b2a96a0f641b7fa559fcf1284af10d2ba8893c420a334955018fda6c2ec25a1dabd35e88d59a175f53ffc3921c52d6b1d42d221d98405c35fcda8a262fa3f2d75a08426926b76f952a5b9b9458b55db902378227da3296b9bf6cd3f60db3311972874228507b7e3c25bba727b624b2ffff6902213722de87d42e0c5c495e69e70e0d456461019692b4adbe4986149a4f1f21f477d0c574853faeeeb6f85db6d49964c67c829541145d5eb60d058f17473275d8d9667d62255451a41a2182f4258c26093ac4012ee1f05b38bfeab4b222ee4b2a506ea2898cff2fb0ce203b3787d66c857cc90e08ba47e8bffe5723c2462034ee09deaf1c776dc38c68004529feb19bd6db65b78c21c2afc772fc6c0639080fc122bc732f6a5fb3b25578e78f2400f97f2590b8ace49ec78b6f32922a10ddef55fe01e45d0676eb0567b6dc40e0e7551d6fc478acddce6563cf80234dc7b35f79e871798fd856d44c8a09878df9cc0d352801d12fbfa208ae3271e4be323248e864b744cfdb5fa3dab7c2271345b61cf44ca3ee2d0c233d12faa16dfef15fa6a75ce6f493a823d1be330516bac5f4c15636626ffaa61393c5fd6709bff461fd27e110c01a3604879af535219a7ab732a12c83ed34dc2369be796086430a3ca5312945306be4ece6ab8cc519f69e66bca74b23bf45a060b99476c26c1a5019908cf21d0bc844bb500ca2b74cb68adbad4757eac9fe6f33ab8d3771d41576de50f9e3d1eb71aa1aacea47c1bef3fd693e82686e040bb228fe117b03060f490ae55f6f41d8ee2a5575914c431c675ab456108c8252c7c2a08fb03b41b5ade444b1b2323dfb704095887e04931876171640cf6895a257e4b6c341e56aebce38109a4b197eb43cc4b6aa05e63a693ba5bc524438f514fab2b83956c5d69d08901f42ca2aec8b384f7c180b61b52324c46d727af8dc19a06bd0b2bba4c01b50e8b3716558f65f596d86b6cbee250a62f4e3cbbb5f35", + "properties": "0b80f60901a007fe065bfeda25e000ba314cfccf2e657e1b7a727c661052afdecf7cc2135fc1974c8c373c2a9adde8ad254cd312d00748775368756e74002cad57d4b10d779c9a0c5b6ba2b2c6b77bd2c920f1f2e7d445ac8e144f8385ca2080987b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e68657663222c2266616c6c6261636b223a6e756c6c7d2c22746172676574223a7b227769647468223a36342c22686569676874223a36347d7d", + "receipt": "fccf2e657e1b7a727c661052afdecf7cc2135fc1974c8c373c2a9adde8ad254cd312d00748775368756e74002cad57d4b10d779c9a0c5b6ba2b2c6b77bd2c920f1f2e7d445ac8e144f8385ca" + }, + { + "capability": "wai.video.hevc", + "envelope": "57414931760000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e68657663222c2266616c6c6261636b223a6e756c6c7d7d35010000000000010201c812ca1fc873f2e748a79166c2f965bffec323cd5affe56be66274ff6f76c35000f04fe2bf6a0525d7ba6642399f9a492fde633c0913a0751f2d763d4ed51af6f97ce13d3e05780acca5bca22489b43ddbe42d9e4837ca8b9ef3e528b2e6d84ec471586ce854fa9cf9094be47d97d32ad26aec0f59e661a1dc3904fc8a6e603db0b6627c4d78bfa13ad4971c3f98f9fdefda2a50c2773182acfb81bf84146935d8e9148c31952f5d0cdf78485bea2a73dfd3135499165751090b66a686804c111c96f9acdc2d4b557935f4e2fcff26fe55fe561eec29d7fc4038f0e6e2725a8ac17c03b3644e0e3b63d504aa3bb527c4c4613b9492fb462014935306f500fad87f6c374f05ab8773c4faf50f03bd2fe9d0e1a853f181d9d1c755cff19bf7db568d56ded75d9d9942d79583ca18c1c0", + "loc": { + "audio_config": null, + "audio_level": null, + "timescale": null, + "timestamp": 1790000000000000, + "video_config": "010203", + "video_frame_marking": "a0" + }, + "name": "hevc_delta", + "payload": "000000010201c812ca1fc873f2e748a79166c2f965bffec323cd5affe56be66274ff6f76c35000f04fe2bf6a0525d7ba6642399f9a492fde633c0913a0751f2d763d4ed51af6f97ce13d3e05780acca5bca22489b43ddbe42d9e4837ca8b9ef3e528b2e6d84ec471586ce854fa9cf9094be47d97d32ad26aec0f59e661a1dc3904fc8a6e603db0b6627c4d78bfa13ad4971c3f98f9fdefda2a50c2773182acfb81bf84146935d8e9148c31952f5d0cdf78485bea2a73dfd3135499165751090b66a686804c111c96f9acdc2d4b557935f4e2fcff26fe55fe561eec29d7fc4038f0e6e2725a8ac17c03b3644e0e3b63d504aa3bb527c4c4613b9492fb462014935306f500fad87f6c374f05ab8773c4faf50f03bd2fe9d0e1a853f181d9d1c755cff19bf7db568d56ded75d9d9942d79583ca18c1c0", + "properties": "0b80cf0901a0040301020303fe065bfeda25e000ba314343caea18ff2a9eadc60b3a9356dc1a8d2187f796de3802c749cbc5a44e835c6d0000002cad57d4b10d779c9a0c5b6ba2b2c6b77bd2c920f1f2e7d445ac8e144f8385ca20767b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e68657663222c2266616c6c6261636b223a6e756c6c7d7d", + "receipt": "43caea18ff2a9eadc60b3a9356dc1a8d2187f796de3802c749cbc5a44e835c6d0000002cad57d4b10d779c9a0c5b6ba2b2c6b77bd2c920f1f2e7d445ac8e144f8385ca" + }, + { + "capability": "wai.audio.aac", + "envelope": "57414931750000007b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e616163222c2266616c6c6261636b223a6e756c6c7d7d0d000000fff14c8001bffc211004608c1c", + "loc": { + "audio_config": "1190", + "audio_level": 127, + "timescale": 48000, + "timestamp": 960, + "video_config": null, + "video_frame_marking": null + }, + "name": "aac_frame", + "payload": "fff14c8001bffc211004608c1c", + "properties": "0b808508c0bb80047f030211900183c0ba51757b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e616163222c2266616c6c6261636b223a6e756c6c7d7d", + "receipt": null + }, + { + "capability": "wai.video.avc", + "envelope": "57414931750000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e617663222c2266616c6c6261636b223a6e756c6c7d7d1d000000000000016742c00a8c8d400000000168ce3c80000000016588840033ff", + "loc": { + "audio_config": null, + "audio_level": null, + "timescale": 90000, + "timestamp": 0, + "video_config": null, + "video_frame_marking": null + }, + "name": "avc_access_unit", + "payload": "000000016742c00a8c8d400000000168ce3c80000000016588840033ff", + "properties": "0b7e08c15f900800ba51757b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e617663222c2266616c6c6261636b223a6e756c6c7d7d", + "receipt": null + } + ], + "property_types": { + "audio_config": 15, + "audio_level": 12, + "timescale": 8, + "timestamp": 16, + "video_config": 13, + "video_frame_marking": 9, + "wai_manifest": 14945, + "wai_receipt": 14913 + }, + "refused": [ + { + "capability": "wai.audio.opus.packet", + "name": "manifest_absent", + "payload": "f8fffe", + "properties": "0b4f08c0bb80047f0483c0ba3143aaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f0000004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec06", + "refuse": "loc_manifest_absent" + }, + { + "capability": "wai.audio.opus.packet", + "name": "manifest_twice", + "payload": "f8fffe", + "properties": "0b814d08c0bb80047f0483c0ba3143aaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f0000004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec06207d7b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d007d7b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d", + "refuse": "loc_property_invalid" + }, + { + "capability": "wai.audio.opus.packet", + "name": "manifest_not_json", + "payload": "f8fffe", + "properties": "0b5808c0bb80047f0483c0ba3143aaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f0000004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec0620077b22776169223a", + "refuse": "loc_manifest_invalid" + }, + { + "capability": "wai.audio.opus.packet", + "name": "manifest_major_2", + "payload": "f8fffe", + "properties": "0b80ce08c0bb80047f0483c0ba3143aaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f0000004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec06207d7b22776169223a22322e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d", + "refuse": "loc_manifest_invalid" + }, + { + "capability": "wai.audio.opus.packet", + "name": "timescale_zero", + "payload": "f8fffe", + "properties": "0b80cc0800047f0483c0ba3143aaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f0000004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec06207d7b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d", + "refuse": "loc_property_invalid" + }, + { + "capability": "wai.audio.opus.packet", + "name": "audio_level_256", + "payload": "f8fffe", + "properties": "0b80cf08c0bb800481000483c0ba3143aaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f0000004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec06207d7b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d", + "refuse": "loc_property_invalid" + }, + { + "capability": "wai.audio.opus.packet", + "name": "timestamp_twice", + "payload": "f8fffe", + "properties": "0b80ce08c0bb80047f0483c0ba3143aaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f0000004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec06207d7b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d0505", + "refuse": "loc_property_invalid" + }, + { + "capability": "wai.audio.opus.packet", + "name": "manifest_outside_immutable", + "payload": "f8fffe", + "properties": "0b4f08c0bb80047f0483c0ba3143aaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f0000004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec06ba567d7b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d", + "refuse": "loc_property_invalid" + }, + { + "capability": "wai.audio.aac", + "name": "track_carries_aac", + "payload": "f8fffe", + "properties": "0b80ce08c0bb80047f0483c0ba3143aaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f0000004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec06207d7b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d", + "refuse": "loc_capability_mismatch" + }, + { + "capability": "wai.audio.opus.packet", + "name": "payload_empty", + "payload": "", + "properties": "0b80ce08c0bb80047f0483c0ba3143aaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f0000004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec06207d7b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d", + "refuse": "loc_payload_form" + }, + { + "capability": "wai.audio.opus.packet", + "name": "payload_changed", + "payload": "f8fffd", + "properties": "0b80ce08c0bb80047f0483c0ba3143aaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f0000004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec06207d7b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d", + "refuse": "loc_receipt_mismatch" + }, + { + "capability": "wai.video.hevc", + "name": "receipt_cut_short", + "payload": "0000000140010c11ffff01600000030000030000030000030000949056ff002000211a5c1808000003003e20000003000003023401000102807e14a4f4800000000142010101600000030000030000030000030000a0208107f96526491b61a588aa93130cbecfaf37e59f5e1446272eda8080000000014401c5a55811500a000000012601a3c2d859f8f15a33ef3e13ff97cdab22d80d8ef89f985f7a052c9540af109e7019a6b2a96a0f641b7fa559fcf1284af10d2ba8893c420a334955018fda6c2ec25a1dabd35e88d59a175f53ffc3921c52d6b1d42d221d98405c35fcda8a262fa3f2d75a08426926b76f952a5b9b9458b55db902378227da3296b9bf6cd3f60db3311972874228507b7e3c25bba727b624b2ffff6902213722de87d42e0c5c495e69e70e0d456461019692b4adbe4986149a4f1f21f477d0c574853faeeeb6f85db6d49964c67c829541145d5eb60d058f17473275d8d9667d62255451a41a2182f4258c26093ac4012ee1f05b38bfeab4b222ee4b2a506ea2898cff2fb0ce203b3787d66c857cc90e08ba47e8bffe5723c2462034ee09deaf1c776dc38c68004529feb19bd6db65b78c21c2afc772fc6c0639080fc122bc732f6a5fb3b25578e78f2400f97f2590b8ace49ec78b6f32922a10ddef55fe01e45d0676eb0567b6dc40e0e7551d6fc478acddce6563cf80234dc7b35f79e871798fd856d44c8a09878df9cc0d352801d12fbfa208ae3271e4be323248e864b744cfdb5fa3dab7c2271345b61cf44ca3ee2d0c233d12faa16dfef15fa6a75ce6f493a823d1be330516bac5f4c15636626ffaa61393c5fd6709bff461fd27e110c01a3604879af535219a7ab732a12c83ed34dc2369be796086430a3ca5312945306be4ece6ab8cc519f69e66bca74b23bf45a060b99476c26c1a5019908cf21d0bc844bb500ca2b74cb68adbad4757eac9fe6f33ab8d3771d41576de50f9e3d1eb71aa1aacea47c1bef3fd693e82686e040bb228fe117b03060f490ae55f6f41d8ee2a5575914c431c675ab456108c8252c7c2a08fb03b41b5ade444b1b2323dfb704095887e04931876171640cf6895a257e4b6c341e56aebce38109a4b197eb43cc4b6aa05e63a693ba5bc524438f514fab2b83956c5d69d08901f42ca2aec8b384f7c180b61b52324c46d727af8dc19a06bd0b2bba4c01b50e8b3716558f65f596d86b6cbee250a62f4e3cbbb5f35", + "properties": "0b80d20901a007fe065bfeda25e000ba3128fccf2e657e1b7a727c661052afdecf7cc2135fc1974c8c373c2a9adde8ad254cd312d007487753682080987b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e68657663222c2266616c6c6261636b223a6e756c6c7d2c22746172676574223a7b227769647468223a36342c22686569676874223a36347d7d", + "refuse": "property_invalid" + }, + { + "capability": "wai.audio.opus.packet", + "name": "receipt_class_beside_zero", + "payload": "f8fffe", + "properties": "0b80d508c0bb80047f0483c0ba314aaaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f000748775368756e74004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec06207d7b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d7d", + "refuse": "property_invalid" + }, + { + "capability": "wai.video.hevc", + "name": "hevc_second_layer", + "payload": "0000000140010c11ffff01600000030000030000030000030000949056ff002000211a5c1808000003003e20000003000003023401000102807e14a4f4800000000142010101600000030000030000030000030000a0208107f96526491b61a588aa93130cbecfaf37e59f5e1446272eda8080000000014401c5a55811500a000000012601a3c2d859f8f15a33ef3e13ff97cdab22d80d8ef89f985f7a052c9540af109e7019a6b2a96a0f641b7fa559fcf1284af10d2ba8893c420a334955018fda6c2ec25a1dabd35e88d59a175f53ffc3921c52d6b1d42d221d98405c35fcda8a262fa3f2d75a08426926b76f952a5b9b9458b55db902378227da3296b9bf6cd3f60db3311972874228507b7e3c25bba727b624b2ffff6902213722de87d42e0c5c495e69e70e0d456461019692b4adbe4986149a4f1f21f477d0c574853faeeeb6f85db6d49964c67c829541145d5eb60d058f17473275d8d9667d62255451a41a2182f4258c26093ac4012ee1f05b38bfeab4b222ee4b2a506ea2898cff2fb0ce203b3787d66c857cc90e08ba47e8bffe5723c2462034ee09deaf1c776dc38c68004529feb19bd6db65b78c21c2afc772fc6c0639080fc122bc732f6a5fb3b25578e78f2400f97f2590b8ace49ec78b6f32922a10ddef55fe01e45d0676eb0567b6dc40e0e7551d6fc478acddce6563cf80234dc7b35f79e871798fd856d44c8a09878df9cc0d352801d12fbfa208ae3271e4be323248e864b744cfdb5fa3dab7c2271345b61cf44ca3ee2d0c233d12faa16dfef15fa6a75ce6f493a823d1be330516bac5f4c15636626ffaa61393c5fd6709bff461fd27e110c01a3604879af535219a7ab732a12c83ed34dc2369be796086430a3ca5312945306be4ece6ab8cc519f69e66bca74b23bf45a060b99476c26c1a5019908cf21d0bc844bb500ca2b74cb68adbad4757eac9fe6f33ab8d3771d41576de50f9e3d1eb71aa1aacea47c1bef3fd693e82686e040bb228fe117b03060f490ae55f6f41d8ee2a5575914c431c675ab456108c8252c7c2a08fb03b41b5ade444b1b2323dfb704095887e04931876171640cf6895a257e4b6c341e56aebce38109a4b197eb43cc4b6aa05e63a693ba5bc524438f514fab2b83956c5d69d08901f42ca2aec8b384f7c180b61b52324c46d727af8dc19a06bd0b2bba4c01b50e8b3716558f65f596d86b6cbee250a62f4e3cbbb5f35000000010209aa", + "properties": "0b80f60901a007fe065bfeda25e000ba314cfccf2e657e1b7a727c661052afdecf7cc2135fc1974c8c373c2a9adde8ad254cd312d00748775368756e74002cad57d4b10d779c9a0c5b6ba2b2c6b77bd2c920f1f2e7d445ac8e144f8385ca2080987b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e68657663222c2266616c6c6261636b223a6e756c6c7d2c22746172676574223a7b227769647468223a36342c22686569676874223a36347d7d", + "refuse": "loc_payload_form" + }, + { + "capability": "wai.video.vp9", + "name": "manifest_names_vp9", + "payload": "f8fffe", + "properties": "0b80c608c0bb80047f0483c0ba3143aaad47e67bfe1e850a0acb285f7561833b32481bafd0ac9161a1a12d06245b9f0000004a875780d54f4d0c8dc9d4513e8f48581eab3431cd7870db3ec0f602ab64ec0620757b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e767039222c2266616c6c6261636b223a6e756c6c7d7d", + "refuse": "loc_capability_unmapped" + } + ], + "writer_refused": [ + { + "envelope": "57414931750000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e767039222c2266616c6c6261636b223a6e756c6c7d7d04000000444b4946", + "name": "unmapped_vp9", + "refuse": "loc_capability_unmapped" + }, + { + "envelope": "57414931760000007b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e68657663222c2266616c6c6261636b223a6e756c6c7d7da10400000000000140010c11ffff01600000030000030000030000030000949056ff002000211a5c1808000003003e20000003000003023401000102807e14a4f4800000000142010101600000030000030000030000030000a0208107f96526491b61a588aa93130cbecfaf37e59f5e1446272eda8080000000014401c5a55811500a000000012601a3c2d859f8f15a33ef3e13ff97cdab22d80d8ef89f985f7a052c9540af109e7019a6b2a96a0f641b7fa559fcf1284af10d2ba8893c420a334955018fda6c2ec25a1dabd35e88d59a175f53ffc3921c52d6b1d42d221d98405c35fcda8a262fa3f2d75a08426926b76f952a5b9b9458b55db902378227da3296b9bf6cd3f60db3311972874228507b7e3c25bba727b624b2ffff6902213722de87d42e0c5c495e69e70e0d456461019692b4adbe4986149a4f1f21f477d0c574853faeeeb6f85db6d49964c67c829541145d5eb60d058f17473275d8d9667d62255451a41a2182f4258c26093ac4012ee1f05b38bfeab4b222ee4b2a506ea2898cff2fb0ce203b3787d66c857cc90e08ba47e8bffe5723c2462034ee09deaf1c776dc38c68004529feb19bd6db65b78c21c2afc772fc6c0639080fc122bc732f6a5fb3b25578e78f2400f97f2590b8ace49ec78b6f32922a10ddef55fe01e45d0676eb0567b6dc40e0e7551d6fc478acddce6563cf80234dc7b35f79e871798fd856d44c8a09878df9cc0d352801d12fbfa208ae3271e4be323248e864b744cfdb5fa3dab7c2271345b61cf44ca3ee2d0c233d12faa16dfef15fa6a75ce6f493a823d1be330516bac5f4c15636626ffaa61393c5fd6709bff461fd27e110c01a3604879af535219a7ab732a12c83ed34dc2369be796086430a3ca5312945306be4ece6ab8cc519f69e66bca74b23bf45a060b99476c26c1a5019908cf21d0bc844bb500ca2b74cb68adbad4757eac9fe6f33ab8d3771d41576de50f9e3d1eb71aa1aacea47c1bef3fd693e82686e040bb228fe117b03060f490ae55f6f41d8ee2a5575914c431c675ab456108c8252c7c2a08fb03b41b5ade444b1b2323dfb704095887e04931876171640cf6895a257e4b6c341e56aebce38109a4b197eb43cc4b6aa05e63a693ba5bc524438f514fab2b83956c5d69d08901f42ca2aec8b384f7c180b61b52324c46d727af8dc19a06bd0b2bba4c01b50e8b3716558f65f596d86b6cbee250a62f4e3cbbb5f35000000010201c812ca1fc873f2e748a79166c2f965bffec323cd5affe56be66274ff6f76c35000f04fe2bf6a0525d7ba6642399f9a492fde633c0913a0751f2d763d4ed51af6f97ce13d3e05780acca5bca22489b43ddbe42d9e4837ca8b9ef3e528b2e6d84ec471586ce854fa9cf9094be47d97d32ad26aec0f59e661a1dc3904fc8a6e603db0b6627c4d78bfa13ad4971c3f98f9fdefda2a50c2773182acfb81bf84146935d8e9148c31952f5d0cdf78485bea2a73dfd3135499165751090b66a686804c111c96f9acdc2d4b557935f4e2fcff26fe55fe561eec29d7fc4038f0e6e2725a8ac17c03b3644e0e3b63d504aa3bb527c4c4613b9492fb462014935306f500fad87f6c374f05ab8773c4faf50f03bd2fe9d0e1a853f181d9d1c755cff19bf7db568d56ded75d9d9942d79583ca18c1c0", + "name": "two_pictures", + "refuse": "loc_payload_form" + }, + { + "envelope": "57414931a50000007b22776169223a22312e30222c226d65646961223a22617564696f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e617564696f2e6f7075732e7061636b6574222c2266616c6c6261636b223a6e756c6c7d2c227365637572697479223a7b22656e6372797074696f6e223a226d6c73222c226b6964223a377d7d03000000f8fffe", + "name": "encrypted_manifest", + "refuse": "loc_manifest_invalid" + }, + { + "envelope": "5741493200000000", + "name": "not_an_envelope", + "refuse": "loc_manifest_invalid" + } + ] +} diff --git a/wai/live-conformance/loc_verify.py b/wai/live-conformance/loc_verify.py new file mode 100644 index 000000000..9c675c7f0 --- /dev/null +++ b/wai/live-conformance/loc_verify.py @@ -0,0 +1,367 @@ +"""The second implementation of LOC carriage (extensions/loc-carriage.md), written from that +text, moq-streaming-format §5, jwp-receipts §2.1 and MOQT -22's encoding of properties, sharing +no code with joule-moq. + + python3 -I loc_verify.py + +For every object of vectors.json it decodes the MOQ Object Properties, reads the object as +loc-carriage §5.1 says, requires the envelope, LOC properties and receipt property stated, and +writes the object again from them as §4.1 says, requiring the same bytes; it requires every +refused object and envelope to be refused with its code, every code of §6's table given, and +the codes to be §6's; then it requires every mutated copy of the corpus, and the corpus under a +copy of this verifier with one of its own checks dropped, to fail. Content hashes are BLAKE3, +computed with ../claim-conformance/_blake3.py. Standard library only. +""" + +import copy +import json +import os +import re +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance")) +import _blake3 as B3 # noqa: E402 + +IMMUTABLE = 0x0B +TIMESCALE, FRAME_MARKING, AUDIO_LEVEL, VIDEO_CONFIG, AUDIO_CONFIG, TIMESTAMP = 0x08, 0x09, 0x0C, 0x0D, 0x0F, 0x10 +WAI_MANIFEST, WAI_RECEIPT = 0x3A61, 0x3A41 +MAPPED = ("wai.audio.opus.packet", "wai.video.avc", "wai.video.hevc", "wai.audio.aac") +DROPPED = set() # checks a mutated verifier leaves out + + +class Refuse(Exception): + def __init__(self, code): + Exception.__init__(self, code) + self.code = code + + +# ---- MOQT -22: the variable-length integer (§8.1) and Key-Value-Pairs (§8.3) ------- + +def get_varint(b, i): + if i >= len(b): + raise ValueError("truncated") + first = b[i] + ones = 0 + while ones < 8 and first & (0x80 >> ones): + ones += 1 + n = 9 if ones >= 8 else ones + 1 + if i + n > len(b): + raise ValueError("truncated") + if n == 9: + return int.from_bytes(b[i + 1:i + 9], "big"), i + 9 + v = first & ((0xFF >> n) if n > 1 else 0x7F) + for x in b[i + 1:i + n]: + v = (v << 8) | x + return v, i + n + + +def put_varint(v): + for n in range(1, 9): + bits = 56 if n == 8 else 7 * n + if v < (1 << bits): + prefix = 0 if n == 1 else (0xFF << (8 - (n - 1))) & 0xFF + raw = v.to_bytes(n, "big") + return bytes([prefix | raw[0]]) + raw[1:] + return b"\xff" + v.to_bytes(8, "big") + + +def decode_pairs(b): + out, i, prev = [], 0, 0 + while i < len(b): + d, i = get_varint(b, i) + t = prev + d + prev = t + if t % 2 == 0: + v, i = get_varint(b, i) + else: + n, i = get_varint(b, i) + if n > 0xFFFF or i + n > len(b): + raise ValueError("a value past its bound") + v, i = bytes(b[i:i + n]), i + n + out.append((t, v)) + return out + + +def encode_pairs(pairs): + out, prev = b"", 0 + for t, v in sorted(pairs, key=lambda p: p[0]): + out += put_varint(t - prev) + prev = t + out += put_varint(v) if t % 2 == 0 else put_varint(len(v)) + v + return out + + +# ---- loc-carriage §3.2: one chunk -------------------------------------------------- + +def nal_units(b): + i = 0 + while i < len(b) and b[i] == 0: + i += 1 + if i < 2 or i >= len(b) or b[i] != 1: + return None + starts, j = [], 0 + while j + 2 < len(b): + if b[j] == 0 and b[j + 1] == 0 and b[j + 2] == 1: + starts.append(j + 3) + j += 3 + else: + j += 1 + out = [] + for k, s in enumerate(starts): + e = starts[k + 1] - 3 if k + 1 < len(starts) else len(b) + while e > s and b[e - 1] == 0: + e -= 1 + out.append(b[s:e]) + return out + + +def one_chunk(cap, p): + if cap == "wai.audio.opus.packet": + return len(p) >= 1 + if cap == "wai.video.avc": + us = nal_units(p) + return us is not None and all(u and not u[0] & 0x80 for u in us) and \ + sum(1 for u in us if 1 <= (u[0] & 0x1F) <= 5 and len(u) > 1 and u[1] & 0x80) == 1 + if cap == "wai.video.hevc": + us = nal_units(p) + return us is not None and all(len(u) >= 2 and not u[0] & 0x80 and ((u[0] & 1) << 5 | u[1] >> 3) == 0 for u in us) and \ + sum(1 for u in us if len(u) >= 3 and (u[0] >> 1) & 0x3F < 32 and u[2] & 0x80) == 1 + if cap == "wai.audio.aac": + return len(p) >= 7 and p[0] == 0xFF and p[1] >> 4 == 0xF and (p[1] >> 1) & 3 == 0 and \ + ((p[3] & 3) << 11 | p[4] << 3 | p[5] >> 5) == len(p) + raise Refuse("loc_capability_unmapped") + + +def manifest_capability(m): + try: + v = json.loads(m.decode("utf-8")) + except (ValueError, UnicodeDecodeError): + raise Refuse("loc_manifest_invalid") + if not isinstance(v, dict): + raise Refuse("loc_manifest_invalid") + wai = v.get("wai") + cap = (v.get("model_requirement") or {}).get("capability") if isinstance(v.get("model_requirement"), dict) else None + sec = v.get("security") + enc = sec.get("encryption") if isinstance(sec, dict) else None + if not (isinstance(wai, str) and wai.split(".")[0] == "1") or not isinstance(cap, str) or enc not in (None, "none"): + raise Refuse("loc_manifest_invalid") + return cap + + +def envelope(m, p): + return b"WAI1" + len(m).to_bytes(4, "little") + m + len(p).to_bytes(4, "little") + p + + +def content_hash(env): + return B3.blake3(b"moq-jwp:object\x01" + env) + + +def read_receipt(b): + try: + if len(b) < 32: + raise ValueError + h, i = b[:32], 32 + joules, i = get_varint(b, i) + n, i = get_varint(b, i) + acq = b[i:i + n].decode("utf-8") + if i + n > len(b): + raise ValueError + i += n + n, i = get_varint(b, i) + if i + n > len(b): + raise ValueError + ev, i = b[i:i + n], i + n + if len(b) != i + 32: + raise ValueError + group = b[i:i + 32] + except (ValueError, UnicodeDecodeError): + raise Refuse("property_invalid") + # moq-streaming-format §5's rules on the property alone, for the classes this corpus uses. + if joules == 0: + ok = acq == "" and ev == b"" + elif acq == "HwShunt": + ok = ev == b"" + else: + raise RuntimeError("a class this verifier does not read: %r" % acq) + if not ok: + raise Refuse("property_invalid") + return {"content_hash": h, "joules": joules, "acquisition": acq, "evidence": ev, "group": group} + + +# ---- loc-carriage §5.1: reading ------------------------------------------------------ + +LOC_RULES = { + TIMESTAMP: lambda v: isinstance(v, int), + TIMESCALE: lambda v: isinstance(v, int) and ("timescale" in DROPPED or v != 0), + FRAME_MARKING: lambda v: isinstance(v, bytes) and 1 <= len(v) <= 4, + AUDIO_LEVEL: lambda v: isinstance(v, int) and v <= 255, + VIDEO_CONFIG: lambda v: isinstance(v, bytes) and len(v) > 0, + AUDIO_CONFIG: lambda v: isinstance(v, bytes) and len(v) > 0, +} + + +def read_object(props_bytes, payload, track_cap): + try: + outer = decode_pairs(props_bytes) + imm = [v for t, v in outer if t == IMMUTABLE] + if len(imm) > 1: + raise ValueError + inner = decode_pairs(imm[0]) if imm else [] + if any(t == IMMUTABLE for t, _ in inner): + raise ValueError + except ValueError: + raise Refuse("loc_property_invalid") + mutable = [(t, v) for t, v in outer if t != IMMUTABLE] + allp = mutable + inner + loc = {} + for t, rule in LOC_RULES.items(): + vs = [v for tt, v in allp if tt == t] + if len(vs) > 1 or (vs and not rule(vs[0])): + raise Refuse("loc_property_invalid") + loc[t] = vs[0] if vs else None + for t in (WAI_MANIFEST, WAI_RECEIPT): + if any(tt == t for tt, _ in mutable) or sum(1 for tt, _ in inner if tt == t) > 1: + raise Refuse("loc_property_invalid") + mans = [v for t, v in inner if t == WAI_MANIFEST] + if not mans: + raise Refuse("loc_manifest_absent") + cap = manifest_capability(mans[0]) + if cap not in MAPPED: + raise Refuse("loc_capability_unmapped") + if cap != track_cap: + raise Refuse("loc_capability_mismatch") + if not one_chunk(cap, payload): + raise Refuse("loc_payload_form") + env = envelope(mans[0], payload) + rcs = [v for t, v in inner if t == WAI_RECEIPT] + receipt = None + if rcs: + receipt = read_receipt(rcs[0]) + if receipt["content_hash"] != content_hash(env): + raise Refuse("loc_receipt_mismatch") + receipt = rcs[0] + return env, loc, receipt + + +# ---- loc-carriage §4.1: writing ------------------------------------------------------ + +def write_object(env, loc, receipt): + if len(env) < 8 or env[:4] != b"WAI1": + raise Refuse("loc_manifest_invalid") + ml = int.from_bytes(env[4:8], "little") + m = env[8:8 + ml] + pl = int.from_bytes(env[8 + ml:12 + ml], "little") + p = env[12 + ml:12 + ml + pl] + if len(m) != ml or len(p) != pl or len(env) != 12 + ml + pl or ml > 0xFFFF: + raise Refuse("loc_manifest_invalid") + cap = manifest_capability(m) + if not one_chunk(cap, p): + raise Refuse("loc_payload_form") + for t, v in loc.items(): + if v is not None and not LOC_RULES[t](v): + raise Refuse("loc_property_invalid") + inner = [(t, v) for t, v in loc.items() if v is not None] + [(WAI_MANIFEST, m)] + if receipt is not None: + if read_receipt(receipt)["content_hash"] != content_hash(env): + raise Refuse("loc_receipt_mismatch") + inner.append((WAI_RECEIPT, receipt)) + return encode_pairs([(IMMUTABLE, encode_pairs(inner))]), p + + +def stated_loc(o): + s = o["loc"] + hx = lambda v: bytes.fromhex(v) if v is not None else None + return {TIMESTAMP: s["timestamp"], TIMESCALE: s["timescale"], FRAME_MARKING: hx(s["video_frame_marking"]), + AUDIO_LEVEL: s["audio_level"], VIDEO_CONFIG: hx(s["video_config"]), AUDIO_CONFIG: hx(s["audio_config"])} + + +def check(doc): + fails, given = [], set() + for o in doc["objects"]: + env, props, payload = bytes.fromhex(o["envelope"]), bytes.fromhex(o["properties"]), bytes.fromhex(o["payload"]) + rc = bytes.fromhex(o["receipt"]) if o["receipt"] else None + try: + got = read_object(props, payload, o["capability"]) + if got != (env, stated_loc(o), rc): + fails.append("%s: reads back to another envelope, LOC properties or receipt" % o["name"]) + except Refuse as e: + fails.append("%s: refused %s" % (o["name"], e.code)) + try: + if write_object(env, stated_loc(o), rc) != (props, payload): + fails.append("%s: written otherwise" % o["name"]) + except Refuse as e: + fails.append("%s: the writer refuses it (%s)" % (o["name"], e.code)) + for c in doc["writer_refused"]: + try: + write_object(bytes.fromhex(c["envelope"]), {t: None for t in LOC_RULES}, None) + got = None + except Refuse as e: + got = e.code + given.add(got) + if got != c["refuse"]: + fails.append("writer %s: %s, stated %s" % (c["name"], got, c["refuse"])) + for c in doc["refused"]: + try: + read_object(bytes.fromhex(c["properties"]), bytes.fromhex(c["payload"]), c["capability"]) + got = None + except Refuse as e: + got = e.code + given.add(got) + if got != c["refuse"]: + fails.append("reader %s: %s, stated %s" % (c["name"], got, c["refuse"])) + text = open(os.path.join(HERE, "..", "extensions", "loc-carriage.md"), encoding="utf-8").read() + at = text.index("\n## 6. Refusals") + codes = re.findall(r"^\| `([a-z_]+)` \|", text[at:text.index("\n## ", at + 1)], re.M) + for c in codes: + if c not in given: + fails.append("no vector is refused with %s" % c) + if set(given) - set(codes): + fails.append("codes given that §6 does not list: %s" % sorted(set(given) - set(codes))) + return fails + + +def main(argv): + if len(argv) != 2: + print(__doc__) + return 2 + doc = json.load(open(os.path.join(argv[1], "vectors.json"), encoding="utf-8")) + fails = check(doc) + for f in fails: + print("FAIL", f) + print("%d objects, %d refused envelopes, %d refused objects: %d failures" % (len(doc["objects"]), len(doc["writer_refused"]), len(doc["refused"]), len(fails))) + + def flip(h, i): + b = bytearray(bytes.fromhex(h)) + b[i] ^= 1 + return b.hex() + + muts = [] + + def mutate(label, f): + d = copy.deepcopy(doc) + f(d) + muts.append((label, d)) + + obj = lambda d, n: next(o for o in d["objects"] if o["name"] == n) + mutate("a byte of a payload", lambda d: obj(d, "opus_unmetered").__setitem__("payload", flip(obj(d, "opus_unmetered")["payload"], 2))) + mutate("a byte of the manifest in the properties", lambda d: obj(d, "hevc_key_metered").__setitem__("properties", flip(obj(d, "hevc_key_metered")["properties"], 40))) + mutate("a stated timestamp", lambda d: obj(d, "opus_unmetered")["loc"].__setitem__("timestamp", 961)) + mutate("a stated receipt's figure", lambda d: obj(d, "hevc_key_metered").__setitem__("receipt", flip(obj(d, "hevc_key_metered")["receipt"], 33))) + mutate("a refusal stated with another code", lambda d: d["refused"][0].__setitem__("refuse", "loc_property_invalid")) + mutate("the track's capability stated otherwise", lambda d: obj(d, "aac_frame").__setitem__("capability", "wai.audio.opus.packet")) + ok = True + for label, d in muts: + caught = check(d) + print(("PASS" if caught else "FAIL") + " mutated copy fails: " + label) + ok &= bool(caught) + DROPPED.add("timescale") + caught = check(doc) + DROPPED.clear() + print(("PASS" if caught else "FAIL") + " the corpus fails a verifier without the Timescale rule") + ok &= bool(caught) + return 0 if not fails and ok else 1 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/wai/registry/capabilities.json b/wai/registry/capabilities.json index 16e00d251..46fbc3e81 100644 --- a/wai/registry/capabilities.json +++ b/wai/registry/capabilities.json @@ -2249,6 +2249,13 @@ "section": "§4.4", "status": "defined" }, + { + "name": "WAI_MANIFEST", + "kind": "catalog", + "document": "extensions/loc-carriage.md", + "section": "§4", + "status": "defined" + }, { "name": "WAI_RATING", "kind": "catalog", diff --git a/wai/wai-rs/src/codecs/registry.rs b/wai/wai-rs/src/codecs/registry.rs index 6c782fd24..4e43beba8 100644 --- a/wai/wai-rs/src/codecs/registry.rs +++ b/wai/wai-rs/src/codecs/registry.rs @@ -595,6 +595,7 @@ const EXPLORATION: &str = "extensions/exploration.md"; const EXACT_UPSCALE: &str = "extensions/exact-upscale.md"; const DELIVERY_TELEMETRY: &str = "extensions/delivery-telemetry.md"; const PROJECTION: &str = "extensions/projection.md"; +const LOC_CARRIAGE: &str = "extensions/loc-carriage.md"; const fn wire(name: &'static str, kind: WireKind, document: &'static str, section: &'static str, status: WireStatus) -> WireIdentifier { @@ -790,6 +791,9 @@ pub const WIRE_IDENTIFIERS: &[WireIdentifier] = &[ wire("science.transaction.wai-op", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), wire("science.transaction.wai-drop", WireKind::TelemetryKey, DELIVERY_TELEMETRY, "§10", WireStatus::Defined), wire("WPJ1", WireKind::Magic, PROJECTION, "§3", WireStatus::Defined), + // An envelope's manifest as an Immutable Property of a LOC object, type 0x3A61, in the + // range MOQT §8.4 leaves to applications. + wire("WAI_MANIFEST", WireKind::Catalog, LOC_CARRIAGE, "§4", WireStatus::Defined), ]; /// A string with the shape of a capability that the reference implementation uses -- 2.54.0 (Apple Git-157) From 0457436807d0dbeded51bed1ca8c30d430845bd2 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 10:43:34 -0400 Subject: [PATCH 020/242] =?UTF-8?q?format-probes:=20cite=20moq-streaming-f?= =?UTF-8?q?ormat=20=C2=A76=20for=20WAI=20defining=20no=20transport?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- wai/extensions/format-probes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wai/extensions/format-probes.md b/wai/extensions/format-probes.md index c5b994c07..2e5ade1a4 100644 --- a/wai/extensions/format-probes.md +++ b/wai/extensions/format-probes.md @@ -185,7 +185,7 @@ a `wai.video.jxs` payload over RFC 9134: frame's byte count. A receiver rebuilds the payload by framing each received codestream as §3.3 does, `fps` from -the session's `exactframerate`. WAI defines no transport (moq-streaming-format §0); this +the session's `exactframerate`. WAI defines no transport (moq-streaming-format §6); this mapping says which of a payload's fields fill which of RFC 9134's. §3.6 **Exactness.** A sink that advertises `wai.image.jxs` or `wai.video.jxs` decodes at the -- 2.54.0 (Apple Git-157) From 3ee1318540490fe48fec510ea9410af8095c9e96 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 10:46:24 -0400 Subject: [PATCH 021/242] CI: the header probes, CMSD reading and LOC carriage; the changelog wai-conformance: the probe unit tests on every leg and the feature built alone and for wasm32; a step running tests/format_probe.rs, the fetched LCEVC streams under wai_format_probe and format_probe_verify.py; steps for cmsd_read_verify.py and loc_verify.py; the new extensions in its paths. joule-code-conformance: the LOC corpus and the H.265 vector it reads in its paths. SPEC cites format-probes by its headed section. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/joule-code-conformance.yml | 10 ++++ .github/workflows/wai-conformance.yml | 53 +++++++++++++++++++- wai/CHANGELOG.md | 50 ++++++++++++++++++ wai/SPEC.md | 4 +- wai/extensions/projection.md | 2 +- wai/wai-rs/Cargo.toml | 5 ++ 6 files changed, 119 insertions(+), 5 deletions(-) diff --git a/.github/workflows/joule-code-conformance.yml b/.github/workflows/joule-code-conformance.yml index 5e53fe191..d7de7e678 100644 --- a/.github/workflows/joule-code-conformance.yml +++ b/.github/workflows/joule-code-conformance.yml @@ -46,6 +46,11 @@ on: # joule-moq's tests/rating_vectors.rs reads every rating claim and serving request of # the age-assurance corpus, and the relay's ratings are held to the same reading. - 'wai/live-conformance/age/**' + # joule-moq's tests/loc_vectors.rs regenerates and checks the LOC carriage corpus, from + # access units of the H.265 vector it reads, and requires loc-carriage §6's table. + - 'wai/live-conformance/loc/**' + - 'wai/format-probe-conformance/ext/hevc/**' + - 'wai/extensions/loc-carriage.md' - '.github/workflows/joule-code-conformance.yml' push: branches: @@ -69,6 +74,11 @@ on: # joule-moq's tests/rating_vectors.rs reads every rating claim and serving request of # the age-assurance corpus, and the relay's ratings are held to the same reading. - 'wai/live-conformance/age/**' + # joule-moq's tests/loc_vectors.rs regenerates and checks the LOC carriage corpus, from + # access units of the H.265 vector it reads, and requires loc-carriage §6's table. + - 'wai/live-conformance/loc/**' + - 'wai/format-probe-conformance/ext/hevc/**' + - 'wai/extensions/loc-carriage.md' - '.github/workflows/joule-code-conformance.yml' jobs: diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index 40c5188d9..18d871afc 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -182,6 +182,14 @@ on: # reports and its §3.2 table to name the reference's descriptors, exploration_verify.py # reads §13, and the registry's wire identifiers test reads its sections. - 'wai/extensions/exploration.md' + # tests/format_probe.rs requires format-probes §2.4 and §12 to list the reference's probes + # and codes, and projection.md §4 and §5 to state its codes; format_probe_verify.py reads + # format-probes' tables; and the registry's wire identifiers test reads projection.md, + # loc-carriage.md and delivery-telemetry.md (whose §6.6 cmsd_read_verify.py reads). + - 'wai/extensions/format-probes.md' + - 'wai/extensions/projection.md' + - 'wai/extensions/loc-carriage.md' + - 'wai/extensions/delivery-telemetry.md' - 'wai/wai-web/**' # The audio provenance check below reads the corpora above with this tool. - 'wai/tools/wai_audio_provenance_check.py' @@ -282,7 +290,7 @@ jobs: # input) fails here rather than wrapping. - name: Library unit tests (the fixed-point determinism floors) working-directory: wai/wai-rs - run: cargo test --lib --no-default-features --features world,audio_scene,haptic,splat4d,score,provenance,neural_int,film,oer,receipt_claims,key_discovery,staged,video_int,audio_int,splat_int,stage_measure,telemetry,video_package,http + run: cargo test --lib --no-default-features --features world,audio_scene,haptic,splat4d,score,provenance,neural_int,film,oer,receipt_claims,key_discovery,staged,video_int,audio_int,splat_int,stage_measure,telemetry,video_package,http,format_probe # The JPEG AI exact decoder reproduces across platforms only if no platform math # library is on its path. Two guards, each failing the step: clippy's # disallowed-methods (wai-rs/clippy.toml), which each module on the decode path @@ -430,7 +438,7 @@ jobs: working-directory: wai/wai-rs run: | set -euo pipefail - for f in neural_int video_int audio_int splat_int staged stage_measure telemetry http; do + for f in neural_int video_int audio_int splat_int staged stage_measure telemetry http format_probe; do cargo check --lib --no-default-features --features "$f" done # The decode bench prints its machine line from bench_env; no other step builds it. @@ -445,6 +453,8 @@ jobs: cargo build --lib --target wasm32-unknown-unknown --no-default-features --features receipt_claims,stage_measure # So is delivery telemetry: a browser sink writes the same reports. cargo build --lib --target wasm32-unknown-unknown --no-default-features --features telemetry + # And the header probes: a browser reads a payload's parameters the same way. + cargo build --lib --target wasm32-unknown-unknown --no-default-features --features format_probe # carriage: every registered capability round-trips byte-exact. cli_wrap_intent and # cli_registry run the `wai` binary, which builds without the codecs feature: the # intent rule of `wai wrap`, its refusal of candidate capabilities (SPEC §5), its media @@ -1641,6 +1651,45 @@ jobs: python3 signer-conformance/signer_verify.py verdicts signer-conformance/vectors.json > "$RUNNER_TEMP/signer-py.tsv" "$GITHUB_WORKSPACE/wai/wai-rs/target/debug/wai_signer_vectors" verdicts signer-conformance/vectors.json > "$RUNNER_TEMP/signer-rs.tsv" diff "$RUNNER_TEMP/signer-py.tsv" "$RUNNER_TEMP/signer-rs.tsv" + # Header probes (extensions/format-probes.md, projection.md): the reference module's unit + # tests (bits, Exp-Golomb and emulation prevention, the codes) and tests/format_probe.rs (the + # committed corpus regenerates byte for byte from its external vectors and holds no other + # generated file, every vector, binding and fact gets its stated outcome, and the + # extensions' tables list exactly the reference's probes and codes); then the five LCEVC + # streams fetch.sh fetches and checks by SHA-256, each of which wai_format_probe and the + # second implementation require (a missing one is a failure); then + # format_probe_verify.py, written from the two texts and sharing no code with wai-rs, + # which probes every vector itself and requires every mutated copy of the corpus to fail. + # Standard library only, run isolated (-I). + - name: header probes · wai-rs, the fetched LCEVC streams and format_probe_verify.py + working-directory: wai + shell: bash + run: | + set -euo pipefail + (cd wai-rs && cargo test --no-default-features --features format_probe --lib format_probe::) + (cd wai-rs && cargo test --no-default-features --features format_probe --test format_probe) + fetched=$(mktemp -d) + bash format-probe-conformance/fetch.sh "$fetched" + (cd wai-rs && cargo run -q --no-default-features --features format_probe --bin wai_format_probe -- verify ../format-probe-conformance --fetched "$fetched") + python3 -I format-probe-conformance/format_probe_verify.py format-probe-conformance --fetched "$fetched" + # CMSD reading (extensions/delivery-telemetry.md §6.6): the second implementation, written + # from that text and RFC 9651 with its own field parser, reads every field of + # telemetry-conformance/cmsd-read/ and every field expected/cmsd.json states, and requires + # every mutated copy to fail. wai-rs's reading is checked by wai_telemetry verify above. + - name: cmsd_read_verify.py + working-directory: wai + shell: bash + run: python3 -I telemetry-conformance/cmsd_read_verify.py telemetry-conformance + # LOC carriage (extensions/loc-carriage.md): the second implementation of joule-moq's loc + # module (whose tests/loc_vectors.rs runs in joule-code-conformance), written from the text, + # with its own MOQT -22 property decoder and BLAKE3 (claim-conformance/_blake3.py): it reads + # every object back to its envelope, writes it again byte for byte, gives every refused + # object and envelope its code, and requires every mutated copy, and the corpus under a + # copy of itself with a check dropped, to fail. + - name: loc_verify.py + working-directory: wai + shell: bash + run: python3 -I live-conformance/loc_verify.py live-conformance/loc # Delivery telemetry's second implementation is an independent open-source CMCD and CMSD # library at locked versions (telemetry-conformance/cml/package-lock.json). The diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index 39e1210ff..c17cf6aed 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -6,6 +6,56 @@ wire format may change and such changes are recorded here. ## v0.4.0 — current +### Registrations: JPEG XS, MV-HEVC and a projection descriptor, IEEE 1857.11 as a candidate; header probes; CMSD reading; LOC carriage + +- **New capabilities** (SPEC §5; the registry export). `wai.image.jxs` and `wai.video.jxs`, JPEG + XS (ISO/IEC 21122): one codestream, or codestreams one a frame in `wai.video.av1`'s framing; + `patent-pool`; `standard-defined`, because a sink that advertises either decodes at ISO/IEC + 21122-4's strict conformance point (Annex B.2: decoded images bitwise identical to the + reference), which the registration requires. `wai.video.mvhevc`, MV-HEVC (ITU-T H.265 Annexes F + and G): the Annex B byte stream of a multiview bitstream, `wai.video.hevc` its usable fallback; + `patent-pool`; `standard-defined` on H.265 (V11) §F.13.5.1. `wai.meta.projection` + ([projection](extensions/projection.md)): a companion descriptor, `WPJ1`, 40 bytes, saying how a + video rendition's pictures map onto what a viewer sees (rectilinear, equirectangular, + half-equirectangular, fisheye; angles in 2�¹� degrees as ITU-T H.274 §8.15 states them) and how + its two views are arranged (packed side by side or top and bottom as ITU-T H.273 §8.4 names + them, or in two layers of a multiview bitstream); royalty-free. `wai.image.ieee1857_11`, IEEE + 1857.11-2024 neural-network-based image coding, registered as a **candidate**: the standard is + not openly readable, and its open reference software writes a picture header with no start + code, from a working draft; `NeuralFloat`. +- **Header probes** ([format-probes](extensions/format-probes.md)) for those capabilities and for + `wai.video.hevc`, `wai.video.vvc`, `wai.video.lcevc`, `wai.audio.mpegh`, `wai.video.avs3` and + `wai.audio.avs3`, which were registered with none: each identifies a payload as its + capability's format and reads the parameters its headers state (profile, level, sizes, + layers and views, sampling, configurations), or refuses it with a code of its §12. Each + probe states its basis: the standard's own text (H.265, H.266), open implementations that + agree where the standard is not openly readable (JPEG XS, LCEVC, MPEG-H, AVS3, IEEE 1857.11), + or WAI's own format. For JPEG XS the probe gives the media-type parameters of RFC 9134 §7.1 + and states when a stream is the constant number of bytes a frame SMPTE ST 2110-22 carries. + `wai-rs`: module `format_probe` (feature `format_probe`), tool `wai_format_probe`. +- **The corpus** [`format-probe-conformance/`](format-probe-conformance/): vectors written by + permissively licensed encoders at pinned revisions from synthetic pictures (H.266, MV-HEVC + with its base layer, JPEG XS image and video, AVS3), with the facts each encoder was asked + for and every command and decode in `provenance.json`; generated vectors for LCEVC, MHAS, AVS3 + audio, the IEEE 1857.11 header and the projection descriptor; negative controls refused with + every code; five LCEVC streams fetched by digest (`fetch.sh`); and `format_probe_verify.py`, + written from the two extensions. +- **Measured for JPEG XS** (format-probes §3.6): the open decoder's C and SIMD paths give + identical output on every JPEG XS vector; a second, independent decoder gives the same bytes on + every vector whose profile it implements, and a lossless vector decodes to its source picture. +- **CMSD reading** ([delivery-telemetry](extensions/delivery-telemetry.md) §6.6): a reader of + `CMSD-Static` and `CMSD-Dynamic` back into the descriptions §6.1 to §6.3 write, passing over + other senders' keys, refusing with `cmsd_malformed`, `cmsd_type` or `cmsd_version`; the fields + and outcomes in `telemetry-conformance/cmsd-read/`, and `cmsd_read_verify.py`. §10 lists the + extension's CMCD and CMSD custom keys in full, and the registry lists each as a wire + identifier of the new kind `telemetry-key`. +- **LOC carriage** ([loc-carriage](extensions/loc-carriage.md)): an envelope whose payload is one + WebCodecs chunk (an Opus packet, an AVC or HEVC access unit, an ADTS frame) as one object of a + track of packaging `loc` (`draft-ietf-moq-loc-04`), its manifest the Immutable Property + `WAI_MANIFEST` and its receipt commitment `WAI_RECEIPT`, rebuilt by a reader byte for byte and + checked against the receipt. The writer and reader are `joule-moq`'s `loc` module; the corpus + is `live-conformance/loc/`, with `loc_verify.py`. + ### `wai.media.upscale`: super-resolution as an exact derivation, residuals against it, and an account of sink-made pictures - **The extension** ([exact-upscale](extensions/exact-upscale.md)) registers the record diff --git a/wai/SPEC.md b/wai/SPEC.md index 0e27a097e..1e71db298 100644 --- a/wai/SPEC.md +++ b/wai/SPEC.md @@ -1560,7 +1560,7 @@ codestream for a temporal unit. A sink that advertises either decodes at the str conformance point of ISO/IEC 21122-4 (Annex B.2: decoded images bitwise identical to the reference images), not only at its relaxed point, so the class of both is `StandardDefined`; the measurements behind it, and RFC 9134's and SMPTE ST 2110-22's -carriage of the payload, are [format-probes](extensions/format-probes.md) §3.5–§3.6. Each is +carriage of the payload, are [format-probes](extensions/format-probes.md) §3. Each is tiered `Lossy` (its role is mezzanine compression; its lossless mode is signalled in the codestream, as for AVIF). Its essential patents are licensed through a pool, so its licence class is `patent-pool`. @@ -2244,7 +2244,7 @@ cited are the ones read; the evidence, and what was not read, is in | | `wai.video.avc`, `wai.video.hevc`, `wai.video.vvc` | every sample of every cropped decoded picture a conforming decoder outputs equals the value the specified decoding process produces (ITU-T H.264 (V16, 06/2026) §C.4; H.265 (V11, 01/2026) §C.5.1; H.266 (V4, 01/2026) §C.5.1); processing an SEI message only signals, such as film grain synthesis, is outside that decoding process | | | `wai.video.vp9` | output frames "identical in all respects", in the same order, to those of the specified decoding process (VP9 Bitstream & Decoding Process Specification v0.6 §8.1) | | | `wai.video.mvhevc` | every sample of every cropped decoded picture of every output layer equals the value the specified decoding process produces (ITU-T H.265 (V11, 01/2026) §F.13.5.1) | -| | `wai.image.jxs`, `wai.video.jxs` | decoded images bitwise identical to the reference images, at the strict conformance point (ISO/IEC 21122-4:2022 §6.3 and Annex B.2, read in its public preview), which the registration requires of a sink; its relaxed point, held to PSNR bounds, is not enough ([format-probes](extensions/format-probes.md) §3.6) | +| | `wai.image.jxs`, `wai.video.jxs` | decoded images bitwise identical to the reference images, at the strict conformance point (ISO/IEC 21122-4:2022 §6.3 and Annex B.2, read in its public preview), which the registration requires of a sink; its relaxed point, held to PSNR bounds, is not enough ([format-probes](extensions/format-probes.md) §3) | | `StandardTolerance` | `wai.audio.opus`, `wai.audio.opus.packet` | output within `opus_compare`'s thresholds of the reference decoder, with the same final range-decoder state (RFC 6716 §6) | | | `wai.image.jpeg` | decoding "with appropriate accuracy", the accuracy of dequantisation and IDCT specified in Part 2 (ITU-T T.81 §7, A.3.3); Part 2 (ITU-T T.83) was not read, so the bound is not stated here | | | `wai.image.jxl` | decoded samples within per-test peak-error and RMSE thresholds of reference images (ISO/IEC 18181-3 §4.1–§4.3) | diff --git a/wai/extensions/projection.md b/wai/extensions/projection.md index 4b6c79b68..5aad7f5e5 100644 --- a/wai/extensions/projection.md +++ b/wai/extensions/projection.md @@ -120,7 +120,7 @@ needs one view. projection SEI message gives `equirectangular` (or `half-equirectangular` where a region-wise packing message packs only the front hemisphere); a sphere rotation message gives the centre; a frame packing arrangement of type 3 or 4 gives `side-by-side` or `top-bottom`; an MV-HEVC -bitstream's two views (format-probes §4.6, `view_ids`) give `layers`. Writing the other way, a +bitstream's two views (format-probes §4, `view_ids`) give `layers`. Writing the other way, a descriptor gives the same messages. Fisheye has no H.274 message; its parameters beyond the coverage (the image circle's centre and radius) are not in this revision of the record. diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index 8c6c1d300..4eb17a96e 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -1703,6 +1703,11 @@ name = "live_moderation" path = "tests/live_moderation.rs" required-features = ["live_moderation"] +[[test]] +name = "format_probe" +path = "tests/format_probe.rs" +required-features = ["format_probe"] + [[test]] name = "synthesis" path = "tests/synthesis.rs" -- 2.54.0 (Apple Git-157) From d4c8634b4f3c320bcc5e65e96b1cc69156372596 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 10:49:34 -0400 Subject: [PATCH 022/242] format_probe: clippy-clean (iterators over the VPS extension's loops, no long argument list) Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/format_probe/bits.rs | 10 +++++----- wai/wai-rs/src/format_probe/corpus.rs | 26 +++++++++++++++++--------- wai/wai-rs/src/format_probe/hevc.rs | 20 +++++++++----------- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/wai/wai-rs/src/format_probe/bits.rs b/wai/wai-rs/src/format_probe/bits.rs index becce9884..5f175b01e 100644 --- a/wai/wai-rs/src/format_probe/bits.rs +++ b/wai/wai-rs/src/format_probe/bits.rs @@ -25,7 +25,7 @@ impl<'a> BitReader<'a> { self.data.len() * 8 - self.pos } pub fn byte_aligned(&self) -> bool { - self.pos % 8 == 0 + self.pos.is_multiple_of(8) } /// `u(n)` for `n` ≤ 64. pub fn u(&mut self, n: u32) -> Result { @@ -73,10 +73,10 @@ impl<'a> BitReader<'a> { pub fn align(&mut self, bit: Option) -> Result<(), Refusal> { while !self.byte_aligned() { let b = self.u(1)?; - if let Some(want) = bit { - if b != want { - return Err(Refusal::ReservedValue); - } + if let Some(want) = bit + && b != want + { + return Err(Refusal::ReservedValue); } } Ok(()) diff --git a/wai/wai-rs/src/format_probe/corpus.rs b/wai/wai-rs/src/format_probe/corpus.rs index eedb0cdd9..9c75f5841 100644 --- a/wai/wai-rs/src/format_probe/corpus.rs +++ b/wai/wai-rs/src/format_probe/corpus.rs @@ -380,7 +380,12 @@ fn aatf(profile: u64, sfi: u64, codec: u64, resolution: u64, tail: &[u64]) -> Ve // ---- IEEE 1857.11 ------------------------------------------------------------------- -fn nic_header(profile: u32, level: u32, model: u32, pset: u32, quality: u32, w: u32, h: u32, depth: u16, fmt: u16, top: u16) -> Vec { +/// `[profile, level, model, parameter set, quality]`, `[width, height]`, and `[depth code, +/// picture format, the two unused bits]`. +fn nic_header(id: [u32; 5], size: [u32; 2], f: [u16; 3]) -> Vec { + let [profile, level, model, pset, quality] = id; + let [w, h] = size; + let [depth, fmt, top] = f; let ids = profile << 21 | level << 15 | model << 12 | pset << 5 | quality; let mut b = ids.to_be_bytes()[1..].to_vec(); b.extend_from_slice(&h.to_be_bytes()); @@ -392,6 +397,9 @@ fn nic_header(profile: u32, level: u32, model: u32, pset: u32, quality: u32, w: b } +/// The units of an AVS3 stream: each start code and the bytes after it. +type Units = Vec<(u8, Vec)>; + // ---- Generation --------------------------------------------------------------------- /// Builds the corpus from the external vectors (`ext/...` paths and bytes). Returns the @@ -527,8 +535,8 @@ pub fn generate(ext: &BTreeMap>) -> Result) let origin = "mutation of ext/avs3/main_128x64.avs3"; let units = avs3::units(&av).map_err(|e| format!("avs3: {e}"))?; let rebuild = |us: &[(u8, Vec)]| -> Vec { us.iter().flat_map(|(c, p)| avs3_unit(*c, p)).collect() }; - let owned: Vec<(u8, Vec)> = units.iter().map(|(c, p)| (*c, p.to_vec())).collect(); - let am = |f: &dyn Fn(&mut Vec<(u8, Vec)>)| { + let owned: Units = units.iter().map(|(c, p)| (*c, p.to_vec())).collect(); + let am = |f: &dyn Fn(&mut Units)| { let mut u = owned.clone(); f(&mut u); rebuild(&u) @@ -616,12 +624,12 @@ pub fn generate(ext: &BTreeMap>) -> Result) // IEEE 1857.11 (a candidate: its header as the reference software writes it). let d = "ieee1857_11"; - b.put("base_8bit", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header(0, 1, 0, 7, 3, 768, 512, 1, 0, 0)); - b.put("main_10bit", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header(1, 2, 1, 33, 5, 1920, 1080, 2, 1, 0)); - b.put("profile_3", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header(3, 1, 0, 0, 0, 64, 64, 1, 0, 0)); - b.put("depth_code_0", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header(0, 1, 0, 0, 0, 64, 64, 0, 0, 0)); - b.put("top_bits", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header(0, 1, 0, 0, 0, 64, 64, 1, 0, 1)); - b.put("truncated", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header(0, 1, 0, 0, 0, 64, 64, 1, 0, 0)[..12].to_vec()); + b.put("base_8bit", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header([0, 1, 0, 7, 3], [768, 512], [1, 0, 0])); + b.put("main_10bit", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header([1, 2, 1, 33, 5], [1920, 1080], [2, 1, 0])); + b.put("profile_3", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header([3, 1, 0, 0, 0], [64, 64], [1, 0, 0])); + b.put("depth_code_0", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header([0, 1, 0, 0, 0], [64, 64], [0, 0, 0])); + b.put("top_bits", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header([0, 1, 0, 0, 0], [64, 64], [1, 0, 1])); + b.put("truncated", CAP_IMAGE_IEEE1857_11, d, "generator", nic_header([0, 1, 0, 0, 0], [64, 64], [1, 0, 0])[..12].to_vec()); // The projection descriptor. let d = "projection"; diff --git a/wai/wai-rs/src/format_probe/hevc.rs b/wai/wai-rs/src/format_probe/hevc.rs index 8474415ea..27971e83b 100644 --- a/wai/wai-rs/src/format_probe/hevc.rs +++ b/wai/wai-rs/src/format_probe/hevc.rs @@ -240,8 +240,8 @@ fn vps_extension(r: &mut BitReader, base_internal: bool, max_layers_minus1: u64, } let types = mask.count_ones() as usize; let mut dim_len = vec![0u32; types]; - for j in 0..types.saturating_sub(usize::from(splitting)) { - dim_len[j] = r.u(3)? as u32 + 1; + for len in dim_len.iter_mut().take(types.saturating_sub(usize::from(splitting))) { + *len = r.u(3)? as u32 + 1; } let max_l = max_layers_minus1.min(62) as usize; if splitting && types > 0 { @@ -313,10 +313,11 @@ fn vps_extension(r: &mut BitReader, base_internal: bool, max_layers_minus1: u64, let n = max_l + 1; let mut dep = deps.clone(); for i in 0..n { - for j in 0..n { - for k in 0..i { - if deps[i][k] && dep[k][j] { - dep[i][j] = true; + for k in 0..i { + if deps[i][k] { + let row_k = dep[k].clone(); + for (cell, &v) in dep[i].iter_mut().zip(&row_k) { + *cell |= v; } } } @@ -353,12 +354,9 @@ fn vps_extension(r: &mut BitReader, base_internal: bool, max_layers_minus1: u64, r.skip(3 * n)?; } if r.flag()? { + // max_tid_il_ref_pics_plus1[ i ][ j ] for each j above i that depends on i. for i in 0..max_l { - for j in (i + 1)..=max_l { - if deps[j][i] { - r.skip(3)?; - } - } + r.skip(3 * deps.iter().skip(i + 1).filter(|row| row[i]).count())?; } } let _default_ref_layers_active = r.flag()?; -- 2.54.0 (Apple Git-157) From 278f3e166d36410b2859af42fbe713973b0f0b12 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 10:51:18 -0400 Subject: [PATCH 023/242] LOC corpus: the second-layer vector appends an SPS of layer 1, so only the base-layer rule refuses it Co-Authored-By: Claude Opus 5.5 --- joule-code/joule-code-rs/crates/joule-moq/src/loc.rs | 4 ++-- wai/live-conformance/loc/vectors.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/joule-code/joule-code-rs/crates/joule-moq/src/loc.rs b/joule-code/joule-code-rs/crates/joule-moq/src/loc.rs index 1c5473b7c..c225ca6a4 100644 --- a/joule-code/joule-code-rs/crates/joule-moq/src/loc.rs +++ b/joule-code/joule-code-rs/crates/joule-moq/src/loc.rs @@ -528,8 +528,8 @@ pub mod corpus { }), base.payload.clone()), ("hevc_second_layer", "wai.video.hevc", hevc.properties.clone(), { let mut pl = hevc.payload.clone(); - // a NAL unit header of nuh_layer_id 1 appended - pl.extend_from_slice(&[0, 0, 0, 1, 0x02, 0x09, 0xaa]); + // an SPS of nuh_layer_id 1 appended: one picture still, but not of the base layer only + pl.extend_from_slice(&[0, 0, 0, 1, 0x42, 0x09, 0x01]); pl }), ]; diff --git a/wai/live-conformance/loc/vectors.json b/wai/live-conformance/loc/vectors.json index 2e12b76b4..a01104ec4 100644 --- a/wai/live-conformance/loc/vectors.json +++ b/wai/live-conformance/loc/vectors.json @@ -202,7 +202,7 @@ { "capability": "wai.video.hevc", "name": "hevc_second_layer", - "payload": "0000000140010c11ffff01600000030000030000030000030000949056ff002000211a5c1808000003003e20000003000003023401000102807e14a4f4800000000142010101600000030000030000030000030000a0208107f96526491b61a588aa93130cbecfaf37e59f5e1446272eda8080000000014401c5a55811500a000000012601a3c2d859f8f15a33ef3e13ff97cdab22d80d8ef89f985f7a052c9540af109e7019a6b2a96a0f641b7fa559fcf1284af10d2ba8893c420a334955018fda6c2ec25a1dabd35e88d59a175f53ffc3921c52d6b1d42d221d98405c35fcda8a262fa3f2d75a08426926b76f952a5b9b9458b55db902378227da3296b9bf6cd3f60db3311972874228507b7e3c25bba727b624b2ffff6902213722de87d42e0c5c495e69e70e0d456461019692b4adbe4986149a4f1f21f477d0c574853faeeeb6f85db6d49964c67c829541145d5eb60d058f17473275d8d9667d62255451a41a2182f4258c26093ac4012ee1f05b38bfeab4b222ee4b2a506ea2898cff2fb0ce203b3787d66c857cc90e08ba47e8bffe5723c2462034ee09deaf1c776dc38c68004529feb19bd6db65b78c21c2afc772fc6c0639080fc122bc732f6a5fb3b25578e78f2400f97f2590b8ace49ec78b6f32922a10ddef55fe01e45d0676eb0567b6dc40e0e7551d6fc478acddce6563cf80234dc7b35f79e871798fd856d44c8a09878df9cc0d352801d12fbfa208ae3271e4be323248e864b744cfdb5fa3dab7c2271345b61cf44ca3ee2d0c233d12faa16dfef15fa6a75ce6f493a823d1be330516bac5f4c15636626ffaa61393c5fd6709bff461fd27e110c01a3604879af535219a7ab732a12c83ed34dc2369be796086430a3ca5312945306be4ece6ab8cc519f69e66bca74b23bf45a060b99476c26c1a5019908cf21d0bc844bb500ca2b74cb68adbad4757eac9fe6f33ab8d3771d41576de50f9e3d1eb71aa1aacea47c1bef3fd693e82686e040bb228fe117b03060f490ae55f6f41d8ee2a5575914c431c675ab456108c8252c7c2a08fb03b41b5ade444b1b2323dfb704095887e04931876171640cf6895a257e4b6c341e56aebce38109a4b197eb43cc4b6aa05e63a693ba5bc524438f514fab2b83956c5d69d08901f42ca2aec8b384f7c180b61b52324c46d727af8dc19a06bd0b2bba4c01b50e8b3716558f65f596d86b6cbee250a62f4e3cbbb5f35000000010209aa", + "payload": "0000000140010c11ffff01600000030000030000030000030000949056ff002000211a5c1808000003003e20000003000003023401000102807e14a4f4800000000142010101600000030000030000030000030000a0208107f96526491b61a588aa93130cbecfaf37e59f5e1446272eda8080000000014401c5a55811500a000000012601a3c2d859f8f15a33ef3e13ff97cdab22d80d8ef89f985f7a052c9540af109e7019a6b2a96a0f641b7fa559fcf1284af10d2ba8893c420a334955018fda6c2ec25a1dabd35e88d59a175f53ffc3921c52d6b1d42d221d98405c35fcda8a262fa3f2d75a08426926b76f952a5b9b9458b55db902378227da3296b9bf6cd3f60db3311972874228507b7e3c25bba727b624b2ffff6902213722de87d42e0c5c495e69e70e0d456461019692b4adbe4986149a4f1f21f477d0c574853faeeeb6f85db6d49964c67c829541145d5eb60d058f17473275d8d9667d62255451a41a2182f4258c26093ac4012ee1f05b38bfeab4b222ee4b2a506ea2898cff2fb0ce203b3787d66c857cc90e08ba47e8bffe5723c2462034ee09deaf1c776dc38c68004529feb19bd6db65b78c21c2afc772fc6c0639080fc122bc732f6a5fb3b25578e78f2400f97f2590b8ace49ec78b6f32922a10ddef55fe01e45d0676eb0567b6dc40e0e7551d6fc478acddce6563cf80234dc7b35f79e871798fd856d44c8a09878df9cc0d352801d12fbfa208ae3271e4be323248e864b744cfdb5fa3dab7c2271345b61cf44ca3ee2d0c233d12faa16dfef15fa6a75ce6f493a823d1be330516bac5f4c15636626ffaa61393c5fd6709bff461fd27e110c01a3604879af535219a7ab732a12c83ed34dc2369be796086430a3ca5312945306be4ece6ab8cc519f69e66bca74b23bf45a060b99476c26c1a5019908cf21d0bc844bb500ca2b74cb68adbad4757eac9fe6f33ab8d3771d41576de50f9e3d1eb71aa1aacea47c1bef3fd693e82686e040bb228fe117b03060f490ae55f6f41d8ee2a5575914c431c675ab456108c8252c7c2a08fb03b41b5ade444b1b2323dfb704095887e04931876171640cf6895a257e4b6c341e56aebce38109a4b197eb43cc4b6aa05e63a693ba5bc524438f514fab2b83956c5d69d08901f42ca2aec8b384f7c180b61b52324c46d727af8dc19a06bd0b2bba4c01b50e8b3716558f65f596d86b6cbee250a62f4e3cbbb5f3500000001420901", "properties": "0b80f60901a007fe065bfeda25e000ba314cfccf2e657e1b7a727c661052afdecf7cc2135fc1974c8c373c2a9adde8ad254cd312d00748775368756e74002cad57d4b10d779c9a0c5b6ba2b2c6b77bd2c920f1f2e7d445ac8e144f8385ca2080987b22776169223a22312e30222c226d65646961223a22766964656f222c22696e74656e74223a227265706c6963617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e766964656f2e68657663222c2266616c6c6261636b223a6e756c6c7d2c22746172676574223a7b227769647468223a36342c22686569676874223a36347d7d", "refuse": "loc_payload_form" }, -- 2.54.0 (Apple Git-157) From 46e081ab8f576e28452e74a1dc9d99716a0b69c1 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 10:57:20 -0400 Subject: [PATCH 024/242] wai.avatar.face: an integer talking-head generator, its records and its verification (avatar_face) Registers wai.avatar.face (GenerativeDeterministic, ops face-int/1, one-file parameter set): the WFG1 generator, WFI1 identity prior and WFK1 keypoint segment readers and writers; the driver's integer warp and keypoint maps; the lowering face-int/1 to one wai-ops/1-i32 graph, with the same arithmetic written out loop by loop for the tests; the identity and track records; verification over them and synthesis's claims, log and label policy; the corpus generator (bin wai_avatar_face_vectors) and the corpus and GPU tests. Registry export regenerated. Co-Authored-By: Claude Opus 5.5 --- wai/REGISTRY.md | 1 + wai/registry/capabilities.json | 20 + wai/vocab/v2.shapes.ttl | 4 + wai/vocab/v2.ttl | 12 + wai/wai-rs/Cargo.toml | 22 + wai/wai-rs/src/avatar_face.rs | 1334 +++++++++++++++++ wai/wai-rs/src/avatar_face/corpus.rs | 962 ++++++++++++ wai/wai-rs/src/bin/wai_avatar_face_vectors.rs | 100 ++ wai/wai-rs/src/codecs/glossary_check.rs | 1 + wai/wai-rs/src/codecs/mod.rs | 36 +- wai/wai-rs/src/codecs/registry.rs | 19 +- wai/wai-rs/src/lib.rs | 9 + wai/wai-rs/tests/avatar_face.rs | 125 ++ wai/wai-rs/tests/avatar_face_gpu.rs | 90 ++ 14 files changed, 2727 insertions(+), 8 deletions(-) create mode 100644 wai/wai-rs/src/avatar_face.rs create mode 100644 wai/wai-rs/src/avatar_face/corpus.rs create mode 100644 wai/wai-rs/src/bin/wai_avatar_face_vectors.rs create mode 100644 wai/wai-rs/tests/avatar_face.rs create mode 100644 wai/wai-rs/tests/avatar_face_gpu.rs diff --git a/wai/REGISTRY.md b/wai/REGISTRY.md index 5cf04f36e..1f55c977b 100644 --- a/wai/REGISTRY.md +++ b/wai/REGISTRY.md @@ -173,6 +173,7 @@ and a role are fixed once registered. | `wai.audio.opus.packet` | payload | registered | audio | standalone | royalty-free | standard-tolerance | — | §5 Broadcast and streaming codecs | | `wai.audio.scene` | payload | registered | audio | standalone | royalty-free | standard-defined | — | §5 Interactive, temporal & computed content | | `wai.audio.score` | payload | registered | audio | standalone | royalty-free | standard-defined | — | §5 Interactive, temporal & computed content | +| `wai.avatar.face` | payload | registered | interactive | standalone | royalty-free | generative-exact | — | §5 Interactive, temporal & computed content | | `wai.avatar.pose` | payload | registered | interactive | standalone | royalty-free | standard-defined | — | §5 Interactive, temporal & computed content | | `wai.bearer.receipts` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content | | `wai.capture.take` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content | diff --git a/wai/registry/capabilities.json b/wai/registry/capabilities.json index a16da3ba0..9c79014a1 100644 --- a/wai/registry/capabilities.json +++ b/wai/registry/capabilities.json @@ -266,6 +266,26 @@ "edition": null, "registered": "2026-06-14" }, + { + "capability": "wai.avatar.face", + "kind": "payload", + "status": "registered", + "media": "interactive", + "role": "standalone", + "tier": "lossless", + "licence": "royalty-free", + "replicate": "generative-exact", + "sets": [], + "spec_section": "§5 Interactive, temporal & computed content", + "edition": null, + "registered": "2026-10-22", + "ops": { + "set": "wai-ops/1", + "profile": "acc64", + "subset": "full", + "lowering": "face-int/1" + } + }, { "capability": "wai.avatar.pose", "kind": "payload", diff --git a/wai/vocab/v2.shapes.ttl b/wai/vocab/v2.shapes.ttl index 858902707..5f3b0a96a 100644 --- a/wai/vocab/v2.shapes.ttl +++ b/wai/vocab/v2.shapes.ttl @@ -30,6 +30,7 @@ wai:ManifestShape a sh:NodeShape ; wai:wai.audio.opus.packet wai:wai.audio.scene wai:wai.audio.score + wai:wai.avatar.face wai:wai.avatar.pose wai:wai.feed.state wai:wai.film.linear @@ -116,6 +117,7 @@ wai:ManifestShape a sh:NodeShape ; wai:wai.audio.opus.packet wai:wai.audio.scene wai:wai.audio.score + wai:wai.avatar.face wai:wai.avatar.pose wai:wai.feed.state wai:wai.film.linear @@ -210,6 +212,7 @@ wai:ComponentShape a sh:NodeShape ; wai:wai.audio.opus.packet wai:wai.audio.scene wai:wai.audio.score + wai:wai.avatar.face wai:wai.avatar.pose wai:wai.feed.state wai:wai.film.linear @@ -296,6 +299,7 @@ wai:ComponentShape a sh:NodeShape ; wai:wai.audio.opus.packet wai:wai.audio.scene wai:wai.audio.score + wai:wai.avatar.face wai:wai.avatar.pose wai:wai.feed.state wai:wai.film.linear diff --git a/wai/vocab/v2.ttl b/wai/vocab/v2.ttl index 30762872b..8cb417b82 100644 --- a/wai/vocab/v2.ttl +++ b/wai/vocab/v2.ttl @@ -288,6 +288,18 @@ wai:wai.audio.score a wai:Capability ; wai:specSection "§5 Interactive, temporal & computed content" ; wai:registeredOn "2026-06-14"^^xsd:date . +wai:wai.avatar.face a wai:Capability ; + rdfs:label "wai.avatar.face" ; + wai:kind wai:kind-payload ; + wai:status wai:status-registered ; + wai:media wai:media-interactive ; + wai:role wai:role-standalone ; + wai:tier wai:tier-lossless ; + wai:licence wai:licence-royalty-free ; + wai:replicateClass wai:replicate-generative-exact ; + wai:specSection "§5 Interactive, temporal & computed content" ; + wai:registeredOn "2026-10-22"^^xsd:date . + wai:wai.avatar.pose a wai:Capability ; rdfs:label "wai.avatar.pose" ; wai:kind wai:kind-payload ; diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index e5e2eafc1..f6a70a0fc 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -357,6 +357,13 @@ synthesis_mark = ["synthesis", "video_int", "generate", "png_still"] # generated, each held to its origin claim (synthesis). Its claims are synthesis's, its residuals # staged's. SHA-256, BLAKE3 and Ed25519 only; no new dependency. exact_upscale = ["synthesis", "staged"] +# Faces from keypoints (extensions/avatars.md §6 to §13): `wai.avatar.face` — a keypoint track +# (WFK1 segments) and an identity prior named by SHA-256 (WFI1) generate a talking head that is +# the same bytes on every sink: the driver's integer warp and keypoint maps, then the generator's +# network (WFG1, pinned by SHA-256) as one wai-ops/1 graph of the lowering `face-int/1`; the +# participant's identity and track records; each segment's synthesis origin claim, and the +# player's log and label policy held to synthesis §10. No new dependency. +avatar_face = ["synthesis", "jwp_hash"] # AI usage preferences and consumption claims (extensions/ai-usage): `wai.ai.usage` — the usage # statement (draft-ietf-aipref-vocab-08's Structured Field form, the TDM Reservation Protocol's # properties and the Training and Data Mining Assertion's entries, verbatim) in an envelope's @@ -915,6 +922,11 @@ name = "wai_upscale_vectors" path = "src/bin/wai_upscale_vectors.rs" required-features = ["exact_upscale"] +[[bin]] +name = "wai_avatar_face_vectors" +path = "src/bin/wai_avatar_face_vectors.rs" +required-features = ["avatar_face"] + [[bin]] name = "wai_upscale_measure" path = "src/bin/wai_upscale_measure.rs" @@ -1718,6 +1730,16 @@ name = "exact_upscale" path = "tests/exact_upscale.rs" required-features = ["exact_upscale"] +[[test]] +name = "avatar_face" +path = "tests/avatar_face.rs" +required-features = ["avatar_face"] + +[[test]] +name = "avatar_face_gpu" +path = "tests/avatar_face_gpu.rs" +required-features = ["avatar_face", "ops_gpu"] + [[test]] name = "exact_upscale_gpu" path = "tests/exact_upscale_gpu.rs" diff --git a/wai/wai-rs/src/avatar_face.rs b/wai/wai-rs/src/avatar_face.rs new file mode 100644 index 000000000..07591c04e --- /dev/null +++ b/wai/wai-rs/src/avatar_face.rs @@ -0,0 +1,1334 @@ +//! Faces from keypoints (`extensions/avatars.md` §6 to §13): the capability `wai.avatar.face`. +//! +//! A participant sends a **keypoint track** (segments of the payload `WFK1`, §7.4) and +//! commits, once, to an **identity prior** (a reference picture and its keypoints, the file +//! `WFI1`, §7.3) by its SHA-256 in a signed **identity record** (§8.2). Every sink that holds +//! the prior and the **generator** (the file `WFG1`, §7.2, pinned by SHA-256) computes the +//! same frames from each segment: the driver warps the reference picture to the driving +//! keypoints and draws a map of each keypoint (§7.5), all in integers, and the generator's +//! network refines the warp as one `WHG1` graph of `wai-ops/1` (§7.6, the lowering +//! `face-int/1`), which runs on the operator set's interpreter, under wasm32 and on a GPU +//! ([`crate::int_ops::gpu`]) to the same bytes. +//! +//! The participant's key signs the identity record and each segment's **track record** +//! (§8.3): that is provenance, who drove which face. Each segment object carries a synthesis +//! origin claim of class `synthesized` ([`crate::synthesis`] §3), and a player's log and a +//! label policy are held to synthesis §10 over the frames (§8.5). +//! +//! - [`parse_generator`], [`parse_identity`], [`parse_segment`] read the three files and +//! refuse each with §7.2's, §7.3's and §7.4's codes; [`write_generator`], +//! [`write_identity`], [`write_segment`] write them; +//! - [`warp`] is the driver (§7.5); [`graph`] the lowering (§7.6); [`load`] loads a +//! generator as a sink does; [`frame`] and [`generate`] make frames; [`direct`] is §7.6 +//! loop by loop, with no graph, which the tests hold the graph to; +//! - [`verify`] is §9. +//! +//! The corpus is `live-conformance/avatar-face/` ([`corpus`]); its second implementation is +//! `live-conformance/avatar_face_verify.py`. + +use std::collections::BTreeMap; + +use ed25519_dalek::{Signer, SigningKey}; +use serde_json::{json, Map, Value}; +use sha2::{Digest, Sha256}; +use wai_ops::graph::{opcode, BiasKind, Graph, InputRec, Loaded, Op, Params, Round, Rq, TensorRec, CAP}; +use wai_ops::Tensor; + +use crate::claim_receipt::rfc3339; +use crate::codecs::CAP_AVATAR_FACE; +use crate::jcs::{jcs, MAX_INTEGER}; +use crate::synthesis::{self, Class, Step, Subject}; + +pub mod corpus; + +/// The capability (§6). +pub const CAPABILITY: &str = CAP_AVATAR_FACE; +/// The lowering of a generator to `WHG1` (§7.6). +pub const LOWERING: &str = "face-int/1"; +/// The domain every record of §8 is signed under. +pub const SIGN_DOMAIN: &[u8] = b"wai:avatar\x01"; +/// The domain of a record's link hash (§8.1). +pub const LINK_DOMAIN: &[u8] = b"wai:avatar-link\x01"; +/// The most keypoints a face has (§7.2). +pub const MAX_KEYPOINTS: usize = 32; +/// The longest side of a frame (§7.2). +pub const MAX_SIDE: usize = 512; +/// The most channels a hidden layer has (§7.2). +pub const MAX_CHANNELS: usize = 64; +/// The most frames one segment holds (§7.4). +pub const MAX_FRAMES: usize = 1024; +/// Weights are integers at 2^12, activations at 2^15, biases at 2^27 (§7.1). +pub const FW: u8 = 12; +pub const FA: u8 = 15; +/// The ceiling of every hidden activation (§7.1). +pub const ACT_HI: i64 = 32767; + +// --------------------------------------------------------------------------------------- +// §7.2 to §7.4 The files +// --------------------------------------------------------------------------------------- + +/// Why a file does not read (§7.2, §7.3, §7.4), in the order each section checks. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum FormError { + BadMagic, + Version, + FieldRange, + Truncated, + Trailing, + Noncanonical, +} + +impl FormError { + pub const ALL: [FormError; 6] = [FormError::BadMagic, FormError::Version, FormError::FieldRange, FormError::Truncated, FormError::Trailing, FormError::Noncanonical]; + + pub fn code(self) -> &'static str { + match self { + FormError::BadMagic => "bad_magic", + FormError::Version => "version", + FormError::FieldRange => "field_range", + FormError::Truncated => "truncated", + FormError::Trailing => "trailing", + FormError::Noncanonical => "noncanonical", + } + } +} + +impl std::fmt::Display for FormError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.code()) + } +} + +/// One convolution of a generator: 3 × 3, zero padding 1, its weights `[cout][cin][3][3]`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Conv { + pub cin: usize, + pub cout: usize, + pub stride: u32, + pub w: Vec, + pub b: Vec, +} + +/// A generator, read (§7.2). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Generator { + pub k: usize, + pub width: usize, + pub height: usize, + pub c1: usize, + pub c2: usize, + pub c3: usize, + /// §7.5's `g`, at least 1. + pub gauss: u16, + /// §7.5's background weight, at least 1. + pub bg: u32, + pub convs: Vec, +} + +/// The `(cin, cout, stride)` of the ten convolutions, in the file's order (§7.2). +pub fn layer_shapes(k: usize, c1: usize, c2: usize, c3: usize) -> [(usize, usize, u32); 10] { + [ + (6 + k, c1, 1), + (c1, c2, 2), + (c2, c3, 2), + (c3, c3, 1), + (c3, c3, 1), + (c3, 4 * c2, 1), + (2 * c2, c2, 1), + (c2, 4 * c1, 1), + (2 * c1, c1, 1), + (c1, 4, 1), + ] +} + +fn need(b: &[u8], at: usize, n: usize) -> Result<(), FormError> { + if b.len() < at + n { Err(FormError::Truncated) } else { Ok(()) } +} + +fn u16_at(b: &[u8], at: usize) -> usize { + u16::from_le_bytes([b[at], b[at + 1]]) as usize +} + +fn side_ok(v: usize) -> bool { + (4..=MAX_SIDE).contains(&v) && v.is_multiple_of(4) +} + +/// Read a generator (§7.2): the magic (`bad_magic`), the version (`version`), the header +/// present (`truncated`) and in range (`field_range`), each convolution's arrays present +/// (`truncated`), and nothing after the last (`trailing`). +pub fn parse_generator(b: &[u8]) -> Result { + need(b, 0, 4)?; + if b[..4] != *b"WFG1" { + return Err(FormError::BadMagic); + } + need(b, 4, 1)?; + if b[4] != 1 { + return Err(FormError::Version); + } + need(b, 5, 17)?; + let k = b[5] as usize; + let (width, height) = (u16_at(b, 6), u16_at(b, 8)); + let (c1, c2, c3) = (u16_at(b, 10), u16_at(b, 12), u16_at(b, 14)); + let gauss = u16::from_le_bytes([b[16], b[17]]); + let bg = u32::from_le_bytes([b[18], b[19], b[20], b[21]]); + let chan = |c: usize| (1..=MAX_CHANNELS).contains(&c); + if !(1..=MAX_KEYPOINTS).contains(&k) || !side_ok(width) || !side_ok(height) || !chan(c1) || !chan(c2) || !chan(c3) || gauss == 0 || bg == 0 { + return Err(FormError::FieldRange); + } + let mut at = 22; + let mut convs = Vec::with_capacity(10); + for (cin, cout, stride) in layer_shapes(k, c1, c2, c3) { + let nw = cout * cin * 9; + need(b, at, 2 * nw + 4 * cout)?; + let w = b[at..at + 2 * nw].as_chunks::<2>().0.iter().map(|c| i16::from_le_bytes(*c)).collect(); + at += 2 * nw; + let bb = b[at..at + 4 * cout].as_chunks::<4>().0.iter().map(|c| i32::from_le_bytes(*c)).collect(); + at += 4 * cout; + convs.push(Conv { cin, cout, stride, w, b: bb }); + } + if at != b.len() { + return Err(FormError::Trailing); + } + Ok(Generator { k, width, height, c1, c2, c3, gauss, bg, convs }) +} + +/// The bytes of a generator: the inverse of [`parse_generator`]. +pub fn write_generator(g: &Generator) -> Vec { + let mut o = b"WFG1".to_vec(); + o.push(1); + o.push(g.k as u8); + for v in [g.width, g.height, g.c1, g.c2, g.c3] { + o.extend_from_slice(&(v as u16).to_le_bytes()); + } + o.extend_from_slice(&g.gauss.to_le_bytes()); + o.extend_from_slice(&g.bg.to_le_bytes()); + for c in &g.convs { + for v in &c.w { + o.extend_from_slice(&v.to_le_bytes()); + } + for v in &c.b { + o.extend_from_slice(&v.to_le_bytes()); + } + } + o +} + +/// A generator whose every weight and bias is drawn from SHA-256 of `seed` and a counter, +/// trained on nothing: for conformance vectors, where a generator exercises the arithmetic. +/// Weights lie in −2048..=2047 (±0.5), biases in ±2^25 (±0.25), so the clamps decide some +/// activations and the mask takes many values. +#[allow(clippy::too_many_arguments)] +pub fn seeded_generator(seed: &[u8], k: usize, width: usize, height: usize, c1: usize, c2: usize, c3: usize, gauss: u16, bg: u32) -> Vec { + let mut stream = (0u32..).flat_map(|i| crate::pin::sha256(&[seed, &i.to_le_bytes()].concat())); + let mut next16 = || u16::from_le_bytes([stream.next().expect("endless"), stream.next().expect("endless")]); + let convs = layer_shapes(k, c1, c2, c3) + .into_iter() + .map(|(cin, cout, stride)| { + let w = (0..cout * cin * 9).map(|_| (next16() % 4096) as i16 - 2048).collect(); + let b = (0..cout).map(|_| ((next16() as i32) - 32768) * 1024).collect(); + Conv { cin, cout, stride, w, b } + }) + .collect(); + write_generator(&Generator { k, width, height, c1, c2, c3, gauss, bg, convs }) +} + +/// An identity prior, read (§7.3): its keypoints in sixteenths of a pixel and its picture. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Identity { + pub k: usize, + pub width: usize, + pub height: usize, + pub keypoints: Vec<(i32, i32)>, + /// RGB8, rows top to bottom. + pub rgb: Vec, +} + +/// Read an identity prior (§7.3), in the order of §7.2's checks. +pub fn parse_identity(b: &[u8]) -> Result { + need(b, 0, 4)?; + if b[..4] != *b"WFI1" { + return Err(FormError::BadMagic); + } + need(b, 4, 1)?; + if b[4] != 1 { + return Err(FormError::Version); + } + need(b, 5, 5)?; + let k = b[5] as usize; + let (width, height) = (u16_at(b, 6), u16_at(b, 8)); + if !(1..=MAX_KEYPOINTS).contains(&k) || !side_ok(width) || !side_ok(height) { + return Err(FormError::FieldRange); + } + let n = 4 * k + width * height * 3; + need(b, 10, n)?; + let keypoints = b[10..10 + 4 * k].as_chunks::<4>().0.iter().map(|c| (i16::from_le_bytes([c[0], c[1]]) as i32, i16::from_le_bytes([c[2], c[3]]) as i32)).collect(); + if b.len() != 10 + n { + return Err(FormError::Trailing); + } + Ok(Identity { k, width, height, keypoints, rgb: b[10 + 4 * k..].to_vec() }) +} + +/// The bytes of an identity prior. +pub fn write_identity(id: &Identity) -> Vec { + let mut o = b"WFI1".to_vec(); + o.push(1); + o.push(id.k as u8); + o.extend_from_slice(&(id.width as u16).to_le_bytes()); + o.extend_from_slice(&(id.height as u16).to_le_bytes()); + for &(x, y) in &id.keypoints { + o.extend_from_slice(&(x as i16).to_le_bytes()); + o.extend_from_slice(&(y as i16).to_le_bytes()); + } + o.extend_from_slice(&id.rgb); + o +} + +/// A keypoint segment, read (§7.4): its frames' keypoints in sixteenths of a pixel. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Segment { + pub k: usize, + pub step: u32, + pub order: u32, + pub generator: [u8; 32], + pub identity: [u8; 32], + pub first: u32, + pub rate: (u16, u16), + pub frames: Vec>, +} + +/// The fixed part of a segment before its first frame (§7.4). +pub const SEGMENT_HEADER: usize = 84; + +struct Bits<'a> { + b: &'a [u8], + at: usize, +} + +impl Bits<'_> { + fn bit(&mut self) -> Result { + let byte = *self.b.get(self.at / 8).ok_or(FormError::Truncated)?; + let v = (byte >> (7 - self.at % 8)) & 1; + self.at += 1; + Ok(v as u64) + } + + fn bits(&mut self, n: u32) -> Result { + let mut v = 0; + for _ in 0..n { + v = (v << 1) | self.bit()?; + } + Ok(v) + } + + /// One Exp-Golomb code of order `k` (§7.4): at most 31 leading zeros (`field_range`). + fn eg(&mut self, k: u32) -> Result { + let mut z = 0u32; + while self.bit()? == 0 { + z += 1; + if z > 31 { + return Err(FormError::FieldRange); + } + } + let m = (1u64 << z) | self.bits(z)?; + Ok(((m - 1) << k) | self.bits(k)?) + } +} + +fn unzigzag(u: u64) -> i64 { + if u & 1 == 0 { (u >> 1) as i64 } else { -(((u + 1) >> 1) as i64) } +} + +fn zigzag(v: i64) -> u64 { + if v >= 0 { (v as u64) << 1 } else { ((-v) as u64) * 2 - 1 } +} + +/// Read a keypoint segment (§7.4): the magic (`bad_magic`), the version (`version`), the +/// header present (`truncated`) and in range (`field_range`), the first frame present +/// (`truncated`), then each later frame's codes, read to their end (`truncated`), each code +/// of at most 31 leading zeros and each coordinate in range (`field_range`); then fewer than +/// eight bits of padding (`trailing`), each 0 (`noncanonical`). +pub fn parse_segment(b: &[u8]) -> Result { + need(b, 0, 4)?; + if b[..4] != *b"WFK1" { + return Err(FormError::BadMagic); + } + need(b, 4, 1)?; + if b[4] != 1 { + return Err(FormError::Version); + } + need(b, 5, SEGMENT_HEADER - 5)?; + let (k, step, order) = (b[5] as usize, b[6] as u32, b[7] as u32); + let generator: [u8; 32] = b[8..40].try_into().expect("32 bytes"); + let identity: [u8; 32] = b[40..72].try_into().expect("32 bytes"); + let first = u32::from_le_bytes(b[72..76].try_into().expect("4 bytes")); + let n = u16_at(b, 76); + let rate = (u16_at(b, 78) as u16, u16_at(b, 80) as u16); + let reserved = u16_at(b, 82); + if !(1..=MAX_KEYPOINTS).contains(&k) || !(1..=16).contains(&step) || order > 15 || !(1..=MAX_FRAMES).contains(&n) || rate.0 == 0 || rate.1 == 0 || reserved != 0 { + return Err(FormError::FieldRange); + } + need(b, SEGMENT_HEADER, 4 * k)?; + let in_range = |c: i64| (-32768..=32767).contains(&(c * step as i64)); + let mut cur: Vec = b[SEGMENT_HEADER..SEGMENT_HEADER + 4 * k].as_chunks::<2>().0.iter().map(|c| i16::from_le_bytes(*c) as i64).collect(); + if !cur.iter().all(|&c| in_range(c)) { + return Err(FormError::FieldRange); + } + let to_frame = |cur: &[i64]| cur.chunks(2).map(|p| ((p[0] * step as i64) as i32, (p[1] * step as i64) as i32)).collect::>(); + let mut frames = vec![to_frame(&cur)]; + let start = SEGMENT_HEADER + 4 * k; + let mut r = Bits { b: &b[start..], at: 0 }; + for _ in 1..n { + for c in cur.iter_mut() { + *c += unzigzag(r.eg(order)?); + if !in_range(*c) { + return Err(FormError::FieldRange); + } + } + frames.push(to_frame(&cur)); + } + let used = r.at.div_ceil(8); + if start + used != b.len() { + return Err(FormError::Trailing); + } + while !r.at.is_multiple_of(8) { + if r.bit()? != 0 { + return Err(FormError::Noncanonical); + } + } + Ok(Segment { k, step, order, generator, identity, first, rate, frames }) +} + +/// The length in bits of Exp-Golomb order `k` of `u`. +fn eg_len(u: u64, k: u32) -> u64 { + let m = (u >> k) + 1; + 2 * (64 - m.leading_zeros() as u64) - 1 + k as u64 +} + +/// Write a segment (§7.4) of `frames` (sixteenths of a pixel, each a multiple of `step`), +/// with the code order that makes it shortest (the lowest such order where two tie). +/// `None` for frames that are not multiples of `step`, out of range, of unequal length, or +/// too many. +pub fn write_segment(frames: &[Vec<(i32, i32)>], step: u32, generator: &[u8; 32], identity: &[u8; 32], first: u32, rate: (u16, u16)) -> Option> { + let k = frames.first()?.len(); + if !(1..=MAX_KEYPOINTS).contains(&k) || !(1..=16).contains(&step) || frames.len() > MAX_FRAMES || frames.iter().any(|f| f.len() != k) { + return None; + } + let mut q: Vec> = Vec::with_capacity(frames.len()); + for f in frames { + let mut row = Vec::with_capacity(2 * k); + for &(x, y) in f { + for c in [x, y] { + if c % step as i32 != 0 || !(-32768..=32767).contains(&c) { + return None; + } + row.push(c as i64 / step as i64); + } + } + q.push(row); + } + let deltas: Vec = q.windows(2).flat_map(|w| w[1].iter().zip(&w[0]).map(|(a, b)| zigzag(a - b)).collect::>()).collect(); + let order = (0..16u32).min_by_key(|&o| (deltas.iter().map(|&u| eg_len(u, o)).sum::(), o)).expect("sixteen orders"); + let mut out = b"WFK1".to_vec(); + out.extend_from_slice(&[1, k as u8, step as u8, order as u8]); + out.extend_from_slice(generator); + out.extend_from_slice(identity); + out.extend_from_slice(&first.to_le_bytes()); + out.extend_from_slice(&(frames.len() as u16).to_le_bytes()); + out.extend_from_slice(&rate.0.to_le_bytes()); + out.extend_from_slice(&rate.1.to_le_bytes()); + out.extend_from_slice(&0u16.to_le_bytes()); + for &c in &q[0] { + out.extend_from_slice(&(c as i16).to_le_bytes()); + } + let mut bits: Vec = Vec::new(); + for u in deltas { + let m = (u >> order) + 1; + let nb = 64 - m.leading_zeros(); + bits.extend(std::iter::repeat_n(0u8, nb as usize - 1)); + bits.extend((0..nb).rev().map(|i| ((m >> i) & 1) as u8)); + bits.extend((0..order).rev().map(|i| ((u >> i) & 1) as u8)); + } + for chunk in bits.chunks(8) { + let mut byte = 0u8; + for (i, &v) in chunk.iter().enumerate() { + byte |= v << (7 - i); + } + out.push(byte); + } + Some(out) +} + +// --------------------------------------------------------------------------------------- +// §7.5 The driver +// --------------------------------------------------------------------------------------- + +/// `exp_neg_q16` (integer-operators §2). +fn exp_neg_q16(x: i64) -> i64 { + wai_ops::interp::exp_neg_q16(x as i128) as i64 +} + +/// sgn(n)·⌊(2|n| + d) / (2d)⌋ for d ≥ 1: half away from zero (integer-operators E12's `rdiv`). +fn rdiv(n: i64, d: i64) -> i64 { + let a = (n.unsigned_abs() as i128 * 2 + d as i128) / (2 * d as i128); + if n < 0 { -(a as i64) } else { a as i64 } +} + +/// The driver (§7.5): the warped picture (RGB8, rows top to bottom) and the keypoint maps +/// (`[K, h, w]`, each in 0..=256), for the driving keypoints `drv` over an identity. +pub fn warp(id: &Identity, drv: &[(i32, i32)], gauss: u16, bg: u32) -> (Vec, Vec) { + let (w, h, k) = (id.width, id.height, id.k); + let mut warped = vec![0u8; w * h * 3]; + let mut maps = vec![0i64; k * w * h]; + let mut wk = vec![0i64; k]; + for y in 0..h { + for x in 0..w { + let (px, py) = (16 * x as i64 + 8, 16 * y as i64 + 8); + let (mut den, mut nx, mut ny) = (bg as i64, 0i64, 0i64); + for (j, &(dx, dy)) in drv.iter().enumerate() { + let (ex, ey) = (px - dx as i64, py - dy as i64); + let e = ((ex * ex + ey * ey) * gauss as i64 + 128) >> 8; + wk[j] = exp_neg_q16(-e); + den += wk[j]; + nx += wk[j] * (id.keypoints[j].0 - dx) as i64; + ny += wk[j] * (id.keypoints[j].1 - dy) as i64; + maps[(j * h + y) * w + x] = wk[j] >> 8; + } + let (ux, uy) = (16 * x as i64 + rdiv(nx, den), 16 * y as i64 + rdiv(ny, den)); + let (ix, iy) = (ux.div_euclid(16), uy.div_euclid(16)); + let (fx, fy) = (ux - 16 * ix, uy - 16 * iy); + let cx = |v: i64| v.clamp(0, w as i64 - 1) as usize; + let cy = |v: i64| v.clamp(0, h as i64 - 1) as usize; + let (x0, x1, y0, y1) = (cx(ix), cx(ix + 1), cy(iy), cy(iy + 1)); + for c in 0..3 { + let p = |yy: usize, xx: usize| id.rgb[(yy * w + xx) * 3 + c] as i64; + let v = ((16 - fx) * (16 - fy) * p(y0, x0) + fx * (16 - fy) * p(y0, x1) + (16 - fx) * fy * p(y1, x0) + fx * fy * p(y1, x1) + 128) >> 8; + warped[(y * w + x) * 3 + c] = v as u8; + } + } + } + (warped, maps) +} + +/// The mask's table (§7.6): `T[v + 32768]` for `v` in −32768..=32767. +pub fn sigmoid_table() -> Vec { + let s = |v: i64| { + let d = 65536 + exp_neg_q16(-16 * v).min(65536); + (((1i64 << 31) + d / 2) / d) as i16 + }; + let mut t = vec![0i16; 65536]; + for v in 0..=32767i64 { + t[(v + 32768) as usize] = s(v); + } + for v in 1..=32767i64 { + t[(32768 - v) as usize] = (32768 - s(v) as i64) as i16; + } + t +} + +// --------------------------------------------------------------------------------------- +// §7.6 The graph +// --------------------------------------------------------------------------------------- + +fn conv_rq(b: &[i32]) -> Rq { + Rq { round: Round::HalfUp, bias_kind: BiasKind::Pre, m_kind: 0, s_kind: 1, m_bytes: 0, b_bytes: 4, mult: vec![], shift: vec![FW], bias: b.iter().map(|&v| v as i64).collect() } +} + +/// The lowering `face-int/1` (§7.6): one graph with three data inputs, sizes set per decode: +/// tensor 0 the warped picture `[3, h, w]` and tensor 1 the reference `[3, h, w]`, each +/// `check [0, 255]`, and tensor 2 the keypoint maps `[K, h, w]`, `check [0, 256]`; then the +/// operators in §7.6's order, each output taking the next tensor index; the one output, the +/// frame `[3, h, w]`. +pub fn graph(g: &Generator) -> Graph { + let k = g.k as u32; + let mut tensors = vec![TensorRec::check(3, 0, 255), TensorRec::check(3, 0, 255), TensorRec::check(k, 0, 256)]; + let mut ops: Vec = Vec::new(); + let mut op = |tensors: &mut Vec, code: u8, ins: Vec, ch: u32, p: Params| -> u32 { + let t = tensors.len() as u32; + tensors.push(TensorRec::derived(ch)); + ops.push(Op { code, ins, outs: vec![t], p }); + t + }; + let one = |m: i64, shift: u8, kind: BiasKind, bias: Vec, round: Round| Rq { + round, + bias_kind: kind, + m_kind: 1, + s_kind: 1, + m_bytes: 4, + b_bytes: if kind == BiasKind::None { 0 } else { 4 }, + mult: vec![m], + shift: vec![shift], + bias, + }; + let conv = |c: &Conv| Params::Conv { + cout: c.cout as u32, + kh: 3, + kw: 3, + sh: c.stride, + sw: c.stride, + ph: 1, + pw: 1, + groups: 1, + w_bytes: 2, + rq: conv_rq(&c.b), + w: c.w.iter().map(|&v| v as i64).collect(), + }; + let relu = Params::Clamp { lo: 0, hi: ACT_HI }; + let cv = &g.convs; + let (c1, c2, c3) = (g.c1 as u32, g.c2 as u32, g.c3 as u32); + let t3 = op(&mut tensors, opcode::RESCALE, vec![0], 3, Params::Rescale(one(256, 0, BiasKind::Pre, vec![-128; 3], Round::Floor))); + let t4 = op(&mut tensors, opcode::RESCALE, vec![1], 3, Params::Rescale(one(256, 0, BiasKind::Pre, vec![-128; 3], Round::Floor))); + let t5 = op(&mut tensors, opcode::RESCALE, vec![2], k, Params::Rescale(one(128, 0, BiasKind::None, vec![], Round::Floor))); + let x = op(&mut tensors, opcode::CONCAT, vec![t3, t4, t5], 6 + k, Params::None); + let a = op(&mut tensors, opcode::CONV, vec![x], c1, conv(&cv[0])); + let e1 = op(&mut tensors, opcode::CLAMP, vec![a], c1, relu.clone()); + let a = op(&mut tensors, opcode::CONV, vec![e1], c2, conv(&cv[1])); + let e2 = op(&mut tensors, opcode::CLAMP, vec![a], c2, relu.clone()); + let a = op(&mut tensors, opcode::CONV, vec![e2], c3, conv(&cv[2])); + let e3 = op(&mut tensors, opcode::CLAMP, vec![a], c3, relu.clone()); + let a = op(&mut tensors, opcode::CONV, vec![e3], c3, conv(&cv[3])); + let m1 = op(&mut tensors, opcode::CLAMP, vec![a], c3, relu.clone()); + let a = op(&mut tensors, opcode::CONV, vec![m1], c3, conv(&cv[4])); + let a = op(&mut tensors, opcode::ADD, vec![a, e3], c3, Params::None); + let r = op(&mut tensors, opcode::CLAMP, vec![a], c3, relu.clone()); + let a = op(&mut tensors, opcode::CONV, vec![r], 4 * c2, conv(&cv[5])); + let a = op(&mut tensors, opcode::PIXEL_SHUFFLE, vec![a], c2, Params::Shuffle { r: 2 }); + let u2 = op(&mut tensors, opcode::CLAMP, vec![a], c2, relu.clone()); + let a = op(&mut tensors, opcode::CONCAT, vec![u2, e2], 2 * c2, Params::None); + let a = op(&mut tensors, opcode::CONV, vec![a], c2, conv(&cv[6])); + let d2 = op(&mut tensors, opcode::CLAMP, vec![a], c2, relu.clone()); + let a = op(&mut tensors, opcode::CONV, vec![d2], 4 * c1, conv(&cv[7])); + let a = op(&mut tensors, opcode::PIXEL_SHUFFLE, vec![a], c1, Params::Shuffle { r: 2 }); + let u1 = op(&mut tensors, opcode::CLAMP, vec![a], c1, relu.clone()); + let a = op(&mut tensors, opcode::CONCAT, vec![u1, e1], 2 * c1, Params::None); + let a = op(&mut tensors, opcode::CONV, vec![a], c1, conv(&cv[8])); + let d1 = op(&mut tensors, opcode::CLAMP, vec![a], c1, relu); + let o = op(&mut tensors, opcode::CONV, vec![d1], 4, conv(&cv[9])); + let a = op(&mut tensors, opcode::SPLIT, vec![o], 3, Params::Split { start: 0, count: 3 }); + let gd = op(&mut tensors, opcode::CLAMP, vec![a], 3, Params::Clamp { lo: -32768, hi: 32767 }); + let a = op(&mut tensors, opcode::SPLIT, vec![o], 1, Params::Split { start: 3, count: 1 }); + let a = op(&mut tensors, opcode::CLAMP, vec![a], 1, Params::Clamp { lo: -32767, hi: 32767 }); + let m = op(&mut tensors, opcode::LUT16, vec![a], 1, Params::Lut16 { e_in: -128, e_out: -128, t: sigmoid_table() }); + let wm = op(&mut tensors, opcode::MUL, vec![t3, m], 3, Params::Mul(Rq::plain(Round::HalfUp, 15))); + let om = op(&mut tensors, opcode::RESCALE, vec![m], 1, Params::Rescale(one(-1, 0, BiasKind::Post, vec![32768], Round::Floor))); + let gm = op(&mut tensors, opcode::MUL, vec![gd, om], 3, Params::Mul(Rq::plain(Round::HalfUp, 15))); + let s = op(&mut tensors, opcode::ADD, vec![wm, gm], 3, Params::None); + let fin = Rq { round: Round::HalfUp, bias_kind: BiasKind::Post, m_kind: 0, s_kind: 1, m_bytes: 0, b_bytes: 4, mult: vec![], shift: vec![8], bias: vec![128; 3] }; + let a = op(&mut tensors, opcode::RESCALE, vec![s], 3, Params::Rescale(fin)); + let out = op(&mut tensors, opcode::CLAMP, vec![a], 3, Params::Clamp { lo: 0, hi: 255 }); + let input = |tensor| InputRec { tensor, max_h: 0, max_w: 0, kind: 0, src: 0, init: 0 }; + Graph { max_elems: CAP as u32, scalars: vec![], inputs: vec![input(0), input(1), input(2)], outputs: vec![out], tensors, ops } +} + +/// Why frames could not be made. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FaceError { + /// The generator does not read (§7.2). + Generator(FormError), + /// The graph did not load or the run refused (never for a generator that reads, on a + /// frame of its size). + Refused(String), + /// The generator, the identity prior and the segment do not agree on `K`, the width + /// and the height (§7.7). + Shape, +} + +impl std::fmt::Display for FaceError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FaceError::Generator(e) => write!(f, "the generator does not read: {e}"), + FaceError::Refused(c) => write!(f, "the generation is refused: {c}"), + FaceError::Shape => f.write_str("the generator, the identity and the keypoints disagree on K, the width or the height"), + } + } +} + +impl std::error::Error for FaceError {} + +/// A generator loaded as a sink loads it: read (§7.2), lowered (§7.6), and loaded from its +/// canonical bytes under `acc64` with its bounds certificate. +pub fn load(generator: &[u8]) -> Result<(Generator, Loaded), FaceError> { + let g = parse_generator(generator).map_err(FaceError::Generator)?; + let l = crate::int_ops::load(&graph(&g)).map_err(|r| FaceError::Refused(r.code.as_str().into()))?; + Ok((g, l)) +} + +/// Whether a generator's graph also meets `acc32` (integer-operators §7.2). +pub fn meets_acc32(generator: &[u8]) -> bool { + parse_generator(generator).is_ok_and(|g| wai_ops::graph::load(&graph(&g).to_bytes(), wai_ops::Profile::Acc32).is_ok()) +} + +fn planar(rgb: &[u8], w: usize, h: usize) -> Tensor { + let n = w * h; + let mut d = vec![0i64; 3 * n]; + for (i, px) in rgb.as_chunks::<3>().0.iter().enumerate() { + for c in 0..3 { + d[c * n + i] = px[c] as i64; + } + } + Tensor::new(3, h as u32, w as u32, d) +} + +fn interleaved(t: &Tensor) -> Vec { + let n = t.h as usize * t.w as usize; + let mut o = vec![0u8; 3 * n]; + for (k, &v) in t.data.iter().enumerate() { + o[(k % n) * 3 + k / n] = v as u8; + } + o +} + +fn shapes_agree(g: &Generator, id: &Identity, k: usize) -> bool { + g.k == id.k && g.k == k && g.width == id.width && g.height == id.height +} + +/// One frame (§7.7): the driver, then the graph. RGB8, rows top to bottom. +pub fn frame(g: &Generator, l: &Loaded, id: &Identity, drv: &[(i32, i32)]) -> Result, FaceError> { + if !shapes_agree(g, id, drv.len()) { + return Err(FaceError::Shape); + } + let (w, h) = (g.width, g.height); + let (warped, maps) = warp(id, drv, g.gauss, g.bg); + let heat = Tensor::new(g.k as u32, h as u32, w as u32, maps); + let out = crate::int_ops::run_graph(l, vec![planar(&warped, w, h), planar(&id.rgb, w, h), heat], &[]).map_err(|r| FaceError::Refused(r.code.as_str().into()))?; + Ok(interleaved(&out[0])) +} + +/// Every frame of a segment, in order (§7.7). +pub fn generate(g: &Generator, l: &Loaded, id: &Identity, seg: &Segment) -> Result>, FaceError> { + if !shapes_agree(g, id, seg.k) { + return Err(FaceError::Shape); + } + seg.frames.iter().map(|f| frame(g, l, id, f)).collect() +} + +/// A segment's decode in face-equivalence's canonical form (§7.7): its frames' RGB8 +/// buffers, one after another. +pub fn canonical(frames: &[Vec]) -> Vec { + frames.concat() +} + +/// §7.6 written out loop by loop over the read generator, with no graph and no operator set: +/// the arithmetic the extension states, which the tests hold the graph to. +pub fn direct(g: &Generator, id: &Identity, drv: &[(i32, i32)]) -> Vec { + let (w, h) = (g.width, g.height); + let (warped, maps) = warp(id, drv, g.gauss, g.bg); + let half_up = |p: i64, s: u32| if s == 0 { p } else { (p + (1 << (s - 1))) >> s }; + // Planes: (channels, h, w, data). + type Planes = (usize, usize, usize, Vec); + let conv = |x: &Planes, c: &Conv| -> Planes { + let (cin, hh, ww, ref d) = *x; + let s = c.stride as usize; + let (ho, wo) = ((hh - 1) / s + 1, (ww - 1) / s + 1); + let mut out = vec![0i64; c.cout * ho * wo]; + for o in 0..c.cout { + for yo in 0..ho { + for xo in 0..wo { + let mut acc = c.b[o] as i64; + for ci in 0..cin { + for ky in 0..3 { + for kx in 0..3 { + let (yy, xx) = ((yo * s + ky) as i64 - 1, (xo * s + kx) as i64 - 1); + if yy < 0 || xx < 0 || yy >= hh as i64 || xx >= ww as i64 { + continue; + } + acc += c.w[((o * cin + ci) * 3 + ky) * 3 + kx] as i64 * d[(ci * hh + yy as usize) * ww + xx as usize]; + } + } + } + out[(o * ho + yo) * wo + xo] = half_up(acc, FW as u32); + } + } + } + (c.cout, ho, wo, out) + }; + let relu = |x: Planes| -> Planes { (x.0, x.1, x.2, x.3.into_iter().map(|v| v.clamp(0, ACT_HI)).collect()) }; + let cat = |a: &Planes, b: &Planes| -> Planes { (a.0 + b.0, a.1, a.2, [a.3.clone(), b.3.clone()].concat()) }; + let shuffle = |x: Planes| -> Planes { + let (c4, hh, ww, d) = x; + let c = c4 / 4; + let mut out = vec![0i64; c4 * hh * ww]; + for co in 0..c { + for y in 0..hh { + for xx in 0..ww { + for i in 0..2 { + for j in 0..2 { + out[(co * 2 * hh + 2 * y + i) * 2 * ww + 2 * xx + j] = d[((co * 4 + i * 2 + j) * hh + y) * ww + xx]; + } + } + } + } + } + (c, 2 * hh, 2 * ww, out) + }; + let n = w * h; + let centred = |rgb: &[u8]| -> Planes { (3, h, w, (0..3 * n).map(|i| (rgb[(i % n) * 3 + i / n] as i64 - 128) * 256).collect()) }; + let t3 = centred(&warped); + let t4 = centred(&id.rgb); + let t5: Planes = (g.k, h, w, maps.iter().map(|v| v * 128).collect()); + let x = cat(&cat(&t3, &t4), &t5); + let cv = &g.convs; + let e1 = relu(conv(&x, &cv[0])); + let e2 = relu(conv(&e1, &cv[1])); + let e3 = relu(conv(&e2, &cv[2])); + let m1 = relu(conv(&e3, &cv[3])); + let m2 = conv(&m1, &cv[4]); + let r = relu((m2.0, m2.1, m2.2, m2.3.iter().zip(&e3.3).map(|(a, b)| a + b).collect())); + let u2 = relu(shuffle(conv(&r, &cv[5]))); + let d2 = relu(conv(&cat(&u2, &e2), &cv[6])); + let u1 = relu(shuffle(conv(&d2, &cv[7]))); + let d1 = relu(conv(&cat(&u1, &e1), &cv[8])); + let o = conv(&d1, &cv[9]); + let table = sigmoid_table(); + let mut out = vec![0u8; 3 * n]; + for i in 0..n { + let z = o.3[3 * n + i].clamp(-32767, 32767); + let m = table[(z + 32768) as usize] as i64; + for c in 0..3 { + let gd = o.3[c * n + i].clamp(-32768, 32767); + let a = half_up(t3.3[c * n + i] * m, 15); + let b = half_up(gd * (32768 - m), 15); + out[i * 3 + c] = (half_up(a + b, 8) + 128).clamp(0, 255) as u8; + } + } + out +} + +// --------------------------------------------------------------------------------------- +// §8 Records +// --------------------------------------------------------------------------------------- + +/// Why a case is refused (§10): this extension's own codes. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Reason { + RecordMalformed, + BadKey, + BadSignature, + IdentityInvalid, + IdentityUnknown, + DriverKeyMismatch, + TrackChainBroken, + ObjectUnknown, + PayloadInvalid, + GeneratorMismatch, + IdentityMismatch, + GeneratorInvalid, + ShapeMismatch, + FrameMismatch, + GenerationNotProved, + ClaimMissing, + ClaimMismatch, +} + +impl Reason { + /// Every reason, in §10's order. + pub const ALL: [Reason; 17] = [ + Reason::RecordMalformed, + Reason::BadKey, + Reason::BadSignature, + Reason::IdentityInvalid, + Reason::IdentityUnknown, + Reason::DriverKeyMismatch, + Reason::TrackChainBroken, + Reason::ObjectUnknown, + Reason::PayloadInvalid, + Reason::GeneratorMismatch, + Reason::IdentityMismatch, + Reason::GeneratorInvalid, + Reason::ShapeMismatch, + Reason::FrameMismatch, + Reason::GenerationNotProved, + Reason::ClaimMissing, + Reason::ClaimMismatch, + ]; + + pub fn code(self) -> &'static str { + match self { + Reason::RecordMalformed => "record_malformed", + Reason::BadKey => "bad_key", + Reason::BadSignature => "bad_signature", + Reason::IdentityInvalid => "identity_invalid", + Reason::IdentityUnknown => "identity_unknown", + Reason::DriverKeyMismatch => "driver_key_mismatch", + Reason::TrackChainBroken => "track_chain_broken", + Reason::ObjectUnknown => "object_unknown", + Reason::PayloadInvalid => "payload_invalid", + Reason::GeneratorMismatch => "generator_mismatch", + Reason::IdentityMismatch => "identity_mismatch", + Reason::GeneratorInvalid => "generator_invalid", + Reason::ShapeMismatch => "shape_mismatch", + Reason::FrameMismatch => "frame_mismatch", + Reason::GenerationNotProved => "generation_not_proved", + Reason::ClaimMissing => "claim_missing", + Reason::ClaimMismatch => "claim_mismatch", + } + } +} + +impl std::fmt::Display for Reason { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.code()) + } +} + +fn sha256(parts: &[&[u8]]) -> [u8; 32] { + let mut h = Sha256::new(); + for p in parts { + h.update(p); + } + h.finalize().into() +} + +pub fn hex(b: &[u8]) -> String { + b.iter().map(|x| format!("{x:02x}")).collect() +} + +fn unhex(s: &str) -> Option> { + if !s.len().is_multiple_of(2) || !s.bytes().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) { + return None; + } + (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok()).collect() +} + +fn hexn(v: &Value) -> Option<[u8; N]> { + unhex(v.as_str()?)?.try_into().ok() +} + +fn int(v: &Value) -> Option { + v.as_u64().filter(|&n| n <= MAX_INTEGER) +} + +fn subset_ok(v: &Value) -> bool { + match v { + Value::Null | Value::String(_) => true, + Value::Number(_) => int(v).is_some(), + Value::Bool(_) => false, + Value::Array(a) => a.iter().all(subset_ok), + Value::Object(o) => o.values().all(subset_ok), + } +} + +fn exact<'a>(v: &'a Value, members: &[&str]) -> Option<&'a Map> { + let o = v.as_object()?; + (o.len() == members.len() && members.iter().all(|m| o.contains_key(*m))).then_some(o) +} + +/// The bytes a record is signed over (§8.1). +pub fn signing_bytes(record: &Value) -> Vec { + let mut r = record.clone(); + if let Some(o) = r.as_object_mut() { + o.remove("sig"); + } + let mut m = SIGN_DOMAIN.to_vec(); + m.extend_from_slice(jcs(&r).expect("a record in the subset").as_bytes()); + m +} + +/// A record's link hash (§8.1). +pub fn link_hash(record: &Value) -> [u8; 32] { + let mut h = blake3::Hasher::new(); + h.update(LINK_DOMAIN); + h.update(jcs(record).expect("a record in the subset").as_bytes()); + *h.finalize().as_bytes() +} + +/// Sign a record body: adds `signer_pubkey` and `sig` (§8.1). +pub fn sign(mut body: Value, key: &SigningKey) -> Value { + let o = body.as_object_mut().expect("a record is an object"); + o.insert("signer_pubkey".into(), json!(hex(&key.verifying_key().to_bytes()))); + o.remove("sig"); + let sig = key.sign(&signing_bytes(&body)); + body.as_object_mut().expect("an object").insert("sig".into(), json!(hex(&sig.to_bytes()))); + body +} + +fn read_record(text: &str, kind: &str, members: &[&str]) -> Result<(Value, [u8; 32], [u8; 32]), Reason> { + let v = crate::claim_text::read(text).map_err(|_| Reason::RecordMalformed)?; + let o = exact(&v, members).ok_or(Reason::RecordMalformed)?; + if o.get("kind").and_then(Value::as_str) != Some(kind) || !subset_ok(&v) { + return Err(Reason::RecordMalformed); + } + let signer: [u8; 32] = hexn(&o["signer_pubkey"]).ok_or(Reason::RecordMalformed)?; + if hexn::<64>(&o["sig"]).is_none() || !o["issued_at"].as_str().is_some_and(rfc3339) { + return Err(Reason::RecordMalformed); + } + Ok((v.clone(), signer, link_hash(&v))) +} + +fn check_signature(v: &Value, signer: &[u8; 32]) -> Result<(), Reason> { + let sig: [u8; 64] = hexn(&v["sig"]).ok_or(Reason::RecordMalformed)?; + crate::sig::verify_strict(signer, &signing_bytes(v), &sig).map_err(|e| match e { + crate::sig::SigError::BadKey => Reason::BadKey, + crate::sig::SigError::BadSignature => Reason::BadSignature, + }) +} + +pub const IDENTITY_MEMBERS: [&str; 5] = ["kind", "identity", "issued_at", "signer_pubkey", "sig"]; +pub const TRACK_MEMBERS: [&str; 9] = ["kind", "object", "identity", "seq", "prev", "frames", "issued_at", "signer_pubkey", "sig"]; + +/// An identity record, read (§8.2). +#[derive(Clone, Debug)] +pub struct IdentityRecord { + pub link: [u8; 32], + pub signer: [u8; 32], + /// The SHA-256 of the identity prior. + pub identity: [u8; 32], +} + +/// Read an identity record's text (§8.2, §8.1). +pub fn read_identity_record(text: &str) -> Result { + let (v, signer, link) = read_record(text, "wai-face-identity", &IDENTITY_MEMBERS)?; + let identity = hexn(&v["identity"]).ok_or(Reason::RecordMalformed)?; + check_signature(&v, &signer)?; + Ok(IdentityRecord { link, signer, identity }) +} + +/// A track record, read (§8.3). +#[derive(Clone, Debug)] +pub struct TrackRecord { + pub link: [u8; 32], + pub signer: [u8; 32], + pub object: [u8; 32], + pub identity: [u8; 32], + pub seq: u64, + pub prev: Option<[u8; 32]>, + pub frames: Option>, +} + +/// Read a track record's text (§8.3, §8.1). +pub fn read_track_record(text: &str) -> Result { + let (v, signer, link) = read_record(text, "wai-face-track", &TRACK_MEMBERS)?; + let fields = || -> Option { + let frames = match &v["frames"] { + Value::Null => None, + Value::Array(a) if (1..=MAX_FRAMES).contains(&a.len()) => Some(a.iter().map(hexn).collect::>>()?), + _ => return None, + }; + Some(TrackRecord { + link, + signer, + object: hexn(&v["object"])?, + identity: hexn(&v["identity"])?, + seq: int(&v["seq"])?, + prev: if v["prev"].is_null() { None } else { Some(hexn(&v["prev"])?) }, + frames, + }) + }; + let t = fields().ok_or(Reason::RecordMalformed)?; + check_signature(&v, &signer)?; + Ok(t) +} + +// --------------------------------------------------------------------------------------- +// §9 Verification +// --------------------------------------------------------------------------------------- + +/// A verifier's policy (§9.1): synthesis's members and `generation`. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct Policy { + pub synthesis: synthesis::Policy, + /// `generation` `require-proof`: every segment's frames are recomputed. + pub require_proof: bool, +} + +/// What a verifier is given (§9.1). +#[derive(Clone, Debug, Default)] +pub struct Case { + pub identities: Vec, + pub tracks: Vec, + pub claims: Vec, + pub reviews: Vec, + pub label_policy: Option, + pub player: Vec, + /// Envelopes, each named by its content hash. + pub objects: Vec>, + /// Generators and identity priors, each named by its SHA-256. + pub generators: Vec>, + pub priors: Vec>, + pub policy: Policy, +} + +/// Where a case is refused (§9.2). +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum At { + Identity(usize), + Track(usize), + Synthesis(synthesis::At), +} + +impl At { + pub fn to_json(&self) -> Value { + match self { + At::Identity(i) => json!(["identity", i]), + At::Track(i) => json!(["track", i]), + At::Synthesis(a) => json!(["synthesis", a.to_json()]), + } + } +} + +/// A reason of this extension's, or one synthesis raises (§10). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Why { + Own(Reason), + Synthesis(synthesis::Reason), +} + +impl Why { + pub fn code(self) -> &'static str { + match self { + Why::Own(r) => r.code(), + Why::Synthesis(r) => r.code(), + } + } +} + +/// What an accepted case states (§9.3). +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Report { + pub identities: Vec, + pub tracks: Vec, + pub claims: Vec, + pub labels: Vec, + pub reviews_presented: Vec, + pub visual_icon: Option, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Outcome { + Accept(Report), + Refuse { at: At, reason: Why }, +} + +/// The manifest and payload of a `WAI1` envelope, or `None`. +fn envelope_parts(b: &[u8]) -> Option<(Value, &[u8])> { + if b.len() < 8 || &b[..4] != b"WAI1" { + return None; + } + let n = u32::from_le_bytes(b[4..8].try_into().ok()?) as usize; + let m: Value = serde_json::from_slice(b.get(8..8 + n)?).ok()?; + let at = 8 + n; + let len = u32::from_le_bytes(b.get(at..at + 4)?.try_into().ok()?) as usize; + let payload = b.get(at + 4..at + 4 + len)?; + (at + 4 + len == b.len()).then_some((m, payload)) +} + +/// Verify a case (§9.2). +pub fn verify(case: &Case) -> Outcome { + match run(case) { + Ok(r) => Outcome::Accept(r), + Err((at, reason)) => Outcome::Refuse { at, reason }, + } +} + +fn own(at: At, r: Reason) -> (At, Why) { + (at, Why::Own(r)) +} + +type Steps = Result; + +fn run(case: &Case) -> Steps { + let mut report = Report::default(); + // Step 1: reading. + let ids: Vec = + case.identities.iter().enumerate().map(|(i, t)| read_identity_record(t).map_err(|r| own(At::Identity(i), r))).collect::>()?; + let tracks: Vec = case.tracks.iter().enumerate().map(|(i, t)| read_track_record(t).map_err(|r| own(At::Track(i), r))).collect::>()?; + let objects: BTreeMap<[u8; 32], &Vec> = case.objects.iter().map(|o| (crate::jwp_hash::content_hash(o), o)).collect(); + let generators: BTreeMap<[u8; 32], &Vec> = case.generators.iter().map(|p| (sha256(&[p]), p)).collect(); + let priors: BTreeMap<[u8; 32], &Vec> = case.priors.iter().map(|p| (sha256(&[p]), p)).collect(); + + // Step 2: the identity records. + let mut held: BTreeMap<[u8; 32], Identity> = BTreeMap::new(); + for (i, r) in ids.iter().enumerate() { + let state = match priors.get(&r.identity) { + Some(b) => { + held.insert(r.identity, parse_identity(b).map_err(|_| own(At::Identity(i), Reason::IdentityInvalid))?); + "read" + } + None => "not-held", + }; + report.identities.push(json!({"link": hex(&r.link), "identity": hex(&r.identity), "prior": state})); + } + let by_link: BTreeMap<[u8; 32], &IdentityRecord> = ids.iter().map(|r| (r.link, r)).collect(); + + // Step 3: the track records. + let mut decoded: Vec<([u8; 32], Vec)> = Vec::new(); + let mut last: BTreeMap<[u8; 32], (u64, [u8; 32])> = BTreeMap::new(); + let mut loaded: BTreeMap<[u8; 32], (Generator, Loaded)> = BTreeMap::new(); + let mut generator_of: BTreeMap<[u8; 32], [u8; 32]> = BTreeMap::new(); + for (i, t) in tracks.iter().enumerate() { + let e = |r| own(At::Track(i), r); + let id = by_link.get(&t.identity).ok_or(e(Reason::IdentityUnknown))?; + if t.signer != id.signer { + return Err(e(Reason::DriverKeyMismatch)); + } + let chained = match last.get(&t.identity) { + None => t.seq == 0 && t.prev.is_none(), + Some((seq, link)) => t.seq == seq + 1 && t.prev == Some(*link), + }; + if !chained { + return Err(e(Reason::TrackChainBroken)); + } + last.insert(t.identity, (t.seq, t.link)); + let env = objects.get(&t.object).ok_or(e(Reason::ObjectUnknown))?; + let (manifest, payload) = envelope_parts(env).ok_or(e(Reason::PayloadInvalid))?; + if manifest["model_requirement"]["capability"] != CAPABILITY { + return Err(e(Reason::PayloadInvalid)); + } + let seg = parse_segment(payload).map_err(|_| e(Reason::PayloadInvalid))?; + let pin = manifest["conditioning"]["prior"]["sha256"].as_str().and_then(unhex); + if pin.as_deref() != Some(&seg.generator[..]) { + return Err(e(Reason::GeneratorMismatch)); + } + if seg.identity != id.identity { + return Err(e(Reason::IdentityMismatch)); + } + generator_of.insert(t.object, seg.generator); + if let Some(b) = generators.get(&seg.generator) + && !loaded.contains_key(&seg.generator) + { + let (g, l) = match load(b) { + Ok(x) => x, + Err(_) => return Err(e(Reason::GeneratorInvalid)), + }; + loaded.insert(seg.generator, (g, l)); + } + let made = match (loaded.get(&seg.generator), held.get(&id.identity)) { + (Some((g, l)), Some(prior)) => { + let frames = generate(g, l, prior, &seg).map_err(|err| match err { + FaceError::Shape => e(Reason::ShapeMismatch), + _ => e(Reason::GeneratorInvalid), + })?; + if let Some(stated) = &t.frames { + let digests: Vec<[u8; 32]> = frames.iter().map(|f| sha256(&[f])).collect(); + if *stated != digests { + return Err(e(Reason::FrameMismatch)); + } + } + Some(canonical(&frames)) + } + _ => None, + }; + if case.policy.require_proof && made.is_none() { + return Err(e(Reason::GenerationNotProved)); + } + report.tracks.push(json!({ + "link": hex(&t.link), + "object": hex(&t.object), + "frames": seg.frames.len(), + "status": if made.is_some() { "proved" } else { "not-recomputed" }, + "output": made.as_ref().map(|m| hex(&sha256(&[m]))), + })); + if let Some(m) = made { + decoded.push((t.object, m)); + } + } + + // Step 4: synthesis, over the claims, the objects, the frames recomputed, the reviews, the + // label policy and the player's log. + let syn = synthesis::Case { + claims: case.claims.clone(), + reviews: case.reviews.clone(), + label_policy: case.label_policy.clone(), + player: case.player.clone(), + objects: case.objects.clone(), + decoded, + policy: case.policy.synthesis, + ..Default::default() + }; + match synthesis::verify(&syn) { + synthesis::Outcome::Refuse { at, reason } => return Err((At::Synthesis(at), Why::Synthesis(reason))), + synthesis::Outcome::Accept(r) => { + report.claims = r.claims.iter().map(|c| json!({"link": c["link"], "class": c["class"]})).collect(); + report.labels = r.labels; + report.reviews_presented = r.reviews_presented; + report.visual_icon = r.visual_icon; + } + } + + // Step 5: each segment's claim. + let claims: Vec = case.claims.iter().map(|t| synthesis::read_origin(t).expect("synthesis read every claim")).collect(); + for (i, t) in tracks.iter().enumerate() { + let e = |r| own(At::Track(i), r); + let mine: Vec<&synthesis::Origin> = claims.iter().filter(|c| c.subject == Subject::Object(t.object)).collect(); + let c = mine.first().ok_or(e(Reason::ClaimMissing))?; + let gd = generator_of[&t.object]; + let ok = c.class == Class::Synthesized && c.step == Step::Generate && c.models.iter().any(|m| m.digest == gd) && c.record == Some(t.link); + if !ok { + return Err(e(Reason::ClaimMismatch)); + } + } + Ok(report) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn tiny() -> (Vec, Identity) { + let gd = seeded_generator(b"tests", 3, 8, 8, 2, 3, 4, 4096, 6554); + let rgb = (0..8 * 8 * 3).map(|i| (i * 37 % 251) as u8).collect(); + (gd, Identity { k: 3, width: 8, height: 8, keypoints: vec![(40, 40), (90, 50), (64, 100)], rgb }) + } + + #[test] + fn the_graph_is_the_arithmetic_of_section_7_6() { + let (gd, id) = tiny(); + let (g, l) = load(&gd).unwrap(); + for drv in [vec![(40, 40), (90, 50), (64, 100)], vec![(48, 36), (96, 58), (60, 110)], vec![(0, 0), (127, 127), (-30, 200)]] { + assert_eq!(frame(&g, &l, &id, &drv).unwrap(), direct(&g, &id, &drv), "{drv:?}"); + } + } + + #[test] + fn the_warp_at_rest_is_the_reference() { + let (gd, id) = tiny(); + let g = parse_generator(&gd).unwrap(); + let (w, maps) = warp(&id, &id.keypoints, g.gauss, g.bg); + assert_eq!(w, id.rgb, "keypoints where the reference has them move nothing"); + assert!(maps.iter().all(|&v| (0..=256).contains(&v))); + assert_eq!(maps.iter().max(), Some(&256), "the map at a keypoint on a pixel centre"); + } + + #[test] + fn the_sigmoid_table_is_symmetric_and_bounded() { + let t = sigmoid_table(); + assert_eq!(t[0], 0); + assert_eq!(t[32768], 16384); + for v in 1..=32767usize { + assert_eq!(t[32768 + v] as i32 + t[32768 - v] as i32, 32768, "{v}"); + assert!((16384..=32767).contains(&t[32768 + v]), "{v}"); + } + assert_eq!(t[32769], 16384, "the exponential's excess over 1 at 0 is clamped away"); + } + + #[test] + fn files_round_trip_and_refuse_in_order() { + let (gd, id) = tiny(); + let g = parse_generator(&gd).unwrap(); + assert_eq!(write_generator(&g), gd); + let idb = write_identity(&id); + assert_eq!(parse_identity(&idb).unwrap(), id); + assert_eq!(parse_generator(b"WFG2"), Err(FormError::BadMagic)); + assert_eq!(parse_generator(&[&gd[..4], &[2]].concat()), Err(FormError::Version)); + assert_eq!(parse_generator(&gd[..gd.len() - 1]), Err(FormError::Truncated)); + assert_eq!(parse_generator(&[&gd[..], &[0]].concat()), Err(FormError::Trailing)); + let mut bad = gd.clone(); + bad[6] = 6; + assert_eq!(parse_generator(&bad), Err(FormError::FieldRange), "a width not a multiple of 4"); + let frames: Vec> = (0..5).map(|f| vec![(40 + 4 * f, 40), (90, 50 - 8 * f), (64 + 12 * f, 100)]).collect(); + let seg = write_segment(&frames, 2, &[1; 32], &[2; 32], 7, (25, 1)).unwrap(); + let s = parse_segment(&seg).unwrap(); + assert_eq!((s.frames.clone(), s.first, s.step), (frames.clone(), 7, 2)); + assert_eq!(parse_segment(&[&seg[..], &[0]].concat()), Err(FormError::Trailing)); + assert_eq!(parse_segment(&seg[..seg.len() - 1]), Err(FormError::Truncated)); + assert_eq!(write_segment(&frames, 3, &[1; 32], &[2; 32], 7, (25, 1)), None, "not multiples of the step"); + } + + #[test] + fn padding_bits_are_zero() { + let frames: Vec> = vec![vec![(0, 0)], vec![(16, 0)]]; + let seg = write_segment(&frames, 16, &[0; 32], &[0; 32], 0, (25, 1)).unwrap(); + let mut bad = seg.clone(); + *bad.last_mut().unwrap() |= 1; + assert_eq!(parse_segment(&bad), Err(FormError::Noncanonical)); + } +} diff --git a/wai/wai-rs/src/avatar_face/corpus.rs b/wai/wai-rs/src/avatar_face/corpus.rs new file mode 100644 index 000000000..199fa8e5f --- /dev/null +++ b/wai/wai-rs/src/avatar_face/corpus.rs @@ -0,0 +1,962 @@ +//! The conformance corpus of extensions/avatars.md §12: `live-conformance/avatar-face/`. +//! +//! [`generate`] writes it from fixed seeds and the inputs it reads, each pinned here by its +//! SHA-256 ([`INPUTS`]): the trained generator `generator.wfg`, two identity priors of faces +//! held out of its training and their keypoint tracks, and the training tool's report, all +//! written by `tools/wai_avatar_face_train.py` (§7.9). Every key is SHA-256 of this corpus's +//! label and an index, the seeded generator and identity are integer functions of a label, +//! every time is a constant, and Ed25519 signing is deterministic, so every file is the same +//! bytes on every machine. Each case states the outcome it was written to have; the generator +//! asserts that this module reaches it before writing it, and that the corpus gives every +//! reason of §10. +//! +//! [`verify`] checks a corpus directory's files against this module. +//! `live-conformance/avatar_face_verify.py` checks the same files from the extension's text, +//! sharing no code with this module. + +use super::*; +use crate::synthesis::EU_ICONS; + +const ISSUED: &str = "2026-10-22T09:00:00Z"; + +const FIRST: u8 = 0; +const SECOND: u8 = 1; +const PLAYER: u8 = 2; +const DEPLOYER: u8 = 3; + +/// The inputs the corpus reads, each a file of the corpus directory pinned by its SHA-256. +pub const INPUTS: [(&str, &str); 6] = [ + ("generator.wfg", "2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6"), + ("identities/face1.wfi", "42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c"), + ("identities/face2.wfi", "d87d24a2e5200d7c7cdc83215a73b649f3ac8fbdc6922949ef211ed9dc1c5e1b"), + ("tracks/face1.json", "d33cf7f64b54dee34f6a25635ab31970fe6ce96a1de1a77abbc8f4df0bd9dc64"), + ("tracks/face2.json", "6feb10c037d4b09b4fba1f23b9b8f35957248b01d2f4039f970bfbe7c1f5ccda"), + ("training.json", "464d28f404d74d8a7b3569836a1ea447c702ce7e0fd227195a729b4e2ab60744"), +]; +/// The files the generator writes besides `vectors.json`. +pub const SEEDED_GENERATOR: &str = "seeded.wfg"; +pub const SEEDED_IDENTITY: &str = "seeded.wfi"; +/// The step of every segment the corpus writes from a track: a quarter of a pixel. +pub const STEP: u32 = 4; + +fn sha(parts: &[&[u8]]) -> [u8; 32] { + sha256(parts) +} + +fn key(i: u8) -> SigningKey { + SigningKey::from_bytes(&sha(&[b"wai:avatar/corpus/key\x00", &[i]])) +} + +fn label(s: &str) -> [u8; 32] { + sha(&[b"wai:avatar/corpus/label\x00", s.as_bytes()]) +} + +fn text(v: &Value) -> String { + jcs(v).expect("a record in the subset") +} + +fn ch(env: &[u8]) -> [u8; 32] { + crate::jwp_hash::content_hash(env) +} + +fn sx(b: &[u8]) -> String { + hex(&sha(&[b])) +} + +/// The keypoint maps' canonical bytes, for their digest: each value a 16-bit little-endian +/// unsigned integer, in `[K][h][w]` order. +pub fn maps_bytes(maps: &[i64]) -> Vec { + maps.iter().flat_map(|&v| (v as u16).to_le_bytes()).collect() +} + +/// The seeded generator: `K` 3, 16 × 16, 4 channels at every width. +pub fn seeded_generator_file() -> Vec { + seeded_generator(b"wai:avatar/corpus/seeded generator", 3, 16, 16, 4, 4, 4, 2048, 6554) +} + +/// The seeded identity: a 16 × 16 picture, every sample an integer function of the label and +/// the pixel's position, and three keypoints. +pub fn seeded_identity_file() -> Vec { + let k = label("seeded identity"); + let (w, h) = (16usize, 16usize); + let mut rgb = Vec::with_capacity(w * h * 3); + for y in 0..h { + for x in 0..w { + for c in 0..3 { + let (a, b) = (k[c] as usize, k[3 + c] as usize); + let mut v = (a * (w + h - 2 - x - y) + b * (x + y)) / (w + h - 2); + let (dx, dy) = (x as i64 - 8, y as i64 - 7); + if dx * dx + 2 * dy * dy < 20 { + v = k[6 + c] as usize; + } + if (x + 3 * y + k[9] as usize).is_multiple_of(5) { + v = k[10 + c] as usize; + } + rgb.push(v as u8); + } + } + } + write_identity(&Identity { k: 3, width: w, height: h, keypoints: vec![(88, 104), (168, 96), (128, 180)], rgb }) +} + +/// The micro generator of the form vectors: `K` 1, 4 × 4, one channel at every width. +fn micro_generator() -> Vec { + seeded_generator(b"wai:avatar/corpus/micro generator", 1, 4, 4, 1, 1, 1, 1, 1) +} + +/// A track's keypoints from its JSON input, each rounded to the corpus's step, ties up. +fn track_frames(json_bytes: &[u8]) -> Vec> { + let v: Value = serde_json::from_slice(json_bytes).expect("a track input reads"); + let q = |c: i64| ((c + STEP as i64 / 2).div_euclid(STEP as i64) * STEP as i64) as i32; + v["keypoints"] + .as_array() + .expect("keypoints") + .iter() + .map(|f| f.as_array().expect("a frame").iter().map(|p| (q(p[0].as_i64().expect("x")), q(p[1].as_i64().expect("y")))).collect()) + .collect() +} + +// ---- envelopes and records ---- + +fn envelope(generator: &[u8; 32], payload: &[u8]) -> Vec { + let manifest = format!( + r#"{{"wai":"1.0","media":"interactive","intent":"create","model_requirement":{{"capability":"{CAPABILITY}","fallback":null}},"conditioning":{{"kind":"face","prior":{{"id":"face generator","sha256":"{}","determinism":"face-int/1 over wai-ops/1"}}}}}}"#, + hex(generator) + ); + let mut b = b"WAI1".to_vec(); + b.extend_from_slice(&(manifest.len() as u32).to_le_bytes()); + b.extend_from_slice(manifest.as_bytes()); + b.extend_from_slice(&(payload.len() as u32).to_le_bytes()); + b.extend_from_slice(payload); + b +} + +fn identity_record(who: u8, identity: &[u8; 32]) -> Value { + sign(json!({"kind": "wai-face-identity", "identity": hex(identity), "issued_at": ISSUED}), &key(who)) +} + +fn track_body(object: &[u8], identity: &Value, seq: u64, prev: Option<&Value>, frames: Option<&[Vec]>) -> Value { + json!({ + "kind": "wai-face-track", + "object": hex(&ch(object)), + "identity": hex(&link_hash(identity)), + "seq": seq, + "prev": prev.map(|p| hex(&link_hash(p))), + "frames": frames.map(|fs| fs.iter().map(|f| sx(f)).collect::>()), + "issued_at": ISSUED, + }) +} + +fn claim_body(object: &[u8], track: Option<&Value>, models: &[[u8; 32]]) -> Value { + json!({ + "kind": "wai-origin", + "subject": {"type": "object", "content_hash": hex(&ch(object))}, + "media": "video", + "class": "synthesized", + "step": "generate", + "operation": CAPABILITY, + "record": track.map(|t| hex(&link_hash(t))), + "source": "trainedAlgorithmicMedia", + "models": models.iter().map(|d| json!({"digest": hex(d), "type": "trained"})).collect::>(), + "oversight": "prompt_guided", + "inputs": [], + "evidence": null, + "issued_at": ISSUED, + }) +} + +fn icon(name: &str) -> String { + EU_ICONS.iter().find(|(n, _)| *n == name).expect("a pinned icon").1.to_string() +} + +fn label_policy() -> Value { + synthesis::sign( + json!({ + "kind": "wai-label-policy", + "classes": ["synthesized"], + "visual": icon("LABEL_AI GENERATED_black.svg"), + "audio": null, + "interval_us": 10_000_000u64, + "grace_us": 500_000u64, + "issued_at": ISSUED, + }), + &key(DEPLOYER), + ) +} + +/// A player's log being written (synthesis §10.3). +struct Log { + entries: Vec, +} + +impl Log { + fn new() -> Log { + Log { entries: vec![] } + } + + fn push(mut self, at: u64, entry: Value) -> Log { + let prev = self.entries.last().map(|e| json!(hex(&synthesis::link_hash(e)))).unwrap_or(Value::Null); + let e = synthesis::sign( + json!({"kind": "wai-player", "session": hex(&label("player session")), "seq": self.entries.len(), "prev": prev, "at": at, "entry": entry}), + &key(PLAYER), + ); + self.entries.push(e); + self + } + + fn texts(&self) -> Vec { + self.entries.iter().map(text).collect() + } +} + +fn present(claim: &Value, output: Option<[u8; 32]>) -> Value { + json!({ + "type": "present", + "subject": claim["subject"]["content_hash"], + "origin": hex(&synthesis::link_hash(claim)), + "class": "synthesized", + "media": "video", + "tier": "generation-equivalence", + "decoder": CAPABILITY, + "variant": null, + "output": output.map(|o| hex(&o)), + }) +} + +fn shown(trigger: &str) -> Value { + json!({"type": "shown", "label": "visual", "asset": icon("LABEL_AI GENERATED_black.svg"), "trigger": trigger}) +} + +fn with(mut body: Value, path: &[&str], v: Value) -> Value { + let mut at = &mut body; + for p in &path[..path.len() - 1] { + at = at.get_mut(*p).expect("a member"); + } + at[path[path.len() - 1]] = v; + body +} + +fn unsigned(v: &Value) -> Value { + let mut b = v.clone(); + let o = b.as_object_mut().expect("an object"); + o.remove("sig"); + o.remove("signer_pubkey"); + b +} + +// ---- the corpus's material ---- + +/// A face driven by a track: its prior, its identity record and its segments. +struct Face { + who: u8, + prior: Vec, + digest: [u8; 32], + record: Value, + frames: Vec>, +} + +impl Face { + fn new(who: u8, prior: &[u8], track: &[u8]) -> Face { + let digest = sha(&[prior]); + Face { who, prior: prior.to_vec(), digest, record: identity_record(who, &digest), frames: track_frames(track) } + } +} + +/// One segment of a face: its payload, its envelope, its frames as the generator gives them. +#[derive(Clone)] +struct Seg { + payload: Vec, + env: Vec, + frames: Vec>, +} + +impl Seg { + fn output(&self) -> [u8; 32] { + sha(&[&canonical(&self.frames)]) + } +} + +enum Intent { + Accept, + Refuse(At, Why), +} + +/// Where a case's generators and priors come from: a file of the corpus, or bytes. +#[derive(Clone)] +enum Held { + File(&'static str), + Bytes(Vec), +} + +struct Vector { + name: &'static str, + about: &'static str, + case: Case, + generators: Vec, + priors: Vec, + intent: Intent, +} + +fn bytes_of(h: &Held, files: &BTreeMap>) -> Vec { + match h { + Held::File(n) => files.get(*n).unwrap_or_else(|| panic!("the corpus file {n}")).clone(), + Held::Bytes(b) => b.clone(), + } +} + +fn held_json(h: &Held) -> Value { + match h { + Held::File(n) => json!({"file": n}), + Held::Bytes(b) => json!({"hex": hex(b)}), + } +} + +fn vectors(files: &BTreeMap>) -> Vec { + let gen_bytes = &files["generator.wfg"]; + let gd = sha(&[gen_bytes]); + let (g, l) = load(gen_bytes).expect("the trained generator loads"); + let f1 = Face::new(FIRST, &files["identities/face1.wfi"], &files["tracks/face1.json"]); + let f2 = Face::new(SECOND, &files["identities/face2.wfi"], &files["tracks/face2.json"]); + let seg = |face: &Face, range: std::ops::Range, generator: [u8; 32], identity: [u8; 32]| -> Seg { + let kp = &face.frames[range.clone()]; + let payload = write_segment(kp, STEP, &generator, &identity, range.start as u32, (25, 1)).expect("a segment"); + let prior = parse_identity(&face.prior).expect("a prior reads"); + let frames = kp.iter().map(|f| frame(&g, &l, &prior, f).expect("a frame")).collect(); + Seg { env: envelope(&generator, &payload), payload, frames } + }; + let a0 = seg(&f1, 0..2, gd, f1.digest); + let a1 = seg(&f1, 2..3, gd, f1.digest); + let b0 = seg(&f2, 0..3, gd, f2.digest); + let signed = |body: Value, who: u8| sign(body, &key(who)); + let track = |face: &Face, s: &Seg, seq: u64, prev: Option<&Value>, frames: bool| signed(track_body(&s.env, &face.record, seq, prev, frames.then_some(&s.frames[..])), face.who); + let claim = |face: &Face, s: &Seg, t: &Value| synthesis::sign(claim_body(&s.env, Some(t), &[gd]), &key(face.who)); + let ta0 = track(&f1, &a0, 0, None, true); + let ta1 = track(&f1, &a1, 1, Some(&ta0), true); + let tb0 = track(&f2, &b0, 0, None, true); + let (ca0, ca1, cb0) = (claim(&f1, &a0, &ta0), claim(&f1, &a1, &ta1), claim(&f2, &b0, &tb0)); + let lp = label_policy(); + let log = Log::new().push(0, present(&ca0, Some(a0.output()))).push(40_000, shown("first-exposure")).push(80_000, present(&ca1, Some(a1.output()))); + let gfile = || vec![Held::File("generator.wfg")]; + let afile = || vec![Held::File("identities/face1.wfi")]; + let f1_case = || Case { + identities: vec![text(&f1.record)], + tracks: vec![text(&ta0), text(&ta1)], + claims: vec![text(&ca0), text(&ca1)], + label_policy: Some(text(&lp)), + player: log.texts(), + objects: vec![a0.env.clone(), a1.env.clone()], + ..Default::default() + }; + let one = |t: &Value, c: &Value, s: &Seg, face: &Face| Case { + identities: vec![text(&face.record)], + tracks: vec![text(t)], + claims: vec![text(c)], + objects: vec![s.env.clone()], + ..Default::default() + }; + let refuse_own = |at: At, r: Reason| Intent::Refuse(at, Why::Own(r)); + let refuse_syn = |at: synthesis::At, r: synthesis::Reason| Intent::Refuse(At::Synthesis(at), Why::Synthesis(r)); + + // Material for single refusals. + let other_gen = sha(&[&seeded_generator_file()]); + // Files that do not read: the seeded ones with their last byte missing. + let seeded_id = seeded_identity_file(); + let corrupt_prior = seeded_id[..seeded_id.len() - 1].to_vec(); + let seeded_gen = seeded_generator_file(); + let corrupt_gen = seeded_gen[..seeded_gen.len() - 1].to_vec(); + let corrupt_gen_digest = sha(&[&corrupt_gen]); + let a_bad_gen = seg(&f1, 0..1, gd, f1.digest); + let a_bad_gen = Seg { env: envelope(&corrupt_gen_digest, &write_segment(&f1.frames[0..1], STEP, &corrupt_gen_digest, &f1.digest, 0, (25, 1)).expect("a segment")), ..a_bad_gen }; + let a_seeded = { + let payload = write_segment(&f1.frames[0..1], STEP, &other_gen, &f1.digest, 0, (25, 1)).expect("a segment"); + Seg { env: envelope(&other_gen, &payload), payload, frames: vec![] } + }; + let a_pin_other = Seg { env: envelope(&other_gen, &a0.payload), ..a0.clone() }; + let a_names_f2 = { + let payload = write_segment(&f1.frames[0..2], STEP, &gd, &f2.digest, 0, (25, 1)).expect("a segment"); + Seg { env: envelope(&gd, &payload), payload, frames: a0.frames.clone() } + }; + let a_trailing = Seg { env: envelope(&gd, &[&a0.payload[..], &[0]].concat()), ..a0.clone() }; + let f1_corrupt_record = identity_record(FIRST, &sha(&[&corrupt_prior])); + let ta_frames_f2 = signed(with(track_body(&a0.env, &f1.record, 0, None, None), &["frames"], json!(b0.frames[..2].iter().map(|f| sx(f)).collect::>())), FIRST); + let ta0_unstated = track(&f1, &a0, 0, None, false); + let ca0_unstated = claim(&f1, &a0, &ta0_unstated); + let ta1_unchained = signed(track_body(&a1.env, &f1.record, 1, None, Some(&a1.frames)), FIRST); + let tb_over_f1 = signed(track_body(&a0.env, &f1.record, 0, None, Some(&a0.frames)), SECOND); + let malformed = signed(with(unsigned(&f1.record), &["name"], json!("first face")), FIRST); + let bad_key = with(f1.record.clone(), &["signer_pubkey"], json!(hex(&crate::sig::IDENTITY))); + let bad_sig = { + let mut t = ta0.clone(); + let s = t["sig"].as_str().expect("a signature").to_string(); + let flipped = format!("{}{}", if s.starts_with('0') { "1" } else { "0" }, &s[1..]); + t["sig"] = json!(flipped); + t + }; + let ca0_other_model = synthesis::sign(claim_body(&a0.env, Some(&ta0), &[other_gen]), &key(FIRST)); + let ca0_captured = synthesis::sign( + json!({ + "kind": "wai-origin", + "subject": {"type": "object", "content_hash": hex(&ch(&a0.env))}, + "media": "video", + "class": "captured", + "step": "capture", + "operation": "camera", + "record": null, + "source": "digitalCapture", + "models": [], + "oversight": null, + "inputs": [], + "evidence": null, + "issued_at": ISSUED, + }), + &key(FIRST), + ); + let warped_only: Vec> = { + let prior = parse_identity(&f1.prior).expect("a prior"); + f1.frames[0..2].iter().map(|f| warp(&prior, f, g.gauss, g.bg).0).collect() + }; + let log_warped = Log::new().push(0, present(&ca0, Some(sha(&[&canonical(&warped_only)])))).push(40_000, shown("first-exposure")); + let log_unlabelled = Log::new().push(0, present(&ca0, Some(a0.output()))); + let strict = Policy { require_proof: true, ..Default::default() }; + + vec![ + Vector { + name: "one_face_two_segments", + about: "a participant's identity record and two segments of her keypoint track, each a track record that states its frames and a synthesized origin claim; the generator and the prior held, so both segments are proved; a player's log presents both at generation-equivalence with the label shown at first exposure (§6, §8, §9)", + case: f1_case(), + generators: gfile(), + priors: afile(), + intent: Intent::Accept, + }, + Vector { + name: "not_held", + about: "the same case with neither the generator nor the prior held: accepted with both segments not recomputed (§9.2 step 3.8)", + case: f1_case(), + generators: vec![], + priors: vec![], + intent: Intent::Accept, + }, + Vector { + name: "two_faces", + about: "two participants, each driving her own face under her own key, their segments interleaved (§8.3)", + case: Case { + identities: vec![text(&f1.record), text(&f2.record)], + tracks: vec![text(&ta0), text(&tb0), text(&ta1)], + claims: vec![text(&ca0), text(&cb0), text(&ca1)], + objects: vec![a0.env.clone(), b0.env.clone(), a1.env.clone()], + ..Default::default() + }, + generators: gfile(), + priors: vec![Held::File("identities/face1.wfi"), Held::File("identities/face2.wfi")], + intent: Intent::Accept, + }, + Vector { + name: "frames_unstated", + about: "a track record that states no frames: the frames are recomputed all the same (§8.3)", + case: one(&ta0_unstated, &ca0_unstated, &a0, &f1), + generators: gfile(), + priors: afile(), + intent: Intent::Accept, + }, + Vector { + name: "record_malformed", + about: "an identity record with a member §8.2 does not list", + case: Case { identities: vec![text(&malformed)], ..Default::default() }, + generators: vec![], + priors: vec![], + intent: refuse_own(At::Identity(0), Reason::RecordMalformed), + }, + Vector { + name: "bad_key", + about: "an identity record whose key is the identity point, of small order (SPEC §7 *Signatures* step 2)", + case: Case { identities: vec![text(&bad_key)], ..Default::default() }, + generators: vec![], + priors: vec![], + intent: refuse_own(At::Identity(0), Reason::BadKey), + }, + Vector { + name: "bad_signature", + about: "a track record whose signature has one digit changed", + case: Case { tracks: vec![text(&bad_sig)], ..one(&ta0, &ca0, &a0, &f1) }, + generators: vec![], + priors: vec![], + intent: refuse_own(At::Track(0), Reason::BadSignature), + }, + Vector { + name: "identity_invalid", + about: "an identity record whose prior is held and does not read: the seeded prior with its last byte missing (§7.3)", + case: Case { identities: vec![text(&f1_corrupt_record)], ..Default::default() }, + generators: vec![], + priors: vec![Held::Bytes(corrupt_prior.clone())], + intent: refuse_own(At::Identity(0), Reason::IdentityInvalid), + }, + Vector { + name: "identity_unknown", + about: "a track record whose identity record is not given", + case: Case { identities: vec![], ..one(&ta0, &ca0, &a0, &f1) }, + generators: vec![], + priors: vec![], + intent: refuse_own(At::Track(0), Reason::IdentityUnknown), + }, + Vector { + name: "driver_key_mismatch", + about: "a second participant signs a segment over the first one's face: the track record's key is not the identity record's (§8.3)", + case: one(&tb_over_f1, &ca0, &a0, &f1), + generators: vec![], + priors: vec![], + intent: refuse_own(At::Track(0), Reason::DriverKeyMismatch), + }, + Vector { + name: "track_chain_broken", + about: "a face's first track record has sequence number 1 (§8.3)", + case: one(&ta1_unchained, &ca1, &a1, &f1), + generators: vec![], + priors: vec![], + intent: refuse_own(At::Track(0), Reason::TrackChainBroken), + }, + Vector { + name: "object_unknown", + about: "a track record whose segment object is not held", + case: Case { objects: vec![], ..one(&ta0, &ca0, &a0, &f1) }, + generators: vec![], + priors: vec![], + intent: refuse_own(At::Track(0), Reason::ObjectUnknown), + }, + Vector { + name: "payload_invalid", + about: "a segment whose payload has a byte after its padding (§7.4, trailing)", + case: { + let t = signed(track_body(&a_trailing.env, &f1.record, 0, None, None), FIRST); + let c = synthesis::sign(claim_body(&a_trailing.env, Some(&t), &[gd]), &key(FIRST)); + one(&t, &c, &a_trailing, &f1) + }, + generators: vec![], + priors: vec![], + intent: refuse_own(At::Track(0), Reason::PayloadInvalid), + }, + Vector { + name: "generator_mismatch", + about: "a segment whose envelope pins another generator than its payload names (§7.4)", + case: { + let t = signed(track_body(&a_pin_other.env, &f1.record, 0, None, None), FIRST); + let c = synthesis::sign(claim_body(&a_pin_other.env, Some(&t), &[gd]), &key(FIRST)); + one(&t, &c, &a_pin_other, &f1) + }, + generators: vec![], + priors: vec![], + intent: refuse_own(At::Track(0), Reason::GeneratorMismatch), + }, + Vector { + name: "identity_mismatch", + about: "a participant's segment whose payload names the other participant's identity prior (§8.3)", + case: { + let t = signed(track_body(&a_names_f2.env, &f1.record, 0, None, None), FIRST); + let c = synthesis::sign(claim_body(&a_names_f2.env, Some(&t), &[gd]), &key(FIRST)); + one(&t, &c, &a_names_f2, &f1) + }, + generators: gfile(), + priors: vec![Held::File("identities/face1.wfi"), Held::File("identities/face2.wfi")], + intent: refuse_own(At::Track(0), Reason::IdentityMismatch), + }, + Vector { + name: "generator_invalid", + about: "a segment that names a generator the verifier holds and that does not read: the seeded generator with its last byte missing (§7.2)", + case: { + let t = signed(track_body(&a_bad_gen.env, &f1.record, 0, None, None), FIRST); + let c = synthesis::sign(claim_body(&a_bad_gen.env, Some(&t), &[corrupt_gen_digest]), &key(FIRST)); + one(&t, &c, &a_bad_gen, &f1) + }, + generators: vec![Held::Bytes(corrupt_gen.clone())], + priors: afile(), + intent: refuse_own(At::Track(0), Reason::GeneratorInvalid), + }, + Vector { + name: "shape_mismatch", + about: "a segment under the seeded generator (three keypoints, 16 × 16) over a face of sixteen keypoints at 64 × 64 (§7.7)", + case: { + let t = signed(track_body(&a_seeded.env, &f1.record, 0, None, None), FIRST); + let c = synthesis::sign(claim_body(&a_seeded.env, Some(&t), &[other_gen]), &key(FIRST)); + one(&t, &c, &a_seeded, &f1) + }, + generators: vec![Held::File(SEEDED_GENERATOR)], + priors: afile(), + intent: refuse_own(At::Track(0), Reason::ShapeMismatch), + }, + Vector { + name: "frame_mismatch", + about: "a track record that states the other participant's frames: the generation gives others (§8.3)", + case: one(&ta_frames_f2, &synthesis::sign(claim_body(&a0.env, Some(&ta_frames_f2), &[gd]), &key(FIRST)), &a0, &f1), + generators: gfile(), + priors: afile(), + intent: refuse_own(At::Track(0), Reason::FrameMismatch), + }, + Vector { + name: "generation_not_proved", + about: "the policy requires proof and the generator is not held (§9.1)", + case: Case { policy: strict, ..one(&ta0, &ca0, &a0, &f1) }, + generators: vec![], + priors: afile(), + intent: refuse_own(At::Track(0), Reason::GenerationNotProved), + }, + Vector { + name: "claim_missing", + about: "a segment with no origin claim (§8.4)", + case: Case { claims: vec![], ..one(&ta0, &ca0, &a0, &f1) }, + generators: gfile(), + priors: afile(), + intent: refuse_own(At::Track(0), Reason::ClaimMissing), + }, + Vector { + name: "claim_mismatch", + about: "a segment's claim names another generator as its model (§8.4)", + case: one(&ta0, &ca0_other_model, &a0, &f1), + generators: gfile(), + priors: afile(), + intent: refuse_own(At::Track(0), Reason::ClaimMismatch), + }, + Vector { + name: "class_understated", + about: "a segment claimed as a capture: its envelope generates, so synthesis refuses the claim (synthesis §3.3)", + case: one(&ta0, &ca0_captured, &a0, &f1), + generators: gfile(), + priors: afile(), + intent: refuse_syn(synthesis::At::Claim(0), synthesis::Reason::ClassUnderstated), + }, + Vector { + name: "present_output_irreproducible", + about: "a player that presents the warped pictures, not the generated frames, under the segment's claim (synthesis §9.2)", + case: Case { label_policy: Some(text(&lp)), player: log_warped.texts(), ..one(&ta0, &ca0, &a0, &f1) }, + generators: gfile(), + priors: afile(), + intent: refuse_syn(synthesis::At::Player(0), synthesis::Reason::PresentOutputIrreproducible), + }, + Vector { + name: "label_not_shown", + about: "a player that presents a generated face with no label at first exposure (synthesis §10.4)", + case: Case { label_policy: Some(text(&lp)), player: log_unlabelled.texts(), ..one(&ta0, &ca0, &a0, &f1) }, + generators: gfile(), + priors: afile(), + intent: refuse_syn(synthesis::At::Player(0), synthesis::Reason::LabelNotShown), + }, + ] +} + +// ---- form vectors (§7.2 to §7.4) ---- + +fn generator_summary(g: &Generator) -> Value { + json!({"k": g.k, "width": g.width, "height": g.height, "c1": g.c1, "c2": g.c2, "c3": g.c3, "gauss": g.gauss, "bg": g.bg}) +} + +fn segment_summary(s: &Segment) -> Value { + json!({ + "k": s.k, "step": s.step, "order": s.order, "generator": hex(&s.generator), "identity": hex(&s.identity), + "first": s.first, "rate": [s.rate.0, s.rate.1], + "keypoints": s.frames.iter().map(|f| f.iter().map(|&(x, y)| json!([x, y])).collect::>()).collect::>(), + }) +} + +fn form_vectors() -> Vec { + let mut out = Vec::new(); + let mut push = |name: &str, form: &str, b: &[u8]| { + let (outcome, detail) = match form { + "generator" => match parse_generator(b) { + Ok(g) => ("read", generator_summary(&g)), + Err(e) => ("refuse", json!(e.code())), + }, + "identity" => match parse_identity(b) { + Ok(i) => ("read", json!({"k": i.k, "width": i.width, "height": i.height, "keypoints": i.keypoints.iter().map(|&(x, y)| json!([x, y])).collect::>(), "rgb_sha256": sx(&i.rgb)})), + Err(e) => ("refuse", json!(e.code())), + }, + _ => match parse_segment(b) { + Ok(s) => ("read", segment_summary(&s)), + Err(e) => ("refuse", json!(e.code())), + }, + }; + let mut m = Map::new(); + m.insert("name".into(), json!(name)); + m.insert("form".into(), json!(form)); + m.insert("hex".into(), json!(hex(b))); + m.insert("outcome".into(), json!(outcome)); + m.insert(if outcome == "read" { "read".into() } else { "reason".into() }, detail); + out.push(Value::Object(m)); + }; + let set = |b: &[u8], at: usize, v: &[u8]| { + let mut c = b.to_vec(); + c[at..at + v.len()].copy_from_slice(v); + c + }; + let gm = micro_generator(); + push("generator", "generator", &gm); + push("generator_bad_magic", "generator", &set(&gm, 3, b"2")); + push("generator_version", "generator", &set(&gm, 4, &[2])); + push("generator_width_not_a_multiple_of_4", "generator", &set(&gm, 6, &6u16.to_le_bytes())); + push("generator_no_channels", "generator", &set(&gm, 10, &0u16.to_le_bytes())); + push("generator_gauss_zero", "generator", &set(&gm, 16, &0u16.to_le_bytes())); + push("generator_truncated", "generator", &gm[..gm.len() - 1]); + push("generator_trailing", "generator", &[&gm[..], &[0]].concat()); + let id = seeded_identity_file(); + push("identity", "identity", &id); + push("identity_bad_magic", "identity", &set(&id, 0, b"WFG1")); + push("identity_version", "identity", &set(&id, 4, &[0])); + push("identity_no_keypoints", "identity", &set(&id, 5, &[0])); + push("identity_truncated", "identity", &id[..id.len() - 1]); + push("identity_trailing", "identity", &[&id[..], &[7]].concat()); + let frames: Vec> = vec![ + vec![(88, 104), (168, 96), (128, 180)], + vec![(90, 104), (168, 94), (128, 186)], + vec![(96, 100), (166, 94), (130, 196)], + vec![(96, 100), (166, 94), (130, 196)], + vec![(-200, 4000), (32766, -32768), (0, 0)], + ]; + let seg = write_segment(&frames, 2, &label("a generator"), &label("an identity"), 25, (30000, 1001)).expect("a segment"); + push("segment", "segment", &seg); + push("segment_bad_magic", "segment", &set(&seg, 0, b"WFK2")); + push("segment_version", "segment", &set(&seg, 4, &[2])); + push("segment_step_zero", "segment", &set(&seg, 6, &[0])); + push("segment_order_16", "segment", &set(&seg, 7, &[16])); + push("segment_no_frames", "segment", &set(&seg, 76, &0u16.to_le_bytes())); + push("segment_reserved", "segment", &set(&seg, 82, &1u16.to_le_bytes())); + push("segment_first_frame_out_of_range", "segment", &set(&seg, SEGMENT_HEADER, &(-20000i16).to_le_bytes())); + push("segment_truncated", "segment", &seg[..seg.len() - 2]); + push("segment_trailing", "segment", &[&seg[..], &[0]].concat()); + // A delta that leaves the range: one frame after (0, 0), a step of 16, x + 2048 · 16. + let far = write_segment(&[vec![(0, 0)], vec![(32752, 0)]], 16, &[0; 32], &[0; 32], 0, (25, 1)).expect("a segment"); + push("segment_two_frames", "segment", &far); + let mut beyond = far.clone(); + beyond[SEGMENT_HEADER] = 2; // first x = 2 · 16 = 32, so the next is 32784, past 32767 + push("segment_delta_out_of_range", "segment", &beyond); + // A code of 32 leading zeros: the first frame, then four zero bytes and a one. + let mut zeros = far[..SEGMENT_HEADER + 4].to_vec(); + zeros[7] = 0; + zeros.extend_from_slice(&[0, 0, 0, 0, 0x80]); + push("segment_code_too_long", "segment", &zeros); + let mut pad = write_segment(&[vec![(0, 0)], vec![(16, 0)]], 16, &[0; 32], &[0; 32], 0, (25, 1)).expect("a segment"); + *pad.last_mut().expect("a byte") |= 1; + push("segment_padding_not_zero", "segment", &pad); + out +} + +// ---- frame vectors (§7.5 to §7.7) ---- + +fn frame_vectors(files: &BTreeMap>) -> Vec { + let mut out = Vec::new(); + let mut add = |name: &str, gfile: &str, ifile: &str, kp: &[(i32, i32)], full: bool| { + let (g, l) = load(&files[gfile]).expect("a corpus generator loads"); + let id = parse_identity(&files[ifile]).expect("a corpus prior reads"); + let (warped, maps) = warp(&id, kp, g.gauss, g.bg); + let f = frame(&g, &l, &id, kp).expect("a frame"); + assert_eq!(f, direct(&g, &id, kp), "the graph is §7.6's arithmetic on {name}"); + let mut m = Map::new(); + m.insert("name".into(), json!(name)); + m.insert("generator".into(), json!(gfile)); + m.insert("identity".into(), json!(ifile)); + m.insert("keypoints".into(), json!(kp.iter().map(|&(x, y)| json!([x, y])).collect::>())); + m.insert("warped_sha256".into(), json!(sx(&warped))); + m.insert("maps_sha256".into(), json!(sx(&maps_bytes(&maps)))); + m.insert("frame_sha256".into(), json!(sx(&f))); + if full { + m.insert("warped".into(), json!(hex(&warped))); + m.insert("frame".into(), json!(hex(&f))); + } + out.push(Value::Object(m)); + }; + add("seeded_at_rest", SEEDED_GENERATOR, SEEDED_IDENTITY, &[(88, 104), (168, 96), (128, 180)], true); + add("seeded_moved", SEEDED_GENERATOR, SEEDED_IDENTITY, &[(100, 96), (176, 104), (120, 200)], true); + add("seeded_off_the_picture", SEEDED_GENERATOR, SEEDED_IDENTITY, &[(-40, 300), (400, -24), (128, 128)], true); + for (face, name) in [("face1", "identities/face1.wfi"), ("face2", "identities/face2.wfi")] { + for (i, kp) in track_frames(&files[&format!("tracks/{face}.json")]).iter().enumerate() { + add(&format!("{face}_{i}"), "generator.wfg", name, kp, false); + } + } + out +} + +// ---- JSON ---- + +fn case_json(v: &Vector) -> Map { + let c = &v.case; + let mut m = Map::new(); + m.insert( + "policy".into(), + json!({ + "dual_layer": if c.policy.synthesis.dual_layer_required { "required" } else { "when-given" }, + "assistive": if c.policy.synthesis.require_proof { "require-proof" } else { "accept-asserted" }, + "second_layer": if c.policy.synthesis.second_layer_proof { "require-proof" } else { "accept-stated" }, + "generation": if c.policy.require_proof { "require-proof" } else { "accept-unrecomputed" }, + }), + ); + m.insert("identities".into(), json!(c.identities)); + m.insert("tracks".into(), json!(c.tracks)); + m.insert("claims".into(), json!(c.claims)); + m.insert("reviews".into(), json!(c.reviews)); + m.insert("label_policy".into(), json!(c.label_policy)); + m.insert("player".into(), json!(c.player)); + m.insert("objects".into(), json!(c.objects.iter().map(|b| hex(b)).collect::>())); + m.insert("generators".into(), json!(v.generators.iter().map(held_json).collect::>())); + m.insert("priors".into(), json!(v.priors.iter().map(held_json).collect::>())); + m +} + +fn outcome_json(o: &Outcome) -> Map { + let mut m = Map::new(); + match o { + Outcome::Refuse { at, reason } => { + m.insert("outcome".into(), json!("refuse")); + m.insert("reason".into(), json!(reason.code())); + m.insert("at".into(), at.to_json()); + } + Outcome::Accept(r) => { + m.insert("outcome".into(), json!("accept")); + m.insert( + "statements".into(), + json!({ + "identities": r.identities, + "tracks": r.tracks, + "claims": r.claims, + "labels": r.labels, + "reviews_presented": r.reviews_presented, + "visual_icon": r.visual_icon, + }), + ); + } + } + m +} + +fn held_from_json(v: &Value, files: &BTreeMap>) -> Option>> { + v.as_array()? + .iter() + .map(|h| match (h["file"].as_str(), h["hex"].as_str()) { + (Some(n), None) => files.get(n).cloned(), + (None, Some(x)) => unhex(x), + _ => None, + }) + .collect() +} + +fn case_from_json(v: &Value, files: &BTreeMap>) -> Option { + let strs = |k: &str| -> Option> { v[k].as_array()?.iter().map(|x| x.as_str().map(String::from)).collect() }; + let p = &v["policy"]; + Some(Case { + identities: strs("identities")?, + tracks: strs("tracks")?, + claims: strs("claims")?, + reviews: strs("reviews")?, + label_policy: if v["label_policy"].is_null() { None } else { Some(v["label_policy"].as_str()?.to_string()) }, + player: strs("player")?, + objects: v["objects"].as_array()?.iter().map(|x| unhex(x.as_str()?)).collect::>()?, + generators: held_from_json(&v["generators"], files)?, + priors: held_from_json(&v["priors"], files)?, + policy: Policy { + synthesis: synthesis::Policy { + dual_layer_required: p["dual_layer"] == "required", + require_proof: p["assistive"] == "require-proof", + second_layer_proof: p["second_layer"] == "require-proof", + }, + require_proof: p["generation"] == "require-proof", + }, + }) +} + +// ---- generation and verification ---- + +/// The inputs that are missing or do not match their pins in [`INPUTS`]. +pub fn check_inputs(files: &BTreeMap>) -> Vec { + INPUTS.iter().filter(|(n, d)| files.get(*n).is_none_or(|b| hex(&sha(&[b])) != *d)).map(|(n, _)| n.to_string()).collect() +} + +/// The files the generator writes besides `vectors.json`. +pub fn written_files() -> Vec<(String, Vec)> { + vec![(SEEDED_GENERATOR.into(), seeded_generator_file()), (SEEDED_IDENTITY.into(), seeded_identity_file())] +} + +/// The corpus: every file the generator writes, by its path below the corpus directory, from +/// the inputs, which must match their pins. +pub fn generate(inputs: &BTreeMap>) -> Vec<(String, Vec)> { + let bad = check_inputs(inputs); + assert!(bad.is_empty(), "the inputs {bad:?} are not the ones INPUTS pins"); + let mut files = inputs.clone(); + let written = written_files(); + files.extend(written.iter().cloned()); + let vs = vectors(&files); + let mut seen = std::collections::BTreeSet::new(); + let mut cases = Vec::new(); + for v in &vs { + let mut case = v.case.clone(); + case.generators = v.generators.iter().map(|h| bytes_of(h, &files)).collect(); + case.priors = v.priors.iter().map(|h| bytes_of(h, &files)).collect(); + let got = super::verify(&case); + match (&v.intent, &got) { + (Intent::Accept, Outcome::Accept(_)) => {} + (Intent::Refuse(at, Why::Own(r)), Outcome::Refuse { at: a, reason: Why::Own(g) }) if at == a && r == g => { + seen.insert(*r); + } + (Intent::Refuse(at, w), Outcome::Refuse { at: a, reason }) if at == a && w == reason => {} + _ => panic!("vector {} does not reach the outcome it was written to have: {got:?}", v.name), + } + let mut m = Map::new(); + m.insert("name".into(), json!(v.name)); + m.insert("about".into(), json!(v.about)); + m.extend(case_json(v)); + m.extend(outcome_json(&got)); + cases.push(Value::Object(m)); + } + let missing: Vec<&str> = Reason::ALL.iter().filter(|r| !seen.contains(r)).map(|r| r.code()).collect(); + assert!(missing.is_empty(), "the corpus gives no case of {missing:?}"); + let forms = form_vectors(); + let codes: std::collections::BTreeSet<&str> = forms.iter().filter_map(|f| f["reason"].as_str()).collect(); + for e in FormError::ALL { + assert!(codes.contains(e.code()), "the form vectors give no {}", e.code()); + } + let doc = json!({ + "capability": CAPABILITY, + "lowering": LOWERING, + "inputs": INPUTS.iter().map(|(n, d)| json!({"file": n, "sha256": d})).collect::>(), + "written": written.iter().map(|(n, b)| json!({"file": n, "sha256": sx(b)})).collect::>(), + "segment_step": STEP, + "forms": forms, + "frames": frame_vectors(&files), + "cases": cases, + }); + let mut out = written; + out.push(("vectors.json".into(), format!("{}\n", serde_json::to_string_pretty(&doc).expect("JSON")).into_bytes())); + out +} + +/// Check a corpus directory's files against this module: every form, frame and case vector +/// gets what it states. +pub fn verify(files: &BTreeMap>) -> Vec { + let mut fails = Vec::new(); + let Some(doc) = files.get("vectors.json").and_then(|s| serde_json::from_slice::(s).ok()) else { + return vec!["vectors.json does not read".into()]; + }; + let forms: BTreeMap = form_vectors().into_iter().map(|f| (f["name"].as_str().unwrap_or("").to_string(), f)).collect(); + for f in doc["forms"].as_array().into_iter().flatten() { + let name = f["name"].as_str().unwrap_or("?"); + if forms.get(name) != Some(f) { + fails.push(format!("form {name}")); + } + } + let frames: Vec = frame_vectors(files); + if doc["frames"].as_array() != Some(&frames) { + fails.push("the frame vectors differ".into()); + } + for v in doc["cases"].as_array().into_iter().flatten() { + let name = v["name"].as_str().unwrap_or("?"); + let Some(c) = case_from_json(v, files) else { + fails.push(format!("{name}: the case does not read")); + continue; + }; + let got = Value::Object(outcome_json(&super::verify(&c))); + let mut want = Map::new(); + for k in ["outcome", "reason", "at", "statements"] { + if let Some(x) = v.get(k) { + want.insert(k.into(), x.clone()); + } + } + if got != Value::Object(want) { + fails.push(format!("{name}: {got}")); + } + } + fails +} diff --git a/wai/wai-rs/src/bin/wai_avatar_face_vectors.rs b/wai/wai-rs/src/bin/wai_avatar_face_vectors.rs new file mode 100644 index 000000000..174c35c4d --- /dev/null +++ b/wai/wai-rs/src/bin/wai_avatar_face_vectors.rs @@ -0,0 +1,100 @@ +//! Conformance corpus for faces from keypoints (`live-conformance/avatar-face/`, +//! extensions/avatars.md §12). +//! +//! wai_avatar_face_vectors gen write vectors.json and the seeded generator +//! and identity, from fixed seeds and the +//! corpus's inputs (the trained generator, two +//! identity priors, their tracks and the training +//! report), which must match their pins +//! wai_avatar_face_vectors verify the inputs match their pins, every other file +//! regenerates byte for byte, no other file but +//! README.md is there, and every vector gets what +//! it states +//! +//! The generator and this verifier are one implementation. The corpus's second implementation +//! is `live-conformance/avatar_face_verify.py`, written from the extension's text and sharing no +//! code with this binary. + +use std::collections::BTreeMap; +use std::path::Path; +use wai::avatar_face::corpus; + +fn read_dir(dir: &Path) -> Result>, String> { + let mut files = BTreeMap::new(); + for sub in ["", "identities", "tracks"] { + let d = dir.join(sub); + let entries = std::fs::read_dir(&d).map_err(|e| format!("read {}: {e}", d.display()))?; + for e in entries.flatten() { + if e.path().is_dir() { + continue; + } + let name = e.file_name().to_string_lossy().into_owned(); + if sub.is_empty() && name == "README.md" { + continue; + } + let key = if sub.is_empty() { name } else { format!("{sub}/{name}") }; + files.insert(key, std::fs::read(e.path()).map_err(|err| format!("read {}: {err}", e.path().display()))?); + } + } + Ok(files) +} + +fn inputs(files: &BTreeMap>) -> BTreeMap> { + corpus::INPUTS.iter().filter_map(|(n, _)| files.get(*n).map(|b| (n.to_string(), b.clone()))).collect() +} + +fn main() { + let a: Vec = std::env::args().collect(); + match (a.get(1).map(String::as_str), a.get(2)) { + (Some("gen"), Some(d)) => { + let files = read_dir(Path::new(d)).unwrap_or_else(|e| { + eprintln!("{e}"); + std::process::exit(1) + }); + let bad = corpus::check_inputs(&inputs(&files)); + if !bad.is_empty() { + eprintln!("the inputs {bad:?} are missing or do not match their pins in corpus::INPUTS"); + std::process::exit(1); + } + for (name, body) in corpus::generate(&inputs(&files)) { + let p = Path::new(d).join(&name); + if let Err(e) = std::fs::write(&p, body) { + eprintln!("write {}: {e}", p.display()); + std::process::exit(1); + } + } + println!("wrote {d}"); + } + (Some("verify"), Some(d)) => std::process::exit(verify(Path::new(d))), + _ => { + eprintln!("usage: wai_avatar_face_vectors gen|verify "); + std::process::exit(2); + } + } +} + +fn verify(dir: &Path) -> i32 { + let files = match read_dir(dir) { + Ok(f) => f, + Err(e) => { + eprintln!("{e}"); + return 1; + } + }; + let inputs = inputs(&files); + let bad = corpus::check_inputs(&inputs); + println!("{} the inputs match their pins", if bad.is_empty() { "PASS" } else { "FAIL" }); + if !bad.is_empty() { + return 1; + } + let mut generated: BTreeMap> = corpus::generate(&inputs).into_iter().collect(); + generated.extend(inputs); + let same = files == generated; + println!("{} every file regenerates byte for byte from its seeds and inputs, and no other file is there", if same { "PASS" } else { "FAIL" }); + let fails = corpus::verify(&files); + for f in &fails { + println!("FAIL {f}"); + } + println!("{} every form, frame and case vector gets what the corpus states", if fails.is_empty() { "PASS" } else { "FAIL" }); + (!same || !fails.is_empty()) as i32 +} diff --git a/wai/wai-rs/src/codecs/glossary_check.rs b/wai/wai-rs/src/codecs/glossary_check.rs index ed6b91a66..b88de9b15 100644 --- a/wai/wai-rs/src/codecs/glossary_check.rs +++ b/wai/wai-rs/src/codecs/glossary_check.rs @@ -85,6 +85,7 @@ const GLOSSARY_NO_ENTRY: &[(&str, &str)] = &[ (CAP_AUDIO_INT_CODEC, WAI_NATIVE), (CAP_AUDIO_SCENE, WAI_NATIVE), (CAP_AUDIO_SCORE, WAI_NATIVE), + (CAP_AVATAR_FACE, WAI_NATIVE), (CAP_AVATAR_POSE, WAI_NATIVE), (CAP_FEED_STATE, WAI_NATIVE), (CAP_FILM_LINEAR, WAI_NATIVE), diff --git a/wai/wai-rs/src/codecs/mod.rs b/wai/wai-rs/src/codecs/mod.rs index 52cd6ba1c..92c4b7614 100644 --- a/wai/wai-rs/src/codecs/mod.rs +++ b/wai/wai-rs/src/codecs/mod.rs @@ -277,6 +277,17 @@ pub const CAP_FEED_STATE: &str = "wai.feed.state"; // telepresence stream is a live world of one avatar. pub const CAP_AVATAR_POSE: &str = "wai.avatar.pose"; +// Faces from keypoints (extensions/avatars §6 to §13): a keypoint track (WFK1 segments, +// signed by the participant who drives it) and an identity prior named by its SHA-256 +// (WFI1, a reference picture and its keypoints) generate a talking head that is the same +// bytes on every sink: an integer warp, then the generator's network as one wai-ops/1 +// graph (lowering face-int/1) whose parameter set is one file, WFG1, pinned by SHA-256. +// GenerativeDeterministic (face-equivalence, a case of generation-equivalence): the frames +// are generated, and every origin claim over them is `generate` (synthesis §3.3). The +// native sink does not advertise it; the avatar_face module (feature avatar_face) is the +// reference. +pub const CAP_AVATAR_FACE: &str = "wai.avatar.face"; + // Deterministic quantum-circuit SIMULATION (extensions/quantum-sim): the wire // carries a compact gate op-log (WQC container), the sink CLASSICALLY simulates it // in a fixed-point complex floor (Amp = i64 re/im at 2^FRAC), and the reconstructed @@ -607,7 +618,7 @@ pub fn known_capabilities() -> &'static [&'static str] { // learned integer-exact audio codec (byte-exact PCM) + splat codec (byte-exact attrs) CAP_AUDIO_INT_CODEC, CAP_SPLAT_INT_CODEC, // deterministic generative (byte-exact, intent=create) - CAP_GENERATE_SPECTRAL, + CAP_GENERATE_SPECTRAL, CAP_AVATAR_FACE, // interactive worlds + asset media (engine behind the `world` feature) CAP_WORLD_SCENE, CAP_WORLD_REPLAY, CAP_WORLD_LIVE, CAP_WORLD_MODEL, CAP_MESH_GLB, CAP_SPLAT_SPZ, CAP_AUDIO_SCENE, CAP_AUDIO_SCORE, CAP_HAPTIC_SIGNAL, CAP_FEED_STATE, @@ -832,7 +843,7 @@ pub fn capability_tier(cap: &str) -> Option { // fixed-point approximation of the ideal unitary evolution, but its // reconstruction is exact and portable; the honest note lives in the // extension's §Fidelity). -CAP_WORLD_SCENE | CAP_WORLD_REPLAY | CAP_WORLD_LIVE | CAP_WORLD_MODEL | CAP_MESH_GLB | CAP_AUDIO_SCENE | CAP_AUDIO_SCORE | CAP_HAPTIC_SIGNAL | CAP_FEED_STATE | CAP_AVATAR_POSE | CAP_FILM_LINEAR | CAP_FILM_REEL | CAP_GENERATE_SPECTRAL | CAP_QUANTUM_CIRCUIT | CAP_QUANTUM_CIRCUIT2 => Lossless, +CAP_WORLD_SCENE | CAP_WORLD_REPLAY | CAP_WORLD_LIVE | CAP_WORLD_MODEL | CAP_MESH_GLB | CAP_AUDIO_SCENE | CAP_AUDIO_SCORE | CAP_HAPTIC_SIGNAL | CAP_FEED_STATE | CAP_AVATAR_POSE | CAP_FILM_LINEAR | CAP_FILM_REEL | CAP_GENERATE_SPECTRAL | CAP_AVATAR_FACE | CAP_QUANTUM_CIRCUIT | CAP_QUANTUM_CIRCUIT2 => Lossless, // Splat delivery is byte-exact and the temporal reconstruction is // frame-state-exact; the splat representation itself approximates a // scene, so the honest tier matches the captured medium, not the @@ -961,6 +972,9 @@ pub fn capability_license(cap: &str) -> Option { | CAP_NEURAL_ELIC | CAP_NEURAL_MLICPP | CAP_NEURAL_MLICV2 | CAP_NEURAL_CMIC | CAP_NEURAL_VIDEO_BMSHJ2018 | CAP_NEURAL_GLC | CAP_NEURAL_DCVC_RT | CAP_NEURAL_DCVC_FM | CAP_SEMANTIC_CLIP | CAP_GENERATE_SPECTRAL + // wai.avatar.face: its reference implementation is this crate's (Apache-2.0), and its + // registered generator was trained here on faces this repository generates. + | CAP_AVATAR_FACE // wai.neural.int_mlicpp, classified when it was registered: its decoder is this // crate's (Apache-2.0) and no model is registered with it (the conformance // vector's weights are WAI's own synthetic ones). The patent status of the @@ -1293,6 +1307,10 @@ pub fn capability_replicate_class(cap: &str) -> Option { // Deterministic integer generator: generated content, byte-identical on // every machine, pinned to (model, prompt, seed, steps) — receiptable. CAP_GENERATE_SPECTRAL => GenerativeDeterministic, + // A talking head generated from a keypoint track and an identity prior by an integer + // warp and one wai-ops/1 graph (extensions/avatars.md §7): byte-identical frames on + // every machine, pinned to (generator, identity, keypoints). + CAP_AVATAR_FACE => GenerativeDeterministic, // Sink-generator regeneration; conforms by similarity, not bytes. CAP_SEMANTIC_CLIP | CAP_SEMANTIC_PERCO2 => Behavioral, @@ -1408,6 +1426,10 @@ pub fn capability_ops(cap: &str) -> Option { // `final_only` end rule, and each latent's σ is bucketed with E3's `ceil` rule // (integer-payloads §14). CAP_NEURAL_INT_MLICPP => ops("wmb1/1", Some(("t2", "lenient", "final_only")), Some("ceil")), + // The face generator's network (extensions/avatars.md §7.6). Its driver's warp and + // keypoint maps are the extension's own integer steps (§7.5), not stage operators of + // §5E, so the member names no stage. + CAP_AVATAR_FACE => ops("face-int/1", None, None), // JPEG AI's integer stages (integer-operators §13.6): every graph of a conforming // bundle meets `acc64`, and its scale graphs `hs_y` and `hs_uv` meet `acc32` // (§13.6.1), so they run on a 32-bit fabric as on a 64-bit one; its other @@ -1488,7 +1510,10 @@ pub fn parameter_set_shape(cap: &str) -> Option { // several files). CAP_NEURAL_INT_SYNTH | CAP_NEURAL_INT_MLICPP | CAP_AUDIO_INT_CODEC | CAP_SPLAT_INT_CODEC | CAP_NEURAL_ENCODEC32 | CAP_NEURAL_DAC | CAP_NEURAL_MIMI | CAP_NEURAL_WAVTOKENIZER - | CAP_NEURAL_SNAC | CAP_NEURAL_VIDEO_BMSHJ2018 => ParameterSetShape::OneFile, + | CAP_NEURAL_SNAC | CAP_NEURAL_VIDEO_BMSHJ2018 + // The face generator, WFG1 (extensions/avatars.md §7.2). The identity prior is named + // by the payload's own digest (§7.4), as wai.generate.spectral's model is. + | CAP_AVATAR_FACE => ParameterSetShape::OneFile, _ => ParameterSetShape::SeveralFiles, }) } @@ -2238,6 +2263,9 @@ mod tests { assert_eq!(capability_replicate_class(CAP_GENERATE_SPECTRAL), Some(GenerativeDeterministic)); assert!(!is_neural_integer_exact(CAP_GENERATE_SPECTRAL), "generation is not a neural decode"); assert_ne!(capability_replicate_class(CAP_GENERATE_SPECTRAL), Some(Behavioral), "deterministic, not similarity"); + // A talking head from keypoints is generated as well (extensions/avatars.md §7.7). + assert_eq!(capability_replicate_class(CAP_AVATAR_FACE), Some(GenerativeDeterministic)); + assert!(!is_neural_integer_exact(CAP_AVATAR_FACE)); // Unknown → no class. assert_eq!(capability_replicate_class("wai.image.nope"), None); assert!(!is_neural_integer_exact("wai.image.nope")); @@ -2306,7 +2334,7 @@ mod tests { one_file.sort_unstable(); let mut want = vec![CAP_NEURAL_INT_SYNTH, CAP_NEURAL_INT_MLICPP, CAP_AUDIO_INT_CODEC, CAP_SPLAT_INT_CODEC, CAP_NEURAL_ENCODEC32, CAP_NEURAL_DAC, CAP_NEURAL_MIMI, CAP_NEURAL_WAVTOKENIZER, - CAP_NEURAL_SNAC, CAP_NEURAL_VIDEO_BMSHJ2018]; + CAP_NEURAL_SNAC, CAP_NEURAL_VIDEO_BMSHJ2018, CAP_AVATAR_FACE]; want.sort_unstable(); assert_eq!(one_file, want); for c in [CAP_IMAGE_PNG, CAP_SEMANTIC_CLIP, CAP_PRIOR_SET, CAP_WORLD_MODEL] { diff --git a/wai/wai-rs/src/codecs/registry.rs b/wai/wai-rs/src/codecs/registry.rs index 9ed0a7939..87e44b474 100644 --- a/wai/wai-rs/src/codecs/registry.rs +++ b/wai/wai-rs/src/codecs/registry.rs @@ -305,6 +305,7 @@ const ROWS: &[(&str, &str, Option<&str>, &str)] = &[ (CAP_HAPTIC_SIGNAL, S_INTERACTIVE, None, "2026-06-13"), (CAP_FEED_STATE, S_INTERACTIVE, None, "2026-06-13"), (CAP_AVATAR_POSE, S_INTERACTIVE, None, "2026-06-13"), + (CAP_AVATAR_FACE, S_INTERACTIVE, None, "2026-10-22"), (CAP_SPLAT_4D, S_INTERACTIVE, None, "2026-06-13"), (CAP_SPLAT_INT_CODEC, S_INTERACTIVE, None, "2026-06-28"), (CAP_FILM_LINEAR, S_INTERACTIVE, None, "2026-06-16"), @@ -929,7 +930,7 @@ pub fn capability_media(cap: &str) -> Option { CAP_NEURAL_VIDEO_BMSHJ2018 | CAP_NEURAL_DCVC_RT | CAP_NEURAL_DCVC_FM | CAP_FILM_LINEAR | CAP_FILM_REEL => Video, CAP_WORLD_SCENE | CAP_WORLD_REPLAY | CAP_WORLD_LIVE | CAP_WORLD_MODEL - | CAP_FEED_STATE | CAP_AVATAR_POSE => Interactive, + | CAP_FEED_STATE | CAP_AVATAR_POSE | CAP_AVATAR_FACE => Interactive, CAP_PRIOR_NNC | CAP_PRIOR_SET | CAP_TENSOR_NNC | CAP_QUANTUM_CIRCUIT | CAP_QUANTUM_CIRCUIT2 | CAP_TERNARY_TT => Binary, c if c.starts_with("wai.image.") => Image, @@ -1680,6 +1681,9 @@ mod tests { /// `wai.neural.int_hyper`, `wai.video.int_hyper` and `wai.neural.int_mlicpp` decode /// through the operator set. /// + /// `wai.avatar.face`, `GenerativeDeterministic`, generates its frames through it + /// (`face-int/1`, extensions/avatars.md §7.6), at `acc64`. + /// /// `wai.image.jpegai` carries one for its scale path and entropy decodes, at `acc32` /// (§13.6.1). /// @@ -1697,7 +1701,7 @@ mod tests { let regs = registrations(); let mut with: Vec<&str> = regs.iter().filter(|r| r.ops.is_some()).map(|r| r.capability).collect(); with.sort_unstable(); - let mut want = vec![CAP_AUDIO_INT_CODEC, CAP_IMAGE_JPEGAI, CAP_NEURAL_INT_HYPER, CAP_NEURAL_INT_MLICPP, CAP_NEURAL_INT_SYNTH, CAP_SPLAT_INT_CODEC, CAP_VIDEO_INT_HYPER]; + let mut want = vec![CAP_AUDIO_INT_CODEC, CAP_AVATAR_FACE, CAP_IMAGE_JPEGAI, CAP_NEURAL_INT_HYPER, CAP_NEURAL_INT_MLICPP, CAP_NEURAL_INT_SYNTH, CAP_SPLAT_INT_CODEC, CAP_VIDEO_INT_HYPER]; want.extend(STAGE_ONLY); want.sort_unstable(); assert_eq!(with, want); @@ -1714,17 +1718,24 @@ mod tests { } // JPEG AI's integer stages decode through the operator set at acc64, its scale // path, which its entropy-consistency claim covers, at acc32 (§13.6). - let (class, profile) = if r.capability == CAP_IMAGE_JPEGAI { (ReplicateClass::NeuralEntropyExact, "acc64") } else { (ReplicateClass::NeuralIntegerExact, "acc64") }; + let (class, profile) = match r.capability { + CAP_IMAGE_JPEGAI => (ReplicateClass::NeuralEntropyExact, "acc64"), + CAP_AVATAR_FACE => (ReplicateClass::GenerativeDeterministic, "acc64"), + _ => (ReplicateClass::NeuralIntegerExact, "acc64"), + }; assert_eq!(r.replicate, Some(class), "{}", r.capability); let roles: &[(&str, &str)] = if r.capability == CAP_IMAGE_JPEGAI { &[("hs_y", "acc32"), ("hs_uv", "acc32")] } else { &[] }; assert_eq!(o.role_profiles, roles, "{}", r.capability); assert_eq!((o.profile, o.subset), (Some(profile), Some("full")), "{}", r.capability); - assert!(o.lowering.is_some_and(|l| ["int_synth/1", "whm1/1", "wam1/1", "wmb1/1", "world/1", "jpegai-int/1"].contains(&l)), "{}", r.capability); + assert!(o.lowering.is_some_and(|l| ["int_synth/1", "whm1/1", "wam1/1", "wmb1/1", "world/1", "jpegai-int/1", "face-int/1"].contains(&l)), "{}", r.capability); } let export: serde_json::Value = serde_json::from_str(&read("registry/capabilities.json")).unwrap(); for c in export["capabilities"].as_array().unwrap() { let want = match c["capability"].as_str().unwrap() { CAP_NEURAL_INT_SYNTH => serde_json::json!({ "set": "wai-ops/1", "profile": "acc64", "subset": "full", "lowering": "int_synth/1" }), + // The face generator (extensions/avatars.md §7.6): its driver's steps are its + // own (§7.5), not stage operators, so no `stage`. + CAP_AVATAR_FACE => serde_json::json!({ "set": "wai-ops/1", "profile": "acc64", "subset": "full", "lowering": "face-int/1" }), CAP_SPLAT_INT_CODEC => serde_json::json!({ "set": "wai-ops/1", "profile": "acc64", "subset": "full", "lowering": "whm1/1", "stage": { "rans64": { "table": "t4097", "escape": "lenient", "end": "canonical" } } diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 073498658..51ff77cac 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -523,6 +523,15 @@ pub mod synthesis_mark; /// cbindgen:ignore #[cfg(feature = "exact_upscale")] pub mod exact_upscale; +// Faces from keypoints (extensions/avatars.md §6 to §13): `wai.avatar.face` — a talking head +// generated, the same bytes on every sink, from a participant's signed keypoint track and an +// identity prior named by digest, through an integer warp and one wai-ops/1 graph; the +// participant's identity and track records; and each segment's synthesis origin claim, label +// and presentation. Rust-only, like the live modules: its constants must not become macros in +// include/wai.h. +/// cbindgen:ignore +#[cfg(feature = "avatar_face")] +pub mod avatar_face; // Forensic watermark sessions (extensions/forensic-watermark.md): `wai.live.watermark` — the edge's // session record of the ETSI TS 104 002 A/B variants it embedded, bound to the session's grant by a // salted commitment, the recovered sequence and the rule that matches it, and the room authority's diff --git a/wai/wai-rs/tests/avatar_face.rs b/wai/wai-rs/tests/avatar_face.rs new file mode 100644 index 000000000..d616a9a23 --- /dev/null +++ b/wai/wai-rs/tests/avatar_face.rs @@ -0,0 +1,125 @@ +//! `wai.avatar.face` (extensions/avatars.md §6 to §13): the committed corpus +//! `live-conformance/avatar-face/` regenerates byte for byte from its fixed seeds and its pinned +//! inputs, holds no other file, every vector in it gets what it states, the extension's §10 lists +//! exactly the reasons the reference reports and the codes its files are refused with, and §7.9's +//! measurement is the training tool's report. The generator itself asserts that the corpus gives +//! every one of the reasons. +#![cfg(feature = "avatar_face")] + +use std::collections::BTreeMap; +use std::path::PathBuf; +use wai::avatar_face::{self, corpus, FormError, Reason}; + +fn wai_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") +} + +fn committed() -> BTreeMap> { + let dir = wai_dir().join("live-conformance/avatar-face"); + let mut files = BTreeMap::new(); + for sub in ["", "identities", "tracks"] { + for e in std::fs::read_dir(dir.join(sub)).expect("the committed corpus").flatten() { + let name = e.file_name().to_string_lossy().into_owned(); + if e.path().is_dir() || (sub.is_empty() && name == "README.md") { + continue; + } + let key = if sub.is_empty() { name } else { format!("{sub}/{name}") }; + files.insert(key, std::fs::read(e.path()).expect("a file")); + } + } + files +} + +fn inputs(files: &BTreeMap>) -> BTreeMap> { + corpus::INPUTS.iter().filter_map(|(n, _)| files.get(*n).map(|b| (n.to_string(), b.clone()))).collect() +} + +fn section<'a>(text: &'a str, heading: &str) -> &'a str { + let at = text.find(heading).unwrap_or_else(|| panic!("{heading}")); + let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); + &text[at..end] +} + +fn extension() -> String { + std::fs::read_to_string(wai_dir().join("extensions/avatars.md")).expect("the extension") +} + +#[test] +fn the_inputs_are_the_pinned_ones() { + assert_eq!(corpus::check_inputs(&inputs(&committed())), Vec::::new()); +} + +#[test] +fn the_committed_corpus_regenerates_from_its_seeds() { + let files = committed(); + let mut generated: BTreeMap> = corpus::generate(&inputs(&files)).into_iter().collect(); + generated.extend(inputs(&files)); + let extra: Vec<&String> = files.keys().filter(|k| !generated.contains_key(*k)).collect(); + let missing: Vec<&String> = generated.keys().filter(|k| !files.contains_key(*k)).collect(); + let differ: Vec<&String> = generated.iter().filter(|(k, v)| files.get(*k).is_some_and(|f| f != *v)).map(|(k, _)| k).collect(); + assert!(extra.is_empty() && missing.is_empty() && differ.is_empty(), "extra {extra:?}, missing {missing:?}, differ {differ:?}: run `wai_avatar_face_vectors gen`"); +} + +#[test] +fn every_vector_gets_what_it_states() { + let fails = corpus::verify(&committed()); + assert!(fails.is_empty(), "{}", fails.join("\n")); +} + +/// §10 lists, in its first table, exactly the codes `Reason::ALL` has, in order, and in its +/// second exactly the codes the files are refused with. +#[test] +fn the_extension_lists_the_reasons() { + let text = extension(); + let s = section(&text, "\n## 10. Reasons"); + let tables: Vec> = s + .split("\n\n") + .filter(|b| b.lines().any(|l| l.starts_with("| `"))) + .map(|b| b.lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect()) + .collect(); + assert_eq!(tables.len(), 2, "§10 has two tables of codes"); + let want: Vec = Reason::ALL.iter().map(|r| r.code().to_string()).collect(); + assert_eq!(tables[0], want, "§10's first table and Reason::ALL differ"); + let forms: Vec = FormError::ALL.iter().map(|r| r.code().to_string()).collect(); + assert_eq!(tables[1], forms, "§10's second table and FormError::ALL differ"); +} + +/// §7.9's tables state the training tool's report, `training.json`, figure for figure. +#[test] +fn the_measurement_is_the_reports() { + let text = extension(); + let s = section(&text, "\n### 7.9 "); + let files = committed(); + let report: serde_json::Value = serde_json::from_slice(&files["training.json"]).expect("training.json"); + let m = &report["measurement"]; + let f = |v: &serde_json::Value| match v.as_f64() { + Some(x) if x.fract() == 0.0 => format!("{x:.1}"), + _ => v.to_string(), + }; + for (key, what) in [("int_float", "integer, float"), ("int_truth", "integer, rendered"), ("float_truth", "float, rendered"), ("warp_truth", "warp alone, rendered")] { + let p = &m["psnr_db"][key]; + let q = &m["ssim"][key]; + let row = format!("| {what} | {} | {} | {} | {} |", f(&p["mean"]), f(&p["min"]), f(&q["mean"]), f(&q["min"])); + assert!(s.contains(&row), "§7.9 lacks the row `{row}`"); + } + for (key, what) in [("truth", "the rendered faces"), ("float", "the float network's"), ("int", "the integer generator's")] { + let row = format!("| {what} | {} | {} |", f(&m["keypoint_rms_px"][key]), f(&m["mouth_keypoint_rms_px"][key])); + assert!(s.contains(&row), "§7.9 lacks the row `{row}`"); + } + for (key, what) in [("truth_step4", "rendered, step 4"), ("truth_step1", "rendered, step 1"), ("detected_step4", "detected, step 4"), ("detected_step1", "detected, step 1")] { + let row = format!("| {what} | {} |", f(&m["track_kbit_s"][key])); + assert!(s.contains(&row), "§7.9 lacks the row `{row}`"); + } + let g = &report["generator"]; + assert_eq!(g["sha256"].as_str(), Some(corpus::INPUTS[0].1), "the report's generator is the corpus's"); +} + +/// The registered generator's graph meets `acc64` and not `acc32` (§7.6), and its frames are +/// §7.6's arithmetic written out loop by loop. +#[test] +fn the_registered_generator_meets_acc64_only() { + let files = committed(); + let g = &files["generator.wfg"]; + assert!(avatar_face::load(g).is_ok()); + assert!(!avatar_face::meets_acc32(g), "a 16-bit activation times a 16-bit weight over hundreds of terms passes i32"); +} diff --git a/wai/wai-rs/tests/avatar_face_gpu.rs b/wai/wai-rs/tests/avatar_face_gpu.rs new file mode 100644 index 000000000..54c95df22 --- /dev/null +++ b/wai/wai-rs/tests/avatar_face_gpu.rs @@ -0,0 +1,90 @@ +//! `face-int/1` on a GPU (extensions/avatars.md §7.8): every frame the corpus names, generated +//! with the generator's graph on each adapter this process finds, through `wai-ops/1-i32` in WGSL +//! (`int_ops::gpu`), is the frame the CPU generates, byte for byte, and the digest the corpus +//! states. Every graph the device runs is also run on the interpreter and the two compared +//! (`gpu::with_gpu_compared`), so the equality is shown graph by graph. +//! +//! **A skip is not a pass:** with no adapter the test prints `face-gpu: SKIPPED` and fails when +//! `WAI_OPS_GPU_REQUIRE` is set. Each adapter's result is one line, `face-gpu: key=…`, appended +//! to `WAI_OPS_GPU_REPORT` when it is set, with the time each path took. + +use std::io::Write; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Instant; + +use serde_json::Value; +use sha2::{Digest, Sha256}; +use wai::avatar_face::{frame, load, parse_identity}; +use wai::int_ops::gpu::{self, wai_ops_gpu}; + +fn report(line: &str) { + println!("{line}"); + if let Ok(p) = std::env::var("WAI_OPS_GPU_REPORT") { + let mut f = std::fs::OpenOptions::new().create(true).append(true).open(&p).unwrap(); + writeln!(f, "{line}").unwrap(); + } +} + +fn hex(b: &[u8]) -> String { + b.iter().map(|x| format!("{x:02x}")).collect() +} + +#[test] +fn every_frame_on_every_adapter_is_the_cpus() { + let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../live-conformance/avatar-face"); + let doc: Value = serde_json::from_slice(&std::fs::read(dir.join("vectors.json")).unwrap()).unwrap(); + let vectors: Vec<&Value> = doc["frames"].as_array().unwrap().iter().collect(); + let generate_all = || -> Vec> { + vectors + .iter() + .map(|v| { + let (g, l) = load(&std::fs::read(dir.join(v["generator"].as_str().unwrap())).unwrap()).unwrap(); + let id = parse_identity(&std::fs::read(dir.join(v["identity"].as_str().unwrap())).unwrap()).unwrap(); + let kp: Vec<(i32, i32)> = v["keypoints"].as_array().unwrap().iter().map(|p| (p[0].as_i64().unwrap() as i32, p[1].as_i64().unwrap() as i32)).collect(); + frame(&g, &l, &id, &kp).unwrap() + }) + .collect() + }; + let t = Instant::now(); + let cpu = generate_all(); + let cpu_ms = t.elapsed().as_secs_f64() * 1e3; + for (v, f) in vectors.iter().zip(&cpu) { + assert_eq!(hex(&Sha256::digest(f)), v["frame_sha256"].as_str().unwrap(), "{}: the CPU's frame is the corpus's", v["name"]); + } + + let found = wai_ops_gpu::blocking::discover(); + let adapters = found.found; + if adapters.is_empty() { + report("face-gpu: SKIPPED (no adapter)"); + assert!(std::env::var("WAI_OPS_GPU_REQUIRE").is_err(), "WAI_OPS_GPU_REQUIRE is set and no adapter was found"); + return; + } + for g in adapters { + let g = Arc::new(g); + let t = Instant::now(); + let (got, stats) = gpu::with_gpu_compared(&g, generate_all); + let compared_ms = t.elapsed().as_secs_f64() * 1e3; + let t = Instant::now(); + let (again, _) = gpu::with_gpu(&g, generate_all); + let gpu_ms = t.elapsed().as_secs_f64() * 1e3; + let same = got == cpu && again == cpu; + report(&format!( + "face-gpu: key={} frames={} same={} graphs_on_gpu={} (acc32 {}, acc64 {}) on_cpu={} compared={} differed={} errors={:?} cpu_ms={cpu_ms:.0} gpu_ms={gpu_ms:.0} compared_ms={compared_ms:.0}", + g.adapter().key(), + cpu.len(), + same, + stats.on_gpu(), + stats.acc32, + stats.acc64, + stats.on_cpu(), + stats.compared, + stats.differed, + stats.errors + )); + assert!(same, "the frames on the device are the CPU's"); + assert!(stats.on_gpu() > 0 && stats.on_cpu() == 0, "every graph ran on the device"); + assert_eq!(stats.differed, 0); + assert!(stats.errors.is_empty()); + } +} -- 2.54.0 (Apple Git-157) From 66054c94865ba333114d3bed4e9248071e2d01f6 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 10:57:20 -0400 Subject: [PATCH 025/242] avatar-face corpus, its second implementation, and the tool that trained its generator live-conformance/avatar-face: the generator trained on faces the tool draws, two held-out identity priors and their tracks, the training report, the seeded generator and identity, and vectors.json (forms, frames, cases). avatar_face_verify.py runs the driver and the generator's arithmetic in Python's integers, verifies records and synthesis, and mutates the corpus. tools/wai_avatar_face_train.py renders the faces, trains, quantises and measures. Co-Authored-By: Claude Opus 5.5 --- wai/live-conformance/README.md | 6 +- wai/live-conformance/avatar-face/README.md | 65 + .../avatar-face/generator.wfg | Bin 0 -> 645062 bytes .../avatar-face/identities/face1.wfi | Bin 0 -> 12362 bytes .../avatar-face/identities/face2.wfi | Bin 0 -> 12362 bytes wai/live-conformance/avatar-face/seeded.wfg | Bin 0 -> 5822 bytes wai/live-conformance/avatar-face/seeded.wfi | Bin 0 -> 790 bytes .../avatar-face/tracks/face1.json | 1 + .../avatar-face/tracks/face2.json | 1 + .../avatar-face/training.json | 849 +++++++ wai/live-conformance/avatar-face/vectors.json | 1967 +++++++++++++++++ wai/live-conformance/avatar_face_verify.py | 771 +++++++ wai/tools/wai_avatar_face_train.py | 978 ++++++++ 13 files changed, 4637 insertions(+), 1 deletion(-) create mode 100644 wai/live-conformance/avatar-face/README.md create mode 100644 wai/live-conformance/avatar-face/generator.wfg create mode 100644 wai/live-conformance/avatar-face/identities/face1.wfi create mode 100644 wai/live-conformance/avatar-face/identities/face2.wfi create mode 100644 wai/live-conformance/avatar-face/seeded.wfg create mode 100644 wai/live-conformance/avatar-face/seeded.wfi create mode 100644 wai/live-conformance/avatar-face/tracks/face1.json create mode 100644 wai/live-conformance/avatar-face/tracks/face2.json create mode 100644 wai/live-conformance/avatar-face/training.json create mode 100644 wai/live-conformance/avatar-face/vectors.json create mode 100644 wai/live-conformance/avatar_face_verify.py create mode 100644 wai/tools/wai_avatar_face_train.py diff --git a/wai/live-conformance/README.md b/wai/live-conformance/README.md index 59444a2af..c172f1d6c 100644 --- a/wai/live-conformance/README.md +++ b/wai/live-conformance/README.md @@ -34,6 +34,7 @@ and the independent verifier of each, beside it. | [`exploration/`](exploration/) | [exploration](../extensions/exploration.md) (`wai.media.exploration`) | `wai_exploration_vectors gen` | `exploration_verify.py` | | [`synthesis-mark/`](synthesis-mark/) | [synthesis](../extensions/synthesis.md) §7.4 to §7.8 (the watermark scheme `wai-mark:tent64`) | `wai_synthesis_mark_vectors gen` | `synthesis_mark_verify.py` | | [`upscale/`](upscale/) | [exact-upscale](../extensions/exact-upscale.md) (`wai.media.upscale`) | `wai_upscale_vectors gen` | `upscale_verify.py` | +| [`avatar-face/`](avatar-face/) | [avatars](../extensions/avatars.md) §6 to §13 (`wai.avatar.face`) | `wai_avatar_face_vectors gen` | `avatar_face_verify.py` | **Provenance.** Generated here: each directory's corpus is written by its row's generator (`gen`, in `wai/wai-rs`), from records authored in code and from fixed seeds, and each @@ -50,7 +51,9 @@ model file written from it). `joule_moq_e2ee_vectors gen` (in `joule-code/joule-code-rs/crates/joule-moq-mls`) writes `e2ee/vectors.json` from fixed seeds, and `e2ee/` also holds the test vectors RFC 9605 and `draft-ietf-moq-secure-objects-01` publish, as printed (`e2ee/README.md`). One recorded record: `metering/relay-record.wmh` and -its ledger, which the reference relay signed in its test. +its ledger, which the reference relay signed in its test. `avatar-face/` reads a generator, two +identity priors and their keypoint tracks that `tools/wai_avatar_face_train.py` wrote from faces it +generates (`avatar-face/README.md`). Each verifier is written from its extension's text, shares no code with the reference (`wai-rs`, or for `e2ee/`, `joule-moq` and `joule-moq-mls`), and is run by @@ -75,6 +78,7 @@ python3 c2pa_live_verify.py c2pa-live python3 synthesis_verify.py synthesis python3 synthesis_mark_verify.py synthesis-mark python3 upscale_verify.py upscale +python3 avatar_face_verify.py avatar-face python3 ai_usage_verify.py usage python3 contribution_verify.py contribution python3 settlement_verify.py settlement diff --git a/wai/live-conformance/avatar-face/README.md b/wai/live-conformance/avatar-face/README.md new file mode 100644 index 000000000..cb3e1a6f8 --- /dev/null +++ b/wai/live-conformance/avatar-face/README.md @@ -0,0 +1,65 @@ +# live-conformance/avatar-face — faces from keypoints + +The corpus of [`extensions/avatars.md`](../../extensions/avatars.md) §12: `vectors.json`, the +inputs it reads, and the two files its generator writes beside it. + +| file | what | written by | +|---|---|---| +| `generator.wfg` | the registered generator (§7.2), `WFG1` | `tools/wai_avatar_face_train.py` | +| `identities/face1.wfi`, `identities/face2.wfi` | identity priors (§7.3) of two faces held out of the generator's training | the same tool | +| `tracks/face1.json`, `tracks/face2.json` | their keypoint tracks: `{"identity", "keypoints"}`, each frame `K` pairs `[x, y]` in sixteenths of a pixel | the same tool | +| `training.json` | the tool's report: its arguments, seeds and losses, the generator's digest, and §7.9's measurement | the same tool | +| `seeded.wfg`, `seeded.wfi` | a generator of three keypoints at 16 × 16 drawn from SHA-256 of a label, trained on nothing, and a picture that is an integer function of a label | `wai_avatar_face_vectors gen` | +| `vectors.json` | the vectors | `wai_avatar_face_vectors gen` | + +`vectors.json` names every input and written file with its SHA-256, and holds: + +- `forms`: files of §7.2 to §7.4, each `{"name", "form", "hex", "outcome"}` with, where it reads, + `read` (the header's fields; for an identity its keypoints and the SHA-256 of its picture; for a + segment its fields and every frame's keypoints in sixteenths of a pixel) and, where it is + refused, `reason`; +- `frames`: each `{"name", "generator", "identity", "keypoints"}`, the files by name and the + driving keypoints in sixteenths of a pixel, with the SHA-256 of the warped picture + (`warped_sha256`), of the keypoint maps (`maps_sha256`: each value a 16-bit little-endian + unsigned integer, in `[K][h][w]` order) and of the frame (`frame_sha256`), each picture RGB8 with + rows top to bottom; for the seeded generator also `warped` and `frame` in hexadecimal. The + registered generator's frames are those of every frame of both tracks, each keypoint rounded to + a multiple of 4 sixteenths, ties up, as the cases' segments carry them; +- `cases`: each a case of §9.1: its `policy` (`dual_layer`, `assistive` and `second_layer`, + synthesis §11.1's, and `generation`); its records as JSON texts, list by list (`identities`, + `tracks`, `claims`, `reviews`, `label_policy`, `player`); the `objects` it holds, each a `WAI1` + envelope in hexadecimal; and the `generators` and `priors` it holds, each `{"file"}` naming a + file of this directory or `{"hex"}`. + +**Face-equivalence.** For each vector, a conforming implementation reaches what it states: a +form's reading or refusal code; a frame's three digests (and samples); a case's `outcome`, and +refused, its `reason` and where, `at` (`["identity", i]`, `["track", i]`, or `["synthesis", …]` +with synthesis's own place), accepted, the `statements` of §9.3 (`identities`, `tracks`, +`claims`, `labels`, `reviews_presented`, `visual_icon`). An implementation that reaches another +does not conform. + +**Provenance.** No picture, recording or footage from outside this repository is in the corpus or +was used to train its generator. `tools/wai_avatar_face_train.py` draws every face it trains on and +every face of this corpus from a seeded parameter set with a renderer of its own (a shaded head, +hair, eyes with lids and pupils, brows, a nose, a mouth with lips and teeth, a background), and +places the sixteen keypoints the parameters put on each picture exactly. Its run with seed +20261022, `--identities 6000` and its defaults otherwise (a 64 × 64 generator of widths 24, 48 +and 64, `gauss` 1024, `bg` 6554) trained the generator; a second run over the same output +directory with `--measure-only` trained the keypoint detector, measured, and wrote the identity +priors, the tracks and the report. The training is in floating point and is not claimed to +regenerate the same bytes on another machine; the inputs' bytes are what is pinned. +`wai_avatar_face_vectors gen` writes the rest from fixed seeds: every key is SHA-256 of a label +and an index, every time is a constant, and Ed25519 signing is deterministic. + +**Licences.** Everything here is this repository's work, under its licence (Apache-2.0): the +renderer, the generated faces and tracks, and the generator trained on them. No third-party +weights, data or code were downloaded to make it. Before choosing to train one, the open +keypoint-driven face-reenactment generators were surveyed with their licences (extensions/avatars.md +§7.9): of those surveyed, the networks whose weights carry a permissive licence need operators +`wai-ops/1` does not define, and the rest carry a non-commercial licence, grant no licence, or +state none for their weights. + +``` +python3 -I ../avatar_face_verify.py . +cargo run --release --no-default-features --features avatar_face --bin wai_avatar_face_vectors -- verify . # from wai/wai-rs, with this directory's path +``` diff --git a/wai/live-conformance/avatar-face/generator.wfg b/wai/live-conformance/avatar-face/generator.wfg new file mode 100644 index 0000000000000000000000000000000000000000..b539ddabce6a8fa40f3814eb8770ae1efbd14a8c GIT binary patch literal 645062 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�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

c69vxq*`(!Q}W!BGkTWq><(2 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

EreOFNZ=P2JtW ze$LRqJoN^osO28HUM8jwMX2TXHGmx-s$R=a);X`%gnG5Eh{(51Zv zU4kmB-0n3jPazOW3#6H@B-pO(SoFl2}U9p(_psX z&BV0UlTW-nP}lwzNEx^g*+k8@nuvpT1^Uk}au&$L;*8vF6N7{&Jz-LsJ#OW$7urdgDJo@W2)A808ThsvQ$`-Plxh0QTq0DXlK*fcZZPps(TnFZ^5Vn_QpUCka>KxR2aU*AMgQH5Ja(F=@az8NTnLk<1omcu4kISM%{oN=wwRU#jJI* zp!`Xvp10Vt4D=ibb#}qkXS6$$H+DU+6UM5(OhUfRE_#F>GW~-a-R1OSsbJRX6?Ajl zD~CGIv|;YYKx~uEJ4|J6XL5L|Fr?&?%;NZn`1a5P)j>B*?-xg%HDt$>Hwk=itrtNh zL){J_krZl zn~=NtPEMfn+k7I{56M7EMlQuHbQwL*OmcGC+PKPX3wv_eETls3nCz$;fVwI&B_a+u zHLYxBZX>IeMFW)%t$K?*oC)eU`6D~vi5Ao2w1pb2#}jw5L4(Ud$rp$Q&jtN@b13qu z5%Adi{nX6fRU`ZDCFzxy-J6~S9CUM#mppSVw&u_p*SnA{zcY4bCKzQ^oZv3YpwgTh3HE~<05(> z4Rf|o)l=KeB>OpQ-Q=$iZlZVl`-wpmTb#Bg#|imI!os7wP=Pobj1 zJ6r&@c2#3U5j~S$D{4VmAKM7(Uh)4 z8JG{+)!oiy*Q%zr6^9zH`D9yO#=i8R#sXc9XT1*Ieh1!B7UpQ)wR$5Bx2+zcudlQf zqPoixzJ1`-cwnulMCMk&pheQ}JAw?ldhSzkKt+;8;#HZHz|-`mJ>G{K-@_hu5RYMG zXORtd-c4gme4PESJ{cG6&an==oM|znl_9~oAfa1)0E`>fm zXj;KI?<4Q_nPDy%{X=ugq^1ecp*QBN+gSeLE{?(*HCW$~*rK%N5mB>Q#NIxeAtt)J z$f-%4b17D}f()APbaLB>jvDPw#+q;Sl~&uBN0`|6lL&7c@WEkF!Z)g(`rxmAC8z4X zn@RT|tEQ;wNzd3a_65A(nfU7`@Y%xfa0`mh&S;rh{lP~$k3BuCi-D**k@emXec4v~ zn8v+aE$6O~*=m0KZcuNvLixzc`v}K#luE56dXyO=_oA5^Fz+O#*dW8@NAS%mHQsd9 zZ_OBYnw7|X#xqY#HeO$R%n10J^7xncIHPgI?nA)N3BgA7SyOguFI!?;znlDI#N?np&p2 zii3Z-Qrr#xD|d)1}}*nT^!fdBaSlD{w6XutuTCQ+9mnMcjx> z<^ybJN^7N9sRHDq_a=&ahtrQEe=^CY6@J)Joyi#y>`1Kpf6UWKuZ1rTz0fY;18;_P zY=FLB2KO@*wC^zA?uLp9%NZNCWQEg08z<78V1>E0^=bQ>Sw+7-s*~Wxn9`=g+JqgXen;Tk$^_$d;yrF{+rH z3TrWy8tr8|gu42k?Am*o%70hRuxt^6x3Qk@{7<=bxciT(zXg1=U>uWMnVrgJIe9+O z%o`Z(Ds=Aa6v!s0fP_0xvk<2Gk?Ye^mUQ1+uY6gFpu8qF69KpIKXzNfrKS?a)e2B( zW8}R&NMKq>f03MtnnW$}xLPtf&w~B_Z09qR(U3hIzx%}6L)1uS@ z2M}c@`0lu{6hD!M*kqekVXE0cQ(LT$wD4_S%Vp#)WO0{SsZBBw9?b1Gsv8mEQf^)sA>#T$H>*mgA@;%?_fn>i7<@VK7WvzVl312}!U#}<4CF-FpyvZoA9Sd21 z1#X?sO-;-;v0ITW)OK|5&yDtv1*4vTZiov&h)wY;^0N{Pwr~s4<2guVIaueV@b@9& zvE9SjNVIboR;Ma@tR7adyeWv~Pfxve=|5ZXDL((6NsaMMk)Aw(I@rS8V51{Mk_?q1 znW*M{4WFEv8L#>DbzQ`MBi7QrE+Ji1DukXOYdNlH&2CR5YdR(@*L&SrLipU-xeL2Qd^B|@ z80s8wjR9hNiQ&Z8Ge>$fIAVf{qZ26lcFB04oI6e{_1n%Q6N#M88DC3c5l7h#dhyZc z=mLz!WiJfc-f3%TK zWFO22ok6FzCkNvOZkwV^+Du8-z-qHbmD7uyT~yjF0d*{~-`E}Gv54--f@?MsM=Obr z9_XHs7vaF*AjLy6>E4k|Q^Q=>3Has@R7>AUX#hJpl?Ay4>MPh z?Gt1SZIUyX+x%635=GTKFMf-sgim0iQt9}>xfz)aOfXm0gNlXZ5RuGh>3_*FR>Mi z+g${x3`#_C(Xp=8QcRuQI7RN(S{j z=846GX<3J*o%uJf`z4VhBJN~CZ6>QGn|>oliMsYw`<98#y{S(pglX(_U9?OF))q?L zSrqmxA?#fMj;sat=n%U- zaA^}&lX|yfoZx)=5+7!s>`1u!+TgqQ1k(OMJ4hsZKyQ)N*lsR3f9pE*cYOaWr~19ccOQpi1n9Xh3>$^cbsJ;;RZ zM!f$9{ZyamE$EQkax{pj6#l_{B98y^jZ>Lwx7KWAFMD89lhFac2+@jHwi1NdNR__Bb{@}$FA<~*1d_0-o=~#BgdHZ zuy0}RTIZ#qA_eAdr}ks@>wxoe!MSH=r`n@8n>#znn~g(UcC+a3*z8v=IEI|Ev?>qw zpDbRR=xP(J#W%89^U(9|nu%tT$Qxuz_|SH1&~6@6S_BU96*u@SJS0&lSqqKTMLfi} zSOp_o5s5yj^26sxBeJqs=Ay%BQ+FZL|2nIwCO&mJ6?HFUC^RIEucBT>tY{D%px5_J z<3+D@bJ&`x;1!6q|EtT0KEZPKN;y?#l>4#c9n27sTRo>TYd<+!(}PjTrz``1+nX7E zmY8PJnM-Jp$JEX)Yq*rx3bx#KkGJPJh_*|u&KVeXYK@3s%lnOSCTPVL8TSLtaSJsi-_y}{EOv6iFAFX zpJ@XSP&U0oSAy%kyG*+<1^M^vGYsQON?w*FK+dMJMkRZBg%-q zZQ^D!pG2g+p4`?&u6^<5thbwI=$5$8*D4km>KKBxFfJ%IKI*|@5_1Z0Ga)>x)J=(3lY)U3Y8T9W`EJ=S*`%Ad8kw`~6 zVqP!d6;6|>myqB6055;XmQ4qVdHqTr!OI8G$jz}2k^Gz2Z|gZo-peT~f@~hbvPZ?Y zctU29%gH1`?_Ng-!bzd)Jik9LIbkK?NHlNjMeXC^m3UK&vhjBvhy{C7j=WiHUx*Cc zf{D2adhxopwm?d2po8BKonFb`JSDEE(1~6Id<9-dD4O#+XsEw?47uq*6tXo;av5-B zJ$Iu%qLblKCu3&Q8}dY|5rLh_`|ky*wML^?MkkEr`8n97tkj}5}YUYes=Tr16E}#O{sKLY$DjB5Fbse>yU+PegkPFK zMCTlS5}5|(1-3G-m-Pu7dk1Nvza3GJ-&o@#pjE}2T;V)2Vs)+)5pO|NM|A8&bJpW! z_YEUraud0&4ni$OWThWG-CKTEmt8D@R=AEv_Tt^cV5qvXq9A*=ic{#pl)Zd-*y-uP zP@OZHh8C*8@3Ql59oak2@=U|Z$ARk$;Z(|EqX)sRtzq@k(ccsB7rdEX%*z66(D@E8 z^E^*}4(iTKJS88QXjPE*XzX2GeDX}(>nCLLE#S$?;XvAACA=LrJo_^HU6MF`7^i%n zorps$w=VK^k!QumoABlyd;-71(<22B$q#(Vr*gtclmLHk=FV0p6LC7+$ZU{CFSN2Z zr)~~D_--QRUJvgxd{axjhiaVTexla-Je-gJJ&3(;#5&+x3>-FRcuTL+{X}Q7PaYzV zcRAPH$owNXm!{bNDa6$-x{+ka*X zVNr9N9G_pNE$A${`wnKi28>32WGTSyc@6W^3QMm+YJ2$S6nnG^*?*5u(1U1tEEs|6 z*v-R#-}V&Q1*wq!fnfR3^e%`C#vjAGpMc9uL7mGCW)GjD&t6wklXc(Wcg2XDry;L5 zloc0ZAE)t;m%};_jr5b7v67W!=coOV-pc%SW2EX5x&KwLfi;o5OXMeXVNdJg@6?BR zuRu-YY*tfN6oyqPj>Y-NZ^w{B@fhErCHu7_IOrKTZwL129QrOVnLEFT4Aub`{p19G z@X6EMmQ=_}arSYTTiq0v+41FrvVhze(1B+^Gg-xb&wUy>xWn6=Vsg$HdJ?7OX-u$` zQ;A>Eaa30#U;C!cEqlOz6{O-~G!fh(Y%ULD$rguju+r=~LZ~g_h40jXBau)pt)Gw3InqYM!UOXIJ zoYt*kHtPN0gV=cd>&z-ie-V6%GTga0!Zq9UKDYO`|5WG5G}LvUw@6%)%D>K4x7ZGZRH`I0?A#6{XB&1ra*gMcWeBP8QCas zR!^iaen~153)%}pCY%3)DO4(1$%-jzn9kNqFx+G3gSq3qnW+Xh^vGF_mQNelqhG+~ z)>3uhdkedR%m|RzP4|J z1G-XRzmon;+?H`vD5P;FsdAiMVp&k^p}O;`Ny`o#Mc0Lc&i_T9mk}?JfcI#Ebl9;U zRA4@XhpCUJvJtH^7X4coe%V!;Oo;tBVFGX}O=MLkt^TTa>Lv8aC`CSJJo>7(AvbTZTFTmsU?sZ9*JdV+=3_ju zl&m2(k?{xEoiynD^q`D{%$j>1sBRTtp4L8lGxqB2A5F$aN^6D4PX%9G* z(@r>=s}PK7YIM&jPBkCi@FTQPf3U_O?rQM<8|9R}KcK@47z{iB1Yf-&*m9*NTGI5nuZK~ovQKp$X6?}G`l z;0oI*A!8#iFha9fuk999K5hX zYA^Mj(_qVTfIcQUO275DB9rByz9+0;4~I(?kJ0~QG-D*RDKw+4}e z)j*{pi=(ygf5dY}n~Yf0a1%5WbY9&EMAVYIHUN8?1ms+aOoyd#9|7@AMzi|S4@ zG<5!ceL^mR8A;$z;j0GM{NMW(T@jIJLX!F^jqrNl$*{{w5i=>an_kxxwpvmPU! z+6k?)7RIa*dBw{dy5^clw-*t`Zq&hkKz=5JO}y&h8g%HXXCl2SdF!q!LeO2NV7-2bcVQDu*xu#t)M<)Z~GeNoCbF zv-B{L3$5H4aW2w@sdaIW&5Z+ zsRqvb3aa~p#_)7Jp&f18aSILd#$Atu@o zygYzYodpK-Dt2tNw-1l$Gwg2*_@94?t$TgAy?)$GMCGKs9%7-~o+Q+R6#}9D;U7Qy zJ`XQqA#w8PWYNYVqFeyRUvJ2Bpc^T>gWv_MaPCtk7v&!rD}yiq_Z)xmo{ZI9?EZJ&(DT+d!*##s zZl1>vs>io`U4rK#IdeIUlvGH+BYM<=6G%v%z)aJLCmt|Qk<`BEsMPGqBzCkZee2$v zuB>Yb`}hL+zJxTq!2*OMx373kIlO;~78nShupA%gJXWC~9?mxG@MU5!Z8-4>psvp_ z2RZTicfta0!NYk@UHL0|xz9JpV3EBU1n=Mivtac<@m#OJ#EQSUg*{<>Gov>zpljM7 zf$fpqMEGkz*r~2?vyZtk`}r>g_lyjY7g3MSyT7nuUrZ~icPi_oW)L|ux$rOg8~6^Q zPrLCNL6FG6KkOQnfcQ{nk>FxPj+U~P#q7dqG%B(lGHSl%G}Q$=}i2 zE#ZP&BO#5ru^TxzKlWud+|p%kMJzDd1m6D}+GHIbups9$FE?Z>(w&6cbP$Owk8JdY z86CKgoNH=RNLn!hYn%j}+BM8%^eU8OVk2iNBhVnCnSy zrZ-P27Z!B|_N6i!uqE%>m_5%7s=LhJdw#$XF!@mYo0G`uV6;a%v{Y&KHXQ#nJ*QC# z)~+b5$XMRwEvrh*vpnC@n|d}4#5I_cEK8+f2^hZ&=!0tPNcz7TI4?S55_?bAJM`*D zyurz^zhohTrMs{f0e-TZJ)ezkzkrP!z}{x!=}VCNbnM7lB>5vU6@iSL41KT#d#*?UU_{$^YG^7@i<3u3hB6!uegsZxD6iLc#VA?h6H<6CS|$r z-rS!K9bFx3((Ykffja~=VO}yzG&Lcm*>wI{CP}WnNPo)AO5bHu}>XEJC)p*86!ji)> zU*o-|uw!9p=|;pGJMhffyzzQYq6s(dCucVR-L?C#gvJ1e=7C3?@pq+5*h_CJ(IMVA z20L?#pVmf4M#GCJ&!-QfhqJL$WjN8-tYr&-`<>rjgOl+p9q0m&)#=aA5Aw8UfA436 z{-A4MW0IjcV{tp%akE~qKOcFs{=DmT^m82|;4?rl^;v0UqP^9~?TwFo59eg!;U6^O z<_so}rw@8zHFXT7`1fmQy+-Kr6tHpg`R|K-im9Stz5blVOWrUXn=%|p=*r)DdZ#V^ z#UgIdNp8#!?&xs#s1*7-Dt{S{eAdU0IL#T*U!32yz<%uKQ{DulLUdQk#4TH6!a0L9 ztaLhdRU^TBk-IZU-)26+yhn6LS^d&ca%x<*mG4uu-usJ9$D{&MX@IN@{-MRY-^NH@UD=WB%^>TTaXC z3b%`&Zo|SI=PC1$guUGF9DLJD^v*o~@;!WSGFCH;XHMmr&5+7u*cne^`of*y-=eeA z^S5)bhAoiT1$<|3P{BcTlIH;(W{*~JMl(2tEu7&bB=!;!GUNTAop_AS27AkalcKJgD)VIP{K7ScSL6`tVkP2-#Ya2l6*&SLIX z7@tkUZSj!BFl3ADL$pW)@3#|8V6oSY(2&cybLDxnYV5@+kkS&Kw2JumR#06pc47n1 z=)zma;_VvqM(IFw>CuN7|K6q$_IWekwwI^w=jI&XKAq=wG-Hh)xIaD6A|95Ti!dD5dBmYO}dDa8_zv|OCEVCWGx$OKgNx{!uQkyDNaG#@8dMR ze&%!d-=6G%mvbM+d$mGy)dG`woo+r8X`IRKujBlRa1-b(j?O=bjJM=o_T>)$;3h=p zZoI_0dApq)kEag)X;h>o?5_p&s^7yofs^2j(d=N5Z(YlCCd2%WLHBulenl)xQ{HR= zwlOCe7?+Wg?D*bE@mk6dZF%w+=}cr_yP*+#@R^BVOD{8LIiF$9H0!8=MH5iQNNen^%KIdH$AbZdG%XV;1?dU}kg|q(2*-YoI zltJ^v<1LmUS3{ADkDOOE&aMNuw+Qmo7U|!}?o=XEa1rPK8Dw&ld-4aJI}E$%&92*q zREz>Qc1E)#1+8yFD~5PA*vM{Io*Tj`eIn`-n-krPUfBc6ssKie1uFBg@D_HJNyz726#4$dbP&7ubq~Lo` zu$K3K`ChL=HWjugI%}E5|KEfJ??xu>ATMdq#xvNHmF(m~^vip0hlgs$vc|z6zYFZ@ zFc9`FHy3xPHBw!G^C`dz+o5IVbF$~K9t+r;Mc~yJ?7$b^Z5)4}lGwvbP=ya{lO9WU z$MeI{NLRQ$N6?6s&{qSkhQ4&ZNV$!AgwfVlrrRfy^m1brtI0Dc$h7hx%vMLiRBhfn zg1#W{@booxID4?;lSD1+hsx)#7&61{F5kFm)HE26%d)u+aat2E%nsfi13QqK9*GTn z1MMWD73^eUCnePet95P@0e1a`eTqUH!Q)AO(rxyM?5}L6iU_4+P6N{*@{#^-vPvH~ zI}J6-oRx0Nsk)r}HhPp*x3p@{e)K|4jw2Z{@XynLC-4>JH1~mO1AjW6-#up<&@N4X zYP{v4VE1ZjMra+KiK&LG$SG*+t}}b-mUEkmi^;^TU7}pY^hkT$meFhx+5>FgE>CNV};= zmUK3FNHwZqGc${Kopsi&X;M*1yHD2*@}^|I04L5%XJK)f4{X#Yf(Bbh>EJ3AIEDVY=4{s|&{98*t%l3OI+IDr$uFRm z=YY6BT9xccI*I6Ee{-9u zZ~k;{2YavmoV(&-=vU}oyC=?xk)|~BEV{zXB&b$T1MH0+>}wj1M|H)U1klfFiq7sbxU@3 zCQ(70!QG^)`4*COTtYo^Eh@HFW5v8a?MB$iUaL^_e*#@4-APyP2#sDiIwk z9!Gs_Chso6m-o2-)7weit9FnZ+r6gY(WFD_Un>35sZhE@hFbeoUfIw@sj2AjWXu*T zLHH8M`7Yla3&{{j_(wg z&t);woZY^uYh#r+(!rKjwIj>19ltHFQLv|-k}=#yLAKjGxR+^w##@ekSmFM*4+&~u z?I~s}&yEjo#VO}IRwjlHmKU&7cg0a_i#W#ooxb>Y>*x&YN(EG3o!hPse{YiLr_@n0|m`>E7E=7WQ-m{R^XPbubFEo`oe{J#?%zaEKWsbQrLby1}t$BGXI9!$U;cv z2&`3K_mjQexnb8rKB~a;)03RoT)JCMn}zgPy{9+hx!%C6_Hewu1An^DCt&~Q<4LAe zbIGydpHtl$VZIPS82h_@RyU$g9~9&8)&Hh0sg_A#7C1-giF~H31vOU1t++%eh3$-R7+=!u)e-!9 z&{W@jtEB!4r$l}1R9`cL-oy!bTp79N#o$98kBpxe1)@7)aW|TSW-Z?8G^aS{xf9lp zSK^TRhPOJ!ilgT37 z!%}FcSL{R)s*uv-J?urNzBMha_3{hn-c@H%bMUJ7prKFVN!7#0Xn-HHLheKtgsExB z`Bglr#BlO8;zWW>Z?a&s?0DiYSyJr>>(k;Kx2+vB*)G_F?SXh~hvBM%2~>=Crn(F4 zpH5sVD)Y)5RFrPD#!)v{1+Li!sy|g>AH{oF8>wjwp3)V2{5RfN1@V`!sY#&YTLon^ z>U?H0YatjW`@Y~&>8x6^TF_uRvNoHvqMvoqOmWX+1B1=qo~LR?=uu$wxmZ&S(;sQ| z!@=UAO;?Eu8_A<0HD~Xq<2V>^J_xOP%%sHAZ-f7OnTV(lo>5xP;t~;041E0U=;MV* z+k&WT;()2*u63HwYg1BNFyF6rJ3F`JLn>%K*dgpIeeEW>y~&?0P}WWWPkwcsNr7+# zGRYRyNgMzP`)YdYgs$T&VeYCI=3aDv{mBfr@>$`M3>UrEC*5hXpjxTm*|jz!0e#8& z-xE^b$9FBqH=N+^wIn)U>K0*E*kSmSCrbLHqbBQtc6T&uH~PL8ik=O^kw4@_m~cm!P&lOi6_KWr{Lf9Go|&Qci@@K3 zXHjVbX`QWFQQJP1`PM%`01oN$#D@JqDQ}9VGMd})1uvgoEfeYXgaxx5Z`JL&rJIin)x7RB53C#^Od>Y6ZAcQPuYiORv=G41%JGXTSPY`B5N(%%7!|p z@24B&bR@Fb`J=OnZ~FMFS;w@`#J92pGMF!c+itM$H`qu#{NQZ*rIVHJhU;p#{vOEe zG1<`pp4RNM3OPg7Q9RZw#HLrxV(eNS-4IsK*2H?}=%;-}t;;%$zXOWiP7Ud@!Yd7Uk436%%hN6cl(I_IM33heN-`8F!E>Ca>ZXG?xSU zzTLjZuu8@d*<;oMoyjhK>6H3E*%|BHoSW5EmJrwF z7^YDywbG#50`3F3i)isKJb3AmSB+NjZdoW17ZawnrE!({o2Ye<{zb>m z6g>Koc#ALax_T2m$9Dfx%xN*(t!DWC@nur^x4+Skiy_a|Mfk)|;&s$OA0M_YnSy@u zM@*r6Bf@oCC&<|)UzzE2MV3bUMwuf-wyVf>?IgCFXa*239f3=FGgkO4=)!S2^5F;J zEK9O4|3lx_03CTvcSA0GjVMs>GWaPMxTkrnc4QO>&}Wre{!i?-np%~J@fxXQ_%sLQ zLzuoUkdxbQm7=!dhx%=<1a8TMu#6XE(!mth7F%=#_HQ8wioHnX(#B zHNaczi4VwRSd-55abM^@`Z&yvE#XGGD+{Uj?jlaG0r*E(s`Vc6R{4?Cm+nLzrjzTF zdLs5|fxoQ1%rx-*C6k+B@`U`MF4=`$ZJpP}VUKSBW;BdC&qwkjxyrr7@>}rTL+IX2 z$<5y_k24`}nstTw7Q;jhxy5}eHzIAt!8$Y1d8DWYjbwI3C2K6Y{Qzfm2;3{NWD+yg zN=fODF-|w`K{@NH$?mHsE-*)OPtdqPC7D^=)tN+9-I54o3)2l^b2hci7rl-u@4PaB zn5N2^^-gzo>=+#{MSb@J{mlkdRF-u8fg2{Aj&E<``($F4iPA-z6~eE{Ct9dIbSkx^ z4ro8JH;;<=xm0Xq^NrWf%{{9F5yBnkc~mUZU8mN0@%xgRmDGTZbUp{3a`QSe4WYPs z;D-1L!fKtH*_Ku02GdZUg%d5Ys*Aq}n$P}D!)mN2a~329IEUSOboVV4^{tA|I(wg9 z5R{vFwV9whGc)AiU--65NvMSWs=WNnoh&V=vwB`1OVnM#9{vWh)1*umy});@Ck`m zk1ns5PJ5V(OUrcT7hSy3(&);t0?c!n1_CZ$+Q1)%-S){g;BZexPdS_O4}wXrEFC@J zG8tK^ym07UW_H3vbU-91#X9ib_C!td;JyXNQ7Ncs-=sgAA9}dG6m76b!59F;PB*hm z9>Uw0Xj)NOebDrOwHNUSo|bqZ%9wtNV=$6#68Ov9V%4LY4?CwsmTt$BB2m3k*v?LD^$9496E%?z;YFCQ5usHI~`N)wTB;rj*rSTgw`p>DI zAh(SaFQ$%eJQ!pqRMw;f66G@VnN3zmJ4IJzSpTAz^hj;tm(vB(zkMlimT-&s!0zt{Db z&}?zBpP=91;~`&Aj8px9 z7JiNPs|8k{lL%=SGE<&7XaTpVzxxfZXsD@S{xy%;#hhq?DcJql_-AMNcNK3r1i#&@ znS8`8IE74XL}nUdy?zm2Z0FSa5Nli}Z^e`>^3*3;|3z-;64qITo$|620es-o_zvF8 z8^O0t=1!+T8_#qFSu&4#)ngH){*Q0k3%<1vyK{|Ip5iID$pj>)N;fun$|K0nU6^@G zntasFZZk31$^Cr82{KE~I5CfrFDVFW0^$|8Ex^pbfC4xFvHq{lM|{VuLAlf{8R2_OHF8yYFYEgn%XjW3|9Yp2e|Au$>EV`k z7BK^Fvk5`2b2%mcOc}W+o2%Yxx;d*7_%Hicnv2}SF`QjF{5XZ@@}4`h74C}&5v_mA zHF5hmT2*#K%nEv&<8vA@@XX`jm8SHC@d?ZKRWAQJBRXAF7bJ#hY+&$j(RR6c&MG8t zEAjXnvMJZ(4p8ZeZUq>8mb>qLyVRAa1ZuJ?RdqKS9Q|+dr!ATPU4lsNGO=7^EO&nx zD}vA_7p+k$4M^p3@!N^1Qpsy3VRY25&i+2`V*klNe)-F}5}80`R=f00-A$Djr<~U+ z7L{E|6;ldjUAfZg3Vv7ET@xHnez9u%l8U;{3Vf99rj-iA2khk*U|RDW=D~*IzJi#76{_fg5>N%k;4;ii29&*i_VH} z8F|Fg^0afpE<(I@*uAAbnAyL^S=Fp%R6F*9)xRWJr=cn?@$OKR;Se3e*VLXP^2HoO z+foRf0+D2Q&f#0F2ER!q7K;vQrS;1Uktx-)uOFR4rmcNU*OUJPq1hTF^*noq&kw4g zMQWMN3?WvHg(Xi)Hip?@)Q@&Yjw_o5bfUR|;>5A_jpa<^zD1~-+?n!XxwVtrYPR6? z-?vtat$n-NCXqb**5&~ zGMR`XW}}1O+skZvbY(Ge0Edyk{B&QAi{9WLV^31YWM?J|^sq|1Bayq9R-UM2x@T1L z=;ivhY4UmTua(i??dP&F`#MD|*4g=){?2FaR44Ka2Ha^Q-cLud%=yQ@8+Fxb2*Q09 zPMHy~A*W^@)hts{zO`~iUNhxc>sDqHF7#Gme&S_obB5_}w{!xc*|l_*Emmi|elX7e ze=u6CfPEy3IN{}C?G=FtIoloX90!qwE1%oC2>VqCkF+NFqf=5!9CijT*B@kD&59BoYU{+4*W`&R13B~k8D+L}?e=yN!`;RbP zriN}4Jy#7CQ?%djGh?`ykDRL}vz5h3YV!K>%Sp`L*r^(cn#B08ovh9neO&$H>n$(% z6MY_Is#qU5jd*Yeokw5wN`(lPq(2ckWhOaNCr%Z3% z69cs0oV5F3M;E~^oq_(ObXKIQt4GiU8#6G&ool~}9;^jjyk|k)s+uaIyH%ZOk@1;j z(Grf4Nc*n)P$svt8Zsj6YHRb@TvD)rf@fR<^=&{u#sNBgye^+M>WTeD=TQk@zBnzZ zdDUBqhCO!n7g*MgSkyD%kAC}EHo5Vk zM-hpXLl42;X4;D`&IaoAsSpM)dH=^(gU5jz)?3xI`okdC)voSeDA&r~fo)hpm_Udy z8c};ezpwj=j`cxSX0?Idfk;WTqHl&#pBw5+XKvXIeL}tbSsoN25iF70sg3&yK9Nqgw+cJ+{iR{XE2@U*+stUGXg~4C z2ba0aDgGqieb>2)E(-*{Q(-wD?@EyGItZ>5M?Nsg=%#ImZJJ7@NlRj!8l2)S&Oa?N zY(J33o9LXHNDY0r=%!id>lyA@`kH!4VMfWz?sED!+7MIB!Adl>GRu;>EnU;;MN?)? zjuR*SFX0PF9hH_kQ{mY13_fWS_q?6SuBYPLlfZvY2Ja^>nL@VaHmhCfH&VZTltRzxVx(2=H1)apKI5FthXCe%byZ0q|gspCFUuUdNYF``fWrWq!O!9hM z$dK*9`~8IfydBTaQh%#{WQwb~8FeYvf5M-QH@Zjt#w2{qLZFx>*wIDIHj4!(K}FTd zRceY#tdq+cx@zDfzVR3Pur-DLnbeX=-XNs=!M80rii(Z3bi;Sz$>e}VHahtFyGwM; zpiJsMa`vB73JrdmOpV9*+X8u-4&VF*w~r&8PPfm3e*H}lo3FZmB^ZW{6^!kr`csRv->mBHsV`8mnF;bKffc}(wmvOk<` z&&jE0B8ycLo7-6x!vF1w2X;@Tx0ArKn#zvQ`%N*q5vF`%Zv6H)Xovf-Zl&~jRfmbJ zY;IAfmAs8rPe>G7j4|BxDwr7oCN9CD67OcL3cIC3%)w1-vIHmd`Z&4s`_C%#yz zJHa7XLM|tY7>lheA@_=_3dJhc%QWPEE`XCYr9yBp*^uk_6U?9_N-8bd=_hc0t^|?l zqI_86<8lwNQURxqn+hx|9$x+hvd@k2G=7`bc)e+fxN^%2#1~)PcT}Y%MDF{6k(X8J zmhrut>MJ;v#%3lcZfB!&cU{u38w0>Q}ygK!& zBAbcIpK|MV5>qZBF3byOS~wBS3*LSsdE)f=C^10XXJKy^!-n;oC%twD>D-`54cO1Y zI?`=P#$~(5QD+{&{#uC`xm}heRT7VeV6!5l8Iza!O5DVVU@*ZmI9;u&s~H**!PXQV$RJZnsTl!-;C zd_^?u6PfiQIQUnRx!%GpD2TnUMlY(z#d?bj<1!+eJI3$6lj*>^9#Jp10xxkTHK<;l zUkQF@1CgUwM|g~=PasEzo86Q=z9xPh&ziqdE47AwmH5Luuq7+;E3WhFJ3h4(UA~>) zm%%EeruJ-|i%ihx8UxLdgGlrkGIWiaiSuZKo!}u}cCr;n$A46|O#o?V#V40hU2~Qv zS0N@mi>G{++OedZUMB2S2F|h~erjz_Z939Xf%x$r`t}NYZHRUiK3`ezgeOR7K78*= z;QDLOIB`HOy*k8?pf7Zoq47gOqn7eL`M?rRfK#pJ2_-qv`&2?qXVq81r80r`gtI4? zc_Xh*)|33)cv3N_h4XsqtKq#)#V1Qj^~PKFEfId^9G<_H+RVAU?_oZ(82>E+w)QSH z4*7|2OR?Yac!y`KIyc%ZF7NQ0KdFoD^BBcTlBx6h@XO;n!uZNJKHy9h-oY`dz4mb~ zRlzY@QdP2$nBh8T!d$fKJl^dT^=S`y_uQak@2PU$z?w>td#R56Jp_~Zi#0FcGfg?; z6nJliIgcpN43DimH+!`MTrY~sj&aC-V$S@LIxDU+gB3QsP@Y65sL1Vu87BbbEQ%+CCdm-vy9B`Cp_}X`f_^a^=hm+rfKB|k= zRh&d)q%4%WL$5-)Fz@R1`z7M+CV{_=X7`yhh&R5F^L#>ntq^B$8(Dlz{rn>^h(yFU z;r#a=G7y2pPb2ewkmt0;JAK9*UF9TqAVsgKZqEvq_dh-}mQSALlq-O{ECgwqMnpc1 z&mL#x3;E7;f8zc^#OGCzmXuU~Rz>Qv{HdbM43_bd{Va|znwVO+h1`YdXvXhI(`eQg z;HF$9Ll}>JzsvsDVSn$ip5ol{!>r*P=QM%;?&j|{^7&hTs&=a&ee*bl(SOdcB2i8T z?jP(Z^kbgI8~DhnrTbIyIiFKo#H~I>RPclQ2tOIv=|k{)uWRZASkgjH>@W6oHoXW1 z@Kn7X^Ff^JdG_)!QBPjv@+CU?1^@RKfBFp_SfBfRg8whejW~&{J!gNt$x>O7hN_%) zBG#D@d1`Kh@}6ECSJ&Az7wr92c?SU`Jp^x6ux3( z_H`+1E`|zI>p;J<#4ZK;r;oQgq#P%V4lSS?>z}jnazQx(a zIe+SiZ=?HdIy|HL*h5jrB9+PbF1laP zVqNL2TFJ`jX$6I-!LDaPo=f4?MWUtWuxrJT{P}3DHdLOTK+?g^$X)No?);`ADkV&g zk~`j7gyFr+;`!K(v#iD8?NWkklxGd?kk@cl7ZX|8hfFpB z?KsF<=3`--@q6#PKk(kY*|`izQ5~2T?jkKkSY;t1fu!KpKIG8l&xYZ9^hZ+D@#YtK zkHp~j$JzBd#OPss*6U$$k~(7;%zs zmx8x46Egk?89L1qp0I{Xd?Q{oc?NI#dLz8=rMz<{I=4z-A8xYVD)6KvLR%$g=R6De zguCz@sol$Z>apYfunCv2fZxy-Ucbk5)-Jf^v#AlxhlG`3=US6{FT#!W>b;ks4?G-Z z9JjF|t1b_&-Uhwj3h8OW4&}!xH)aaLId*Izk^#RWxuD%(=F5;wuQGo-{>WAIPd9en ztDOnw-Ml%dbFmTdEt0kQhHmp_yf9B54C+4C;S-W@lsC^z*F-Edc4BU7Q`pOuIT7g2 zq{Z$`$c^dmFBLt6ovF@$ORr^las;_#L6ckTmPNTgo$-@ao728qVqQ=V`O@^Ey0R{H z8p%zp=tusYvN4);7_mbgcfQ#ni~EW=-^lDU-PbYlt_o#d`Y!V1S>T6w)t&2S{3!(wi>I^uvaCOXWg&P&w%UP2|Oc@ zze84)H__GU#1@ktzPldiW*GRW!MEYRA4g_>Dc#TW+*edh{v=y+-U*_PJ|1&1I;jZN z$DGs)bYpSCywOF-7AKe5?QZI-qT)Dpp;I-{HK&sad-VXneVY2^XgyUH3nb%|mbiEI zJX4>EXSL)aYa#cr024fiQ_XT+SJcnZG8XrCE%UG!aJN#4Oo4o6e2fC_d3XVGB5CPi zTsUBNG&^9^Ylp`>j=W)0y%R>WA0`&|;Edc1R=EsKa95th|2iWV!EgJ-5y$SYJVy}@%iYQ|CJQqshu7V(GMUAJLYMl1B+G0@@i*u9$M!WxS( zQ^K4xm%&Ivk;cPnAk*H8aX)gA``st=VF79q33QiPR0^i)ebF)8HM$B2-#hU{ex*~j zu?$A44yqw&nwNCt#KtBr=lAhM3Z}^YfW@H%I4}Bx z+BAa&Ix8LNXSt6G%zLyEL>-yA0n^YuiLhn=<3<&SC*YkO;Eg7l1hOJE*KN=R9>d=u ztSuFT?8{3qk9I^R6ZLBNAFJyLD!GK^5({1sz1oAyCbSIaS^&-SAMe(jUh+uZd75ai z2a(m>E*9z(avQTFCX-V)vZ1d&m~dLy%xD z+9f?g%(eHDaqVD=!u}V4L+mMMTNeEg51ST(#xFvKYq#6kloIK|FVf2TMA`GLT-5qy z(=$ai)0k;%!?nxb#+Gk%Y$C5FdcE{|l{Yz4osl}O`>&Ijy4K-t7juak@+wp?W~ILB z2!2{sq$C|yYX^CNV9>;FNJ#|R$V12WV38K`Yc+m7faP0@y;_7GZ-*W2ZQ(uqzNWW3 zFPskO(UnYX`yZ7(`;edJrnSSvZr)Od5g(^qv*{zFQ=f3_Q=8_ff;8e z2R8yIc%=ux6PA&#oQbg52w65LzLSqPsx2=$ivqi1oKv^lH6bg-M0r4t5A2T~&&^DV z?(Pmc|C0Eq3mm(Z)en(PePs@D{h)BOP!89ph>&K>C+My)sxY#-eSCX;laP-J#_Q+m zg-xrD?e=`2VaQ1Xe5co-I8*c(Cjy?uq%i%ZrmJIx)sOCyr+6mX31KQlSre@vlbcd_ zS6$Q@?(RZ25p{>*P?Vw$fDm}+h_ zS`+8$e?q$Yx*=<0Wm%IGuJOafCv>)pZ1yO}hO;_8IipQtG}(yFa&`3bZ}a>&qYjWU z5OSf4`LnSZ0NN8K+dJ^@nb{tHh#IJJ*qz9iWmMHvf7Q~OMLmBxD(Awi#{QjlCA)*^ z#`*uI0o8Uc6AIkIWF!92d0bo6%5b1#t{-s-MW*W_t;9e!^a z-AomQwfcbABbTZ)_-_-az!-=J3VOhPj*&mC?`{kGJwDdC-^HS;MaOslHnXXHTqD|0 z6Md4|@cqOPEJ0RPLm9iZ?wUN4p?m&%YLh7<+InD9qLbyeFsVS6brPZ<<_(mdU zqn<u8>>#`t3%T^rj0Z3&#E~l$ z958yWY-`m7of+oF(FX&}7^509jdaKn#*%rh@A9M7m@GpK_otIlUe^_(l|78CPCE0~ z&+U;Z$Wb(uo2=jHuNnCC^m4$_{m|5LPLuDQp@QW>=NOi`6!Kge|8p2|#yvc_dUOy) zn6bX$Dw(=(O`wMPsc(n)JNhE}d8&#*j&z$YOWkueYO2=5(RF}`VKvq7W&FkMeK4JG zb%&9`iBeVYwMxmmblV-l^9>PEHEFy=XK#ZTRUk6w++hhluP1u@bCY+5h;zjW|71%ui)Hm|8Hn1k20 z->qow;j=qf#+6h9{iJT8FX(t;vxvHZFs#Wv;w3M3y+K{UFmSfsc;?!?Gh?Yx_yCS} z8lSQl{@9H_H!U@9Fq=F7mYhdM{6&W>SaH0(VD!XVG}JFVh08<W{ z$Y~RCMFDbqWAUtab2G2e?=cNbvottfRrv05febt|H}Md9nUP@fUGenJo4>$xj+@fF z*#zvw1$?-?tSdIL#xOkk`^0w>$QS(aFTq<6qto|Ltyfk)#C|MhQg#K9ygICIj42E= zMFX>rn5ihYbs;|eS57@0eL44$gw{+FJ!KwppE|O0k$BQg-MJWfcvWZ+w(9?F9aFiTEIdx}*E7{WQKiHE`fXnTWmHptX*H zv{YlKe{iyGiFdZKN{5s1`V?yOy}#JSb)4L6R+)rzs)9HClB&7-tk)&FJV69Hg=d`v zn<`39eI9R61++FlmD7Vkght}o7DJl<;(out_KY&8h~gFzu_pp|t&b(D$4RE3nl%M+ zMm4_Koq-F<#e{V(p?eZD;_vG-p0K3SVP?s9XCT)`?lQ-x=I5szdy`P05c z_>WCPBxER3*NyLgfGk$wWCQH!EHIEFe6QErI+YCVU6Aye=#B_><2^`8bMb~<$Ut;8 zk@&%zK@p2e5mm#~fd5*T6MGNBG#CFRn(uz>w&I(Q;6>NPjxNTYcx(w?H+~#q*GEJe zIk?9^SpO5A6-nfI4V_et6K%*hw?lqA6RmlZJj?#^FUoyb91TDN zL0p-ao2x+L;Av;C(}O|%;%>#ovkt&a(157-Ir{RtE7_ekyytQB+G3uR6@PFm`cC}m z1pZr)-p`*w);iw~S$TF``);Qo>`p&NkJOk%e(u$oJ(KQ4SPnb1JH z(G2JKsj}>d$M*Vwl_$rpt!Gzfu%r38GgWxrC(f@un*1I+eVw)BM4P4L2E63CWw@zc zPi-XclLq{<7;DMEExO8jQt|3T)wsq8U%ryHzUz@J%+8=epUBq_1|7oPftTYnmDULHMK z2lQ|ND_q6-^hf{I;{8tZ-o>~L0=XJTG}ndQq`Q`Vi_3lQL$= zEMir%5jCk*e~Py2!2dPj@0Wvdu0~_E!Gp-icU-Be&Q-|*H`XTM($I0k(P-Y{2slZ6i=>`yU(mPj_C}u$wQM0$*_2uH^clb zr<9S*>u7c(3Aa5zmZb;iR#P;t$5K3r_p3<;QPGL~AGR(h$Xt5{tQbZaBB%ANIQwZwXf(`}c-(ufW>YvV)zdEAGJ)ZQg4q1WXp?>`Lh;|k|{n;j{R?@|G6^@h6egTy%G3GlP- zv&IWpm093nhj-o_+~%N0Paafe)b`H zWQu#6I+Fj;TJ_nNDD%mD=O>f*U=LJBvdXIAs99NfRj{QU_0lFoRFF8)d;-Zd_$cNku5 z1#Z~gKQj5%^usGGkM|IQC+Y{;f5v&n!WLx1GZ@aPqz41-z>`0tDv^DS7&y=ZE` z`4@e4>CbH#2*#U|J@|~4uYklPK|cGl|0j87ac)^jQ50$XiVm8GFZI;a!9(bWmZ^iK z8_jKFA|#)z3?8)?Z#y~m_ZU03l;_+a%J?7WH|>wbtqx*$k>3v#5yZHYnB>=y6Y_MT zhe-EfH|F!ZpWx2>cuq;4aMH}+#BPy6@ND;HGajGpD*X+Wh_*YKeaP%({>_O7o`H3J zj2(H3R_R71c?7quCSFEUynYKyQ5*Ezo44K+e0l&1Mi^#-yB7 zR%|Puk=7FHy>9PTh`MJQ`wm)yx54)MGnz?apPt2C_+TeCd(B@WN|%=T|HK?GxOZPz z*-UO$COQdQQ#p5#{c?TR2aJ|R4ogCdWtWY*|_Uhl>?x54WJ?s2A4yt8Il zDd@HDBEp=e_MxcQvXI$>U%|9)Dl4*@L1>+WpjR8L2FT+)p{cn!$b`-Z&LmcxQRhYW zlJmrydb??kes<6vP33Q4Tg;CED{IK4&O||HMOGa|^Z}4MK?;vVJzsUOjP&bC!DQAF04nWSB*P)R- zd%1~rbDQ1ILb(stNlb%2{+a|axVOh75iAh#nc9uxN?gfUCk;q~otjqc70-ZqiW3FT> zmuW`zP+$5Q)2UJB9abqTH6?k8Dwj~*F-$a+nVBrq75?*W?8;@c4_nulTUQbkJ~!Cm z0}v|-N*AqW(6I}~jfdF7if5n2Gabjhwusg6Siq0d!^rTG%CiAx1^pK3VWNGBWHplu zLD{N^fn*ZGnHbv=Un#4e%WSuRR4_j*RHwiu3NVM1Y^JJV7J&G@b{_fnFmW-7zX!2v zF>{dW@X~G^xdz^Xtkzr^fvhcJeQU^*!kLG)FCca+2Yj=g^WC&@hS<&A@3s&XeD`4& zj}OwEiGL=-W3bXx0uMh=_4OyLPZ+!lDV!O4i@0ciGBMOoXO2!yY|@MpIZ`YbhFzHjszkhH|I7G0nx%yDXOVN-IvX{8lqF15_rXTFI`PFr^JKQ!cN zIQ(O~Tfot)5aI698}VN}k7S|T8E7H?@fD*=qO|&=OSyYtSH{nhvB*BvBZk=OEH}@7 z?>6ydJCO#obSB&h3GwpsQ(tcPybEMp3Ww1-ip22P0%P<|xLp$~s3F%%gLTAiv zlae`cIpr>W9R3y+C?qR`X2b0!^N60&UCm72c(;m)(3`n|ceqD0kfi=$}N}rKCdjv<`8GVu^p@*{r}K#sc4|OuRx= zNG7ElGiRR5iPVOEvPNSoCb|t&GJFKv_mw>EagzeuU4UHMOy+P$=w-S%Ph6&4`feMD zUO%7vi|NmJ$~*Z~D7zPdj+#NfV}pdLh#FxL)F==UozxUFGk!++i_?D_*LTG!C{Flv zv>?}V->D~yB_$ybKl>W?R60g}r z)VYAUX(99v9i+!=jpJce2cnC(brkf)mZfo6iN42&3;O81#}FUl6%6vKn4~ zCG#JQ5U;47Kc$3Q)|p1kngd%mieARiGP$ZR(x_da&UE&J$L(VF-QUP&4>Gx3RU+Z& z1Zqm~Eh@veVByEf{U#N)p?~YHXpL#6ffyL#X)5_4=tXVYV_u?$qrOhrpoS8yJ{01{JFuqK1~ zcV#`T=<^*6x;_gH=|>}OBHH={R`tQO(c_raKF+OR&O3K?ZfiH0#boI6N%EpqmMrh!vwv0Xaj|(y~hk9COU-&>?5u@0N&l!9j~NZE1v5m?g^rce~g8{J`diC zvefr3XTQBED>;b5m3qOAN=xqdESP8`HA=6g;{6~UK@;7%QCFM{&SkI^Z%!O_nB1ib zME^5k8qZ4>JE1xYpT(hdmy5$wQvK}R^!cB z5cLq7d2?86n_T#8Vyq4HnSL-+K?Ay*rLcxM#w92DpWYZ~MyJ)(sXhE0rAMqnrra)Iq|5fx)((Oa%d&6 z5Z=v7f{p(VwEQYDRyY$lS214$RFAXY?z`t3==%$+=CKn7h&}t@BlN}Z_wbk#W(a#< zo?G!jWrZIi3)S=`WI8zHrs^L~Y8WhX(gj(b32))POhM~{*UbkL7t1<_YsW2D^YWps!qgej5+?!`aluSB@XJH$^tH2z;=ccY)MP$E@8<$8d zgbm^V_UAn{iwWrEapCg251w#=xL_U?^@ohddL9#doPtb4D`MNz=tIC$V%t&F1bLC+ zMReYI&?|2SPB}cJ+*C6TAwr8yJnL1`R06Gh3Koz7RCzI4+~ZFjg)jL6e=C%G`Vx7p z&E6kne@Ac*_lP2k^7)+jap#Fo{{=~`hKJjLSg#MbTmf$D4m5OH?npF<-*+MeujXqJ zc+7CHYR>|mM6Od3_og8lo=3i`0^V9PV&N|QJQK56(^=4|a_HS8AnL=3pFBS5WSp|* zoQtxiYn=5{o=gua)nxtfM7++osUQ~?f4h}8n1TIHgZ8dYWc`Wf9_1#z0pToz@86tk z{%CwLuX^_!R?Fj`{6t(3pPJeo{C6rSk409prRjjAz9f!6$+wjzVhDh(R|ida#Em`$ zc0A$_3Z9zX^17l+Un^Vjfl;F>5e^~} z!ak3~Qr-W9MKmEYSpc>*my@pwuE9(rcCP_(Kn=1qo7t5Sta%AI!6dxijC{x6?DHzJ zK`TK`uJOIqs6zXUq@4YO_Y6U%yeSWju)LqKjCa9EJT8JzyzK?7#KU#w6OX3A@<;vg zZevYlY4QFc>f}QnuM6J zH7pOuiIj%1N7InDVW9u3iRM0$@jYOYgM|&kqu zJVw#OoD%#c^sDdVy~?q#r&vF@H<4T~eAct%>*{0u?{P*?kvkj7?~A3e$!&GyzC;tp zz2qnF61D6g!a2&SZgY;gz@4U&dz_D?d$@cl-qB-uC=3qdRq?MPnhAkT5>`&0+l}Y1 zC3|v^x2eLUhx42i84i4)w|Fd3+^_kdck975z3BkQ(Mn9LVzr)@Tn6V*1bimtkmB$^ zIIvd-Ivs3*nZP{tKTbLxvG)wn!7%EJ+EHI5k@>mo>pIrCiEmuPO`Xh}JmTE`;ylY? z+dbC9y-1IRzDkPC%!$THAzI1fRty*}TBx0@b-S4ph(uQ6GxMt%(Zgt+-`ay$m6Kkt zI6SwV@4U>ZUW4KEahEv5bbor1Pw1ZJv45J?L|+aL(Id?~5o({)GxS<3F(P>ntv{boTmWYcJS4Y8>%(wxIYVb&0bl{S6VzY zZ(%v9LB#pnU4d-h@WHWfMS;_8QmbW6t19(68_-@8(XCUlWlR3V2_93xBz4k?rMxsx!Px=@RF{QpCy@eggNlZizf5_YH9=QNyJq+n}*8ypUrG_fa<57s-QL8zs+9q`-M(!I)jU)^&NDR z>V-0s6NPon<2<4+FcF-qKTIi0$ammrB*p6GgWl&D*S6TM%|Xrm8KUiuCJzAL9I>-FuPkJ6D&tfkSlc+b6cjd z_A}3E1&ByTydppT#|ymJ|8y>KM|ToU^d5P~w9w3yFkyO$y^x#mue$+cq>yf?CRuan zCA}(o>A$HFs;@4}|J)G#e@UsjXrcyu+dDfXWLS(hg@H z*-2M={;gMqUDHh=Gf_8HikW3koq_c3%{FIE2aw2q>N~lU#8`>HS+gH2z5~YDgfM9w zqwZb|F8ts1KYi zvq+{FGiQ1O)59KO;a+1wa?{V(g-WMX==aVlLNB5ldFih%jtWFLMXX~YyUD7r2i3Fh zh)urPvWu={HiE5|7q8I~k)n%CC~xo%HJoG4dEJJ*%?oliL-CuBai5y91AWK=#oa_+3lUU zWM!j@^fIzl4CHM;^ClnCO?p8Zx+2f=v_({PgoD^!Lz6Zz^T1jz`gdkZ#5Q^pZ4Ha&vf(ZqI#m}?aORRib19rRoPSA zvF5B!gojOKh1rcnERc2NzgYYDZX10SU;Dd`bc&cI#Cr2(dehfh%Pivr*xFAfE|Z(C zyA{;|swKZUQv&d-Mu++*>exAG%t2ULZ|FYw8|leL z9gq=DKQo8-EFzBJ+m;m5!OuEKSlVO&rkl*fG-YA;OB9mWZoy=x1OhA&ME-B90M!W( zWhZRYfAWA^j2)RTPwK~bB_m)M$tw5hf8Bg)7P_Q1dTN~vW9rlySqm@sv0E73x)kQD zFy9RNXETP2Fc=%|%sC*N9H1w&7ne@zsRDa9u~dv5Kn6)+)8%{fxw3 zz_$pIh3U9GiXSxuBylqSz&Wy)Wt|@ylpEN4y2q&1){n?QmPq6;gGO!WSEVJsnkjZ;{_R z$Z<ya}*hP;kwQunBj`*q1?^F_z%gsoh9*? zQs9Nn0lQ5uN~k;5zaX6(^;od^*Ww15h&c~Jn`#ikt>CjZQ!4ePf z9J&MHZOp9t5`XP>qjXvGEKr<`N^|ltXVE&Nnd{yb{)Bs`nfuni9&e^obVvM^Q%Kxz zk(FxO-exV?hiukLCO+2#KNb@HYwl?@9^qE@yP!!96I)AGP!jvl5TxS)9=|6m)45p( zog!$gdS>hXOQX{hse8b@R!O8gtJ-LgtWkP?H;+=2db)3Y9528waztfWm^UbLo zZ(zdY7b5;t>XFRu<@dl=cdId`KJ#Z6%57j%r{z_08#m=Wr)}T>dLSECriR*N#(*O% z30Z4rF=vB()Vp6c#!c+_P~eH!rG-T87tL2Xxp$c2<_weGuIuG8(rSU#^J?NI z8|brvWP7KOIju~saFq2(E))5K_TrH@g}-j1x*l}MIU@eDE?7rQHW`=h?#v)0AIWOn z4I)N!X7av%Sby({aq_1B9d}}Y{%Ar2!|W31)C8&xy~ek#n&|uN@C+VDYaN%Hw2=dJ zBWt9tqo#&LI~}Ou46`Dmf7vPBoA%>C9{8fhngg@>a#OE`i8Lsk}P=&BDYeC=MGE*_WsHVQSwSB)#B6=)3aF@5!nXuVZ zvuf%ps-)_s>Z6BxI9m+d!9hh~S_uu1vRQCj9GH((+zJt+b zFA>3KVuW38t?0$}Tk3gV%8s&;l~OS;F>u@;qHWcYijW6xGCjf61f9R^RD=8ch8}Ga zI-l$?dDab66WyG$yKg;KwxAB<9AGQp1m_X8Zoq@uV~RPcoUBZbJx_o508vO)@QwPB z4z{qm)--((EnYsb7DVErb;5MTZ$3mO!6Fxy+H7)W;bYr6pNuriO-+~t3pz99KSBSR zM&gw$X`OPLI4P0I=lVv@2vQgP3-zQC0Z>Lk>!7;3H2c!59k>*O0=1(&!H=l+Vt`I)BC5AZ>Y>W z4+8=3g!dC5`oDwUQyVQ9O8j4p`km`U-gn8Jq(Xo00Iw^p2Vl=G!|IY#4L5Dg1T)Dj zu)muAa-UV%E+9**lKKXoR|@q^3{hLvQPAUh z+<(YFLll*>#4x=99kSCIgZ*r32Ae|ahKYq&^gQrgNM}75LV9qbiahHxR%kAMVJrKk zxo-E39-tdL?aXug8D}}tyj2IW#baR;UoDPAQx7Eq&V6QHv~>O@D(Sqp{UW|tF zy4zPKQSS_~yqu|xJ;W}iCo7-7n%mdZ)+@zx>mba>2dhgH#K!-Yq~ka znhKG>oK4J=jI94G^M#DaB3P5Enk2ft$nJy)t$*TmeitWHP9>RI+tQbu9r`6lV4bHi z1^1BJ?=iK(w)ar{bei)gC#X;UO5IOh@`TC26?-ASjkVOPePgNRDWS-1iZoWFGY+n& zI%QP06}^Wc50 zAp5kM%BPX$xBJj>fN&YLu8SATHg@VIV zo9p<{iVxvPZ3go391JlTK1g}dPnU!FXA}r{U-Z#Ex0f#~jP7;qQDTvOH2RVm6Sd6A z?|u-c)hE|>#=BP>zca|5NtSxE=tPEWjD5_MmRm%BCzjso@1h4;bwXwb#@q9vimM4^ zjn+wTns4}@u9?GR2Rc&Qy8%6smMVgO#G{~Uvbo5l#=z_PFnXw7?R+w=bU2)t3CPae zmyvX2e1$i6Clc}-Ib*`0DMxm3o_okiP6n&AxJrlG2=xPBW)W}i&5`N|u0D^d+jih# zUd7>K(?a$KMLddLPD4cVj(4Ah4Bw*aa~PSdaCXO!uMr#lGsE5)-O{Nds5qdzXu5vs ze`<=*g|bc@&|7sHUoLakU1F^;JJGH1_rPEtO;*I}CAVYM@Kr1qY&ABmi$a2LH17w20Xr#2FTxKn_%EWN7F{_Ch`eQl1 z5KA*P)rk+k{9q9Hgw%+1^?i-Hgv_2`9%UL+5e?N_=N112l8Cy#)bPpoQhk{{~3k0F3^G&d1vgaE|B&swTX1GZa}rj;wuc&bR$=KE<6QT0BYgki5 zYC0zCexL_s!~vqRn7W;5Z->hqBCXRB3?Y#oM)oCH;IsZ{pLROv0(Oweh3t40c=ORd zr-?mA{GX$<0FNSB;`nsWZUO;XLu`tIAksMw%l6%83Se9Cu|xI7Xqw62tH|lZo0?(zf#i(n&t4 zy+!AhOX(1{T`n?iSugZMIh*M6xUNe_?w-sranQ6bAKabvnT?3#EVWembx-?|x=DmI z7Dh`P)}W;or&_3{@t5rBWT-F0kEx888H442LB@E32(p)nxmde0bhzIl2Xl9}g7AFN z$BZPhul_)FakSsB64>eWTyE6?k+Tu)D{;M8RROYbB~Dz*6Gw}W44~^tBl&kX5E=>>17n%e7nWBVh+GPBUBU!=XzbNeePh35Xug>*cx+m`)UU;syMj zKarAfJjEK^?l0hn#XvEh;N3R{?R@~_=aj#LJyds+iGBw87CAZJKeS(3Q>X$v4DOeo zXrY;P-pp%GBZkeE;Dzq0&e@Hq=q@U=#$7hX%Tm6xW{1EYv8AvshwO^PS`i>2KhRH0 zjIL&1^N?|d*+!`#YmlStoCEuIjhUXe$91IltM!iBTs#Gn~ET zL1x%Z{3Gc+cUZr3s7f|ITTQK0VyN}Ye_GXWDq1y(DT?T%#yBQFoffHdPgTdx{cMO!AB}g z{re3ju201;YNy6obb!GkwehdDPwUpPZ>>t!h?p$qNNX^>rm>)dYYfGNoU$@=Y}=S= zAV)Xj*Qr8sOMC|y9#ciId@UNl0kg!K1WO-+DYleaMSydQ+-6HtPv>d zP1svA@n%oiFU_*%d8Xg}XEe70jWwC4LA&XBPVdQscje=RuH|EWz(_c!eFrIwM3sCa%nfPk#P7tuo3;6~Q|jfGbi|A=P1eHC&AUqwDo3U0z2n1}PsWuT7+^933hujugE zn?Ag4jDkYaWi5;S%;+YiUE63N0<1jZfUanLqWfe5I>`>;#(!XE;#F!U6U{TmabIr8 zot#!SaZ41^`J55Vtw=&Qg7kD#xI@3^Jm|z)@c5>(reyX5@|wXaF^JeXT}ZCA{jjUo z7{$plE}%H4cosmNlJx7d#DmBzv(_m!bg7ssdWfY~J{c+oS&PInkm^&i1>WmAIKFW6 z0&JNUzH)myU)Hnn*B!5?j&nWWhT3gk@Is_7;_!%3@WW2w)BOoA=o$2UR` zRS<4>@LAYWFL-Zfs)M@G2lo1pSw@(3IqsvQtaknwoydr$zkDfqRTpzEm~%jJ$Le-O zT}ALlvcgHsM&<4e$EGqm8}$w);b0}9_u~|uz<1542&cFajE2?7Vn1%lKI0p>YYEw% z{PY|=ygs@$^CF^P4iu%U!DX=nKQxOo6<_ZKsOn&Fu=61NnW@9DerMjdkXPOWe>EBz ztqhBUS+%MZS)M&GzB`exVKqc-XYoXmgLod%V~lgu;15$rj4A4pWs~h_VpX6z;e~Nc zU5Zbw?y2^=i@c$S=vd!75S;TmKZxc6kdhhtBrLO1;8aUQM^Q#+<_;ZDr$eaxF zdW0-T9U~ve-!Q6B#*?dLnhhA%BiQ;)bs?WL3mTK`3D#*{!#H8KaZZ{))lg@kd4*|L z3FSQUN#%(C9T1i4&K!KHGer55cp8SRabfdQ!zGy@vNa2z#UjJ z;%#Kf1tFPK}vSQLr7(;z|!9^Yw9ib2=&@XEyhww`V~nMH)1kVRk;I>zOzR9;E-(}u*2I%l1gQ6(PwKGIM&`Hc zTM=ZRwyTPAnNv?qr04oNCU-?p?@?9x>?h6ySOV-~`TOs3qK?XcVIS@fQrD{`8fsOxH}^RnOftof=eRy)+#>woU#&@WXd zozOoguojqCyfNHzCu6)SA#GE;HF@yUInMiT|)$3(SpX38%LwquQ-E{v1bUunc`` za$^;Wa4WNb&^syv@-AV+5PQJVNz7a_A3bw3;GMmY=ZP?d%Epw!FwmJ1~vCmu}dcIFp{p>foDf?55eDXyR41%0hW;C0EfmJ7i9O~%?A7Q+?pB&CpqQZZO zMe2h(H^37ZOBC6HEL|<}l{?YUa~JgEU-Cb0zrrTmQsz*A0#D&ybt7UuM^2M$8Mm`E zyw{7|^%hj4=B5s_fm6n+i=K-YQR)D^TxqN^H+ZUx(vHW|2HCvkd5O;zg?!v*&esv~ z9%e%%xN0cc@*Eh;UNX*|$UtR=#Z?RBB{{d^HQcf-_5~xvv(en3-kMATbgJs%OqJU~ zPI(RN&;X*=lkrme;2xiMHrZSK>)?u4ChInWNWHd52u89QEBsi6fttJ#JK&JHJm;sx zG1Th97r$ZuA*L|l_LKT$ zM{a>QH6xblMJ?NXeT)4(LY8wE-pNBE>P5(77_nOn|8MgqtvJm_#3NyN@hQOA>7>u? zoJ-W13|!|k@$U+LYBda@dqm-HSVtvRype3~BB~o#z!lC7lQ0Z_XgJSI3l^}RZ>!CY zxO}`+WU52>8<*42i)YQD@^u=F+c=(Ik}P5lI0kX({s?ZR+YvA~`}KyOYDo6s11Q{N zo@l{?{EMH<37>F26{@wtEZgvFBX3b3`E?m_#X<5~khF=ndAln*U$^8@XKzcXA#6$TK){Bf)6u;pwMmPqVT15b(b&Sm~iy;;Q^! zn(X*w_GUetvHa|0EBFiV`SVt=-_-1uI{~~cS@5)EFU}zaLF~p?IB2_ouV9)j@86A6Y61rP4pjFYryI{b$V>KN7W*<2 zyz(T`$$i*pxyh>KL+bv3tMY*j zEJH7LWDuCbA4q!&_SBu{j=tqHRj5t9$f*oP{uglO_3=YaA!qf;Adf*WD1Nd$+N%Mz z0H;`CQv4M>X1d>ZgC)3D-ymDtj%u)q=v>A7ZDThbR^;cs{CG1tx#LZVYDbgP*^YeN zgE{byf4>X9+K||z8|>RK?o)EUFEL!90GJ%rh>f0cukLVuO~FDE!SJsF3p*=4Pd>6E zc|nRQA_0})@g*b1+t0b*N3Nq;A(i5I4Hv*$3V`gaM;iAc!!P+(2c#w~_0vzVf{$SP zcH;(|#s^%?XLI4*x%aI-s7`L4{2F9yA;^_PDk4EER&fs_dHPg%j7*V6f@<-WUFa0> zFE-(CZbws4>7v-K4Cu<6+yYQqq$2}f&~)&fyZAH@SoM6KksmF)l#>m`h7^WRw-N5o zaQ^ivC!LwM?0{TSla9`A!MiXk37b<0g#I%t+sNH=;6=2bl=qoE2 zfjoHFoy4#~6TnZM!^+oyCo@SHEl?1tI*G(*qC@sMI8*J&)72tV;?DZ&fK(U3E;QyT zMUeT5?8JA@IglP1>(LAW{M^5+atG`B3!iu!IyDM>DmmR+BK08{WPZn%oVyJ49Oa#Tg{a<$VSsCxyA!>G}3gc%u1{%)+d<5p|Vi zVd?jz7tR_z5gyZXq@XM3kO*mvMm{DJ8$949{*PTP&)X*9NvWtd$cOIT&q}8v;g!ik z4nnhR=cZ*x$212O+0BaDQ>puf48E(=vxC*9Kuf3Q;B_mJm@b@&TN{0c z+wg$fLPvLWR$e4;6%tgG?_L7uDgYMBQ*PLPzVkbG><}k>6kXYgbM1kox8RP}<&1){ zD6_Ga$JiftKGrmz*^jsB%Fp(~YJ`$;8OPoDfIf^zZe5;xUjDu`>m5T?`gyd;7jS}; ztojJ&6^ASr=ecjO5*@K3YkAu$XeKv5o|kv(fETqI{)@|fe}cTs_IlxQi~{KIqx6HUHnCi8VplzJ^HN_UiC{(dpTW!hEjzxhWB}n zt;&Ywa#50?1j&%Ds>;MaL@B29XEq6XyjP8i^w8Pt8!#R)T@8+VPv+!C#S%c6)}$J|7kDP`%F_dDq*L)3Lo-1kGI^(`Oo70vU8&6IDsFWvfH8kI`%y` zdLRuaTZWD}u8vNFJdNa@b|hcamK*aA+4z6ay_31^k)SAddE1w0g{&YRpRh}vsM+p} z&Y492fn?}Iv>_draunxTY1@`tP zdMus_<0z)SzNQ;f9&y^KCQ2AvVI4=K;hM{XWc9ksnxcsHk1D{l;{L`6ySRUuQOXyM zk5w9ba2`ED4+u29@7v(EIc}Yi zD#3)8Jpq@j-A*Diw>jK6OU`X52-qQS95V-UGR=2|+M{OaDzcZjK`mYpk&})NyU|ry zRJ1N{->@@__WGK=#Mi@mCrzUd^;68^jClX zcspHuX+2TBVTMtV`qy?lzsAX|y0r}P&$XD8DUwp(Ki1x()^kS>tDMH{WPidG>_{KL zbb1^)_6Vn({}ol`Wh7up{qh_|dnC|cH zX(O7jrtD<5Q=2`}AVpQO@9CU5zBy=%YGzN%>uY7%)TkEo9@Z<=JXOw2#3YX0oNITa zbt*_P)*flTXuC7aLtzFa^VIYAw)Vy)4Q#D9J7e^FY|=97)^DheOxo#S*P^yHxqbu- zxiquI6FO_v1z3qf?lGs&T*&>Ju z#F5k<&4if~CBmEt7-g&B>de*w`aXD7w7)qOn`L0{r(~j9YCE@Xq+_gY)I5g~4Mghw z5?`2^O&#S6Sp>cRRjpEEdCzGk6W--MyR%aTN!@CG^d5lg60CyFil)cAmMW>8c42#} zaauB4hUoc=by+tv^Wztq=unOBj6zG*f`NLQ8~91zG#-G>{2=$ZnHy}N%Pm${3YM_P zSY~Hpy$MAwSPqHdNADxsMGYI>9WrpMhk#iwcTRhh)CtPd7j}VBT&)r9;nlo0j)54) zfo{%_M~pdsCYIpGb*2+ucAW;j@!bAHA4eZtvPa`v)U~InD)`W;iS2K}v#*GbGO=oK z_*1!*a>I~<$Qz9)+g?;yGmyz8Xj*rxwtqEtq@<}Nk- z-lIa9&77B@vj+AsAN=i5^iibSKg-x0mnWue{7vw$B-S|no^x!M3Q5}4{Sx89- ze5t$Wsk--W(xwyq_v*a?PXDKz+-{ex!%ePe}5$&0qG+!iP zFB0lt*ezQ?zKZA{%#w}+HC=>Fo6Y$IqDhZoFP9NtF0*G*$sOy=G}mAY-ifiCOgPe+ ziLR}k;6-N#m)r>k@&epxJCnvv>OpcS_G~hqXf`6d>*()tbmQz|hZy4|em+Q;gI1l4 z%r7*X%bEcz^&qhLU7`kc0nc=LBvO(o_}7WC+u|L3SJ!nSRS+guI%}4hmJZoHW4^1M z%sIUW)9V&_`oD>hlA;-XpwnlKs`^mEv+yvl8I{y#)j=MkQ*2}Adi%-QmKH%q2G4Xk z)M{cEvj>O-x|W|<#xC^hK9hoX!tuzbTXMQjkc-3AMefl(VpfYI@{YC2Gscnr?)tPW ztxD(}W`Cyp+@mXTQM;tMLpB5{&4i_ibKV#i^>F5Yj-^(jx9&pa;UD-C|LM_qS#!z5 z+r&(-jD<2t4x$fieJ8Cs*{(@$BbX}Rmu4C@%{qYI-#g%Q+AH z&pk)+*UoxgnE%snsXuXOPL=(+Qb&C!RjVLk`E|zKfq# zg7~PX?t>(r(^JK___m^a&;)xolB{U9$p)Zfi_#tHGvz~c^g+FL!z5j6x0d1IV6&WKh{ zZ(SF?o|YS%U1Viq54G6zpN^1k&9!`{1oMvWm?Ly{Z+<$D{UoBTh~0EKpPxXEmpH3r zJ-yWVi=4z>sa0P5?&nB&D{`EB;8QGc9O`lYQa$Nc^Vm9tPgzU#WhUheRo1kPNVS97 z`b^|eR;uDA9;yfwe~A@LK&}o-K2~I;Hnf0P!>qPZ*j_4d$ptm1!^{@7R=vgJ`GReD zfS=Ktd+x;-p9W9t0!*bt%qV{ZYxXg0-Xd04mB*Jtw@dIco{q8JrDi1~vAV~uHFNd} zx2k5R)5u(-ydc}-?DZg#$6)|Vcx~gfQoY1u{MU0t7E_2VkDxdDaOYl7hb-VnH;=2M z>&T)05WQ68x811$bZTIa-5CSb;rWdOD_BDO8b*Az4vh?NNcJ-Rl8-^z8o@8PN&Rmg zF%Q=DO(UJNP1i?OGK-ha1F;HpV?Vz92z==B#xJJYU4*%iiMM)Sd?QCN$L?jn^bgU~ z9gjN7gzj8&u1$}6e7cOzWpdg~VMGsJwzaD3EV2IM4wOQVPe$(Q;LYyBW4=O!NIy)F zvu1PvsUf;i4OLc_6r1!TRhX_5)#>(|124ORjJ6@;ZxWF@AAlVwJ6LK2asl@q0hG4gx3U_wDs0i9I*Epzq;Q5>(Vj2VflL8MYE0g=$z-pc( zLWrp%u!7;R1SipVr4MNMO}*P(Y0!&H^^hq%zl_x8E*WAxu)i?zz6O;fON}0OSGmhO z*{DkY=T>;F{}N%m!eea&7PQ}gMW1r2>$Ea~eC^~C<&AX#Da2sw0k;YSk?vmYO${UY zfOsm4ixKz-vBaOJ^d$Jw<5YFdV7zsO*jza)L~6X(Ze*JN!%}r~cG(@!(0^0akb+;O zL?tJWY-DsdFN;m~3jEDd;v;B(Z}dF2+Y z{Ok*2+~#^VJv!#-tcIc8KD`s^i65sOvV>iTbzdVrN5IB%fq=xJvCHCjFCp`&h@uBD z^K%k2;;K=@5MuN;Ovw+C&oUs0a*;+%qw^xE|T(SE5N@`{#nG4C; z*YhMo>ja?94iTq!#^=tA4e6~%T9ORJO5ZMfx|2`_i>?8GsRa6g{6!~}uJ%Zsh|0|z z@W#f#VY_BG6z>8DtGD3lIly%G5+A1q%_O2V|26-#i+SsNhpXy_VrFEVAfQmuu{PSP zx0y(EUH5o*LIyPnLPbRi})%fiC2{ zGw(Rd?YaIcu%LsuO~=88ymWjlhg8Ip-7Bw>fTu-JJ9k+gw3?&+7h086d-J++NS+tt z>64yMo>Zrub4+4BW#lm*GGRI&_qC8S8+Oeh^SPZJuH|I3S|IUq97xlS-%)h5=*B*E zq1N`geL_^A6GvfhBbfLntSo`gy#K}@$DU1dw)&>ieZNWEN+Om2Ko?T$aQ`oRoUuf_ z@c*G3fkn7=yVg zRFpM>#T+J}x|2LM5c`$H4>|)@8OeOk(*E<`&#UhGDgAU}#eO@laW6J5b_2bF;8=== z%mmrPXEuq&{>Jt=yMcb8LgZV#@3hD$@iMtXTW1up>b?0KFaCs8TkZ5VbgJ7EucPEh#QLZ{tTBe_GS&j+ZlFn|GJ>NRK2p5({!%nDx{Zosr+%U{bkt zFW&^Qh}vCu4qi@v&%jQm06WP;yuJn2Pgnjc43F^@2+U9rAh(m<60pz2#KI@(6!i+U zsRtNcS3HUB{HZaPrU!mT1o#YjS0bAMSmi0$(7tF^I?Gb0(Ad?gcnS%@@w)ICSm}5n z%!Q;bIT_X_lC!OZ_t77#FpW5HD(Kb``adlI=gW!=Ph%a?Ajiqbr7+=@Cx!B)hn!9? zJjiU|d68tua)4lNCsxjb@8?7A$HSSPh5h}AZkf#CQv<_^8Ja zL6pQ+97m5Q<2i|pedx)#tZ^Z+ULD?b1I*>7=+Jq1b4`)Q?)0yG3xDDXNPZu@{to=) z5o#SPga3x`=GpL7nf*u991RMqc%Liy?)_PR7M?p8KQx$l{R;j}T96Jm3;YK@wL3pF z4pj0b`%nR!lMy@DnhZv7IvCRTmQS`ICVNeNR9c=p3}j*fZ+?^=$Zn*h=Vxbrwi`S6 z8oS+tY#LE4e#{EkHtw9u2Uw?ud}}QEK!a>>PGa;2WLriOtzY49LfDxnoO3;}re(x6 zZgmt=KqtU_$ONoXGD`P(W_8}r%>XtBqjXuUmQIQVPR$;)V-ADc;kgcXwJztJ1FYf` zmbD*#Z%rz;b76t1f>jnn=4vAit?BsKHG><(_ak~c4l#NE`- z!1f|QSf{{PNY5U&0%0A+E)^p`)EIksLqFoP1z1};5SyJyRV()0!J~hTgg<2GCUBZX z&{Lgw*L>svmaxMC?4gf$9Y$8?jt=A}P7xjck9%AVf-F}y=NY(mM`UCT`&o!C!P&7V2Y9QN{J%TL zem&Cih<$wpO0p5Tstm_1Ig&n{)sJA`U{oSW%=zP-{)WAknn_)RlK%$ock0YZfL_?d??ujWY2_qUIg58p)MpUfes9D-q2^@q^C8xE4pZ^ zh42;^cs{~Z$mndeKk8@pOQ)ntjl8+qIRd6qW%wUu=vbIkq%qrr0^T=lxt6(8Bb<3w zEBvL2NN6Pb>=X5BeZ_eFQViFQzXiOXl&}V)x%>6F-&sKI({pBj`U0Hg>LAE%DfNRY zZM=HeGf6~OxTKNvp&Fsj+mro0MS1%?lb}MW>3z(!t^#l|`^wFDg2kl69w(ROJh{wk zp0n~#nMu@T;!8v7YtA}ZZG(OQX^31_kteKfuhI!&^<@H!57W8nq1n+GNS*y4{Q<^F zyjFHy^$DbvPF^F92{{Xultm_lHrF>!}a!Sc}`l{Joo^>kew)7UwBsLdE^BuZ0Ey4d&9ZEF`9@PR8WtGeb$E>`A=jU z&%u>AO7DTIDy!^CpPCk6J~42gkAP;}b1sn0DyCDzGrI#rZLR!356Eshk#6gp7SG9e zR5Uj5j3V%V);iBT9%jv7Fc#{pYOe8tUTbH_>p4hI6VaIpK{po`goSR1M}GmSyX=G$ zbvC8i_ai*XF4lMZx%0!Hldh$8xcSrZkaOGddZr0K9M)MCm0u^M2SH){voNEFNP!iU zpzm*y^a#0C%_ok!q1UT3bo>%3m6)i9(x zf3_ahc@vq{J20F-(}njwXOmH1!ZL;vFPG;I0HjN%7b6Z`= zl9a?+)<^HUeYWt!!J&_U5Ns6Row>{}J0LQ!ZvoaueI9=0ZnFIWR#jL`eeBU7rVaF3 z>wom{+kmt_)v56qyVDtAwqe>mjP{b=T;?z;l?GCEHjg`Y!`W=-=N|3Tm1Tg)N%cDR znj2LIbTf=!X{nP~W=w>`|H>a>kI^&0SH{yncfW4z_mcT6XL+2Nb}}NTBb@6ltWLCg zr7nv!#zZ=~jsWNH?W}?I76HyPkSxg7(m7JMC2>TgttS(N7o}|9wJD)*QwqlL{fX049+x7(ujKY=-GjpTA zOUvT2kM8dbW6soldaowuURS3MCW0LQX83_KbQBoY2&R31(~+VobrkRE5xoFA*_>?j zByPb7-Iywdi_UOy+rBHj$odZ|)7EgBvE(f;h)b~#smy5R^b-g5RJ^G{AZL4=ZnCAi zDQzc}s6dbD3i7L%Wfgad=*>boMPy5OT(;OouiFCDHvG?CtQW_3&@JudSgk4gHdsH*p&p1jmV@ZZ;T1VIaNJI*`nWoD$;lo0qA8rL^RQJ>3nG$ zEtqx~rFVOnq>cN1Tg zA)lc}M>9sPW)8t5)zhCYCd>?#rR|4iK4Q~dyu)o`?8fqc#%nO^xBkVx;c6$^qb$9L za#CN?*3ou#G)$~Nqh6-&SfN&!eMx)=i_D=CI!C~UB$<)dnn@+1O?*~aLNxHLqAcX-Ez{xg>)`?zsp#X|Pu`(Rx?|L&aHp zk}jpP%Q%5JP;^ zfj%sE*ell~&qTJq3Eq=U{8brQ4MIN3^LOotcP7Ks8v_;)j@P{zbm=p`raMjC1G1c! z*yuBQpeVN_kkhG!2200no`RH(;(K2qaVgM*;rPF|@$dE%5k;T@M}Y!N;hVCeMfQV5 zy0f~LgJGwK{jrnSCN;idF+2`p8!X%*Vv9PkD}%Uu*+JMm+=>pY&z-rw4*$ChF@%@3 zOu&Oai5GYi9zsoQ?0h`KwCr3pzP|}wYM$}4uYPw1*hgfsl~vWns%3$tSOR_3maIW4 zzIO_7#rEI!urB_#0?E$^s`d++OGbusId4nEfs{Q!D&2{X?zG&MJShcUZ7^}$HF7Lt z(G;;nBJdB`#cq7AI{SMQhRY?MWMduAa#C-2gJ0Z8xARH{_M$G5T^hEB;u$H~+2Z)V zGx6{%5MebY8hXf%l>|{gM&9EDen}K-aZkfTR{jbePYiLQJ3pu{8q>$OA7H10V4g)I z7dQEdTF9H`6hH7xcYb+RiNl#@#*>3s)p+u;f3Pz)*5glXWewO> z<;WFnVja<()L3j}5WEzZIppE*+wl3?L=R!;S?Bk8GJ%C1YDU!2i|ke@cKZu)>+D>AQjjl3HnhFW_1dr7t%O}H$SYH0Fe|8yX_jvkz_PQZh^i+PnDBsu_ zi}VKDR0tl|BR8f8hr5E+{F8k-$Q_wagtZu6!)jtPFDIFceGO!n-y!`?cqfgC-)q}NMKz>~wXD#F+H~Qx*k~bLa#hqC7 z7>REOHuHpx++yOS+MLKWc47|acpUwIn>f^BCtk64v-nd-@U7`cQ3Gy&W+XW!cWOR+ zoezKRHJ$^Fn~|Oo?AB*aPH_fLd6R@#ooD2vd%)fK%2S3TaV6NpMVwDg&f@`+Fpak= zh_s>k@RqyqsrSf`fB|PA5AKxoDE@vHCsPV{LN?g*$_+537EQM705txWOmqA_J$v zm8uhu9pgThCq8Wg&oLF}HJrcSfV^bq_H|?@`trX(q(3#-oXe6b#rZEpeu9wUI%v+U zM2HP|?qlwBDc;icxPPFl-3c)X>A-u9w_1Tz1dtI*#=mPi7G|UaM0PZzjm~uA^o-o4 z#h@p-$5iynA;Cpg7?>9pr`-M*Gg@)+s*Gmi4d3hfaXbq<#PvceCFa@+kHp z717)TZc`7CqQm%|-`L-gNU{eD@E50$4}9f3$VpFj*vG%R6NESNj_$PgOh!{|+#l>~ zDBl;&-=#rUr{P|-WFKDg`%>O{BI|e!5_6d+JVe(%V%LsxLZ8q*%ojm-wP9@rnqVdx zG!c^02^rYReI3vHyM1Qgu-@9p;VC5e8aua}w;jVtF9G*>$hr3B_bv-@!A7U^#yA+^4)vB6N)A=~t1E62upeuy+%Y zuoqw;_0X(gNI^5Oi#L3~JJIkZXQg=iSIA!~&X+l{?As$``2%v|^0ikX;cwW(&+K7B z>|t^+-+FNF-LAu_u%m6U={4|cTt8z5nuHmk=$DP)T!TO^VZb806_Au`cqdiKl3(IB z=Oe00%bVKlr@#ib=kxp6w=jOH7al@yb}XEAbVO=yU@gMfi%|A$H!@HMJG>luS;_h5 z;r#Y zS4`Y}qC@TU^cl}aRee>p9G+#QJ|RQsL6Jxf6FuY}B9570Lhr0zOx~&Co2>tlTZKC* zEvcL&D(FE*XF1mHD~hNe@`PAswUW`GGpEExzoX8u2ks<|g<_NOow)Kk8RVjxsuO(i z;>2T3@FD(Y52~=z97KE%U^x^eL*GnJFdBFYn}35+RNyV|I&sER`Vl-vM-3F=_-r3x zPh@v41Pr4xev`QXSyP+_Qx>pX{~_Duj0t26LXpyN9ga@L9+e*j5jdHAfssr&s zM}b5xCS&vqeDblrO7!?Ar;+3LcZ~qipk9J$>>a3kzoAZWyFKCrXT6X*vlMjKo2Vb_ zyXpq`{}63c(X`w?X}|C@pBBB64$eYynIE(?tAsBvb2}Z(!Q@O9sgh0(PZeh!6O1zJ z?sA{=L6MS@b#(;OZ2LN4#CV(ZOxY2ydJ;LPQgi^?Nq(*slX3!$_o{|wga_>AXX&o`+0DAL`uRfec3( z%jHBEvsrml_{(6iN4eR7BAD1U)W{-lz{y-nzu&Y(XB&y+yW+`gWmf1ZJb`eSSuU!z zm8#Jdyv1vM-CH+O0 zI448`?(*OA7oB7l!}XX!2f~+R4|ZT928x+R2GITD@)#TwTaHkF=@pWy2ymEqRy2S; z3Z;^bDOgxlDutQ3z8@P?#`z9bRTlp&3?2G__kWDtOoG2Z6Cb4&{>nal@n9^Rn`yYf zEi4IgI1~L=63bGG{hEl5i)Kxm(S}UWz@j|mXKSIo+Xa4k`Zs?E+@E{TQLx=@P-MbF8jS1F-SOGom&g`mG|*rSw^Cd3lOu^ z=iQJLyokx1&B5Qb7||dPO25WmUx(J5$M?46PbFE+2v&C*i%^2Soy9lW{O%%wuh2(Z zIDsN~;FHj^*U`+okecgw4>$4B*6}TKSnXM=E{b5o+!?Xau(s=?>%U`%<2c()_;;)E zS|ok~CypgE_?>S1TJhQTmSC5&!@NHX0tVv_ucZxkdTQ{4?I}Bpy~q>`Z;mZYFGb#ap>J=O3KcCAjV6GB}lw>{?Q6@Rr}Xp}Y7s z-;jq~yk%{ECBp7kA#3vwdolsPur|-DOsta)AEYC`#X|hR%hUkP<{q}gGwaQ{UFA(P z6NQe!vwEzj<88FXj;}>}Zer`&vTnD!E|B`QOPu9$cDDqd>`L6}=BdfP5(zcnofpGe zKg#|OW<3+QiDx*yT3AoT&6$RubCNwuh-c#J)v?&=J#gpm4S9`r^Uu!CvGiifc@a zSb*~`j4!{M8=sT)Cg3OT8JYRXAmr!i zx9A?9TFV`&$*^+*7jR^Y|Tm^W=+t9&Z$=r@&-O2H#ma!hDzq8j> zvAs8t;6}gkiOP83(a0^+hLF@RNK ziphK`OVaHtCu?&1js8Ny%cF0$@ly*}aZ)k?d58czaHqH7JAcJz z48T%-0c7W-(r`C$Dos4$(PuZZt?Sy%)`? z(AaPFMyEFXv;O8hnFx01R^iV5j3JvCfsO#(V_%ai zkdck5rYP)fhc7+dC`F~rdOd_5{f_;fEZhY;174@cf4oYkujpD-A)hk;l$)4wx>_@n zL~k@(u@8S(C6r+=@GbR!{gutG>F@48NpFvMtZE&yJL8RfB7m;n1LP0ALl1B|#=CQS zVb0Rqe=;)Mn>Z#NRy+u-wt~4sH9@ulWFqwKBGA|cc2xWWa-QqqK(55=+`?+TXn;5v z_!7(d4n1mfTU&GA%F=nkfbVe_dvjk2o)^HM8c@Y>!J#@`9Z*A!y`qomPR_Kw*skv) zr?Bv>^=Ll>&6k(FOe3(ZM`#|GUy%ZIW)zu{ujHx=qt*V_E9?a7l==xz?VL_&hQ-E^ zVb2=3-CNEW6`xDx6@qE#@8BuN>ST19YC~<+MQ+w2bZ@Bf1LU9+-$$N7P9=-;fI7Bj zaKaZ-YjDip)Ho&@`V-@EuZ~}XRlGsQy`Hm*IhkEVI^v(%#4H(zaq}|i>=PC*ni+vF zu~6@sQ8-S78R4Qi(O5~agx$m^wTa|m`=Ptss_xC?E2_ZzYU(9JjD{-so44s!A66p@ zXocta3@>3iT%d1s)wyTS)H75rU08*yL~!wTIl}zOY_BJ39=u5(w!5n*KRH6UaO5Q) z!CqfN9a27U;NI$vI;da%Sgqr!plJdUPv%L+q4lz{-aEwjMOarekcC~`9Gj?b4|yac zm^~?Pb#ofZbN)?ahu+w8ncnqTjxf4&mhsL!?GP<`=t>ohk9ZC_x(Zfl2zQ43aeToD zT?<4YoLjRVP1u^V+e2JmnV(-xq}5fpdDF`x6LR9#s^=x|8ch_w9dx%D@%v|NYan{f zjl|FBiP)p_Sj}D7;$g5*Z-W{%!*h9o?(43u}6lZmsXn7*1m7Ml zgAH?m6)eFcD~#XLgZ*g7D$8I++Y^Vo9ks8bd*ifgo15#lbgC+h51tu*_Y&S{Ha2Gm zt0_Tr{gNBR_2i^ppc!3mOsI#csz6=M{b+rH@F<2lf?gc%UJDSd}cIu?mJwA z_8??i*sIdqs8j5JOKj137(VUrEPC@dbKv{b#|Qk%uOs-Uv%n#$feP-&b~I-lcUfIE zZca_Go7Mc+Uaa?7FtJg@mIFAcdwg#taJn118*49!oVu0PYmkom_&E8n$#;>0E7&2| zBif3udLC`m8_&h%Lb!d&58(X{!T;FKjt1k|FDJr!^!vLTVZ$e}|09sD@%$+in>h>5 zITsf20N;BNdk-5NPck`s^f!Bai9cP&jvhmc?cjS-!lYP_?sF@Z1K5{h*w=UbsRJHU zWB$DcCz%|L8A9*nxtw(ovM&*wUmxCLGIlI6$l7o;(Ft}olHEGTKL0`gqxzi4O!lN3 z@keoFaX$BQEpN94`AEYz1R+%!;1Yd9cSq8@d@H=gAbkMuDHE8^3uM~u3Dk%?o)x60 zD!2?YbU>Lscpg9StiF>U*$E$VJd)j&e<=k9Qje4T$ogi%sjNr5cFC#1wCjZA{j2J? zdYSH~kLaZIOACW(REQH@gwDIi`$qD9zu4&)Na6)_b!DFE_6C?vd^3Y}jpMxT^5+l- zwj{rg)I*W~gREqQPK5j{C+e>Zo_&Iwk${{_8Fn@uS?9;Ri-nx0Vqar9*&O_PZSF}l zl5`Tiv5E})MzqLcyrcQ-w!kktugmedMS2Q5Ih%~TTXT?t8~PU8Ux#&FKspm*z02T* z?ZPIn=D+rFvodkBvw=|^;w*3TnN_^qVRqm!K2a9tOFm(>nT@vME?t~MtgJc#U34qq z*Y<#K-BCt~Gen3FOv!y}rq6R@xAKYYcqAt@aiDsjndU_opw-yhk037fKnfQ!U*S1e z&pkWJIjPbZzd(#O+0q{7L|XOrC(%wE29y6nPo8Vs{wSj7+-SZ}NKH2Kym^=&GDpA1 zlNv_v|1$Pgl~}LDhp%StRF8~h%xfFM6yX)b2LaR`j<=J>q~dPZA8>B*s!3y2bO(D4?T7l)mb;(7C`? z>WHbT7<2vlQhiXv47Qg!bLD2SOv5pQQ=c0@zps%-w827VXV%|9ItXp`rj!>%TQi5< zgX+W^s&c}?b~<~$xn1r=lcY2b%5<`}YU01;+iq7z7#E`*76T8KLsKnuFH9 zLpS|X#t!)*wwT%$d)lJ@)~;q%RP|!7I0Z~)`{_tih1+@vMr~{7y!b;6)P>gOBl?gocx}gPE$B(TiBm)J{RmfrP8Lb z%1>v{9GpTHxSc~_i2cx`joNCQwur|&8@-v7d4uTc3putTX!b~K@%rBg@l9`i>u3Bk zc+N?U3Z5p$-`aK>h!*s#`j?({QXZGfjoLDS*yKB`lge=Fmd80c{h_W$2=4ohJmWNa z(p3;;(I+2tKk(LM*ttON@k`Y~{PMSCg74p|sxw2?iJ!~6+yEtrL#wsGLenh*5Bd(6 zLNI!17x%s<9;8BR)*zbQjz<52e;a3|yQjkg}O*uD002tN2RnG5MwD*tw7B>2PerTyE1_Z0}YgeIHK@ z<~CQui_J}Z{2u!QUWTPx3@+l%6FQH*+l8Gk!928O*vfG5g%F;dj3>V1=X^-(KK$L; zM2frdaq5yMT8EA3M4S-q{0*Aoa#N$&iI-TE>#T;^n)n1W@$*{|HM_ki{>1*31%p_D z?(ff=Ex{_RL+`&N;*7<9yVv+LRYJuBK!@rEg#vH@m&1I+WV;|EK3Al42w_uIG;`yiJOkbgYPk_h`WGBk8-VpTO ze?09D_j(UD#_jVI!g(jhZqsQJ|FRbGdm^IuA?$`b)9yQKt3yORmy<7w4_pO*Xg{a5 zm=h>~ReZ=>GY#%{4x}0HzXP8k3BFZnZ2WCjT7#VXGa|j_yir~J!@fi#%vxtZp7S0h zi2dH-X>?$x)?y>C{`QP=AxEisj|hB)c#td?uepo7p28YU`i-g>JoSH^+a4IIAxt7! z&8;8E{siHd|A`;=o>MQ(cMbr{IL5wo#qO@hD+?z7vlvfp1Bl@^PU9VZ@KL!#7$-^tFn+I2X^LDm&L3Z+I_x!5OfZ zzu_UC;WTStQ(YE?o1JY9@>ddX+nt%2hi_eiKYowrP3HvLu>)y2v30C$9}>|3A4YM8 z%aQeN_)XEs&mP_{1K%5lY%JwziLvK@^ZOTWL?@)M4Nrf>H;>`JXX3?<;kLRsPENR& zBk@i$aT1DWk7VC2;qm<9_Z!?3KR;UuA1fc3u}8=&bLz47jlmonBE!$PHAC_CT^4_G zqL6>kh7sJ{a_q)0Zd*nWmVdc3OWEmN$k$a?^ne|0if4TgU9}nPBa)L$kLPxUeLI2Y zU6noQhopsbE-#R11Nq1bGC3JZ*u@)sN0$V$*XPjb>)4}Mq}`pWIh5S0JKg;jcP9p0 zag|dY$0s)=tO)HtbDnwB#M0K8pBn0n+mgi@K0Z`V7viBhvGe zH~tf=P>ScfwI(i;d>wiw20fXZopL3yDl(oF4DB(#S1>sJ-^fKzG{wK1#T`8f9(`K$ zU=emyqiKrs*)HtA+kwl&H`M0OZeINkce(*LbSc`(odV$21G`y|uSivM&Tb}}VIsCF zDL#8?WUehInvrPY3%lPI>1@Gnc40M}kk${J&^}^@R=jP0EZYQR>j{z6Se`tYy*-Q$ zdB$o}vCjE01v?|R*Z3JXGuVcV#ab+bJIAs$t7^#IVb&h2Do@-n8#$kh&i;sozJ{fo z&fWiqvwnq!eUD9hEj;g}Do<`%8Bo=pdP+KCAMeI!tb}}!> zl-o(C9a8D$OYd=i(;{#kgh7!8uNIA0vpW?2Y3;j{PqlOi~iP<|^EmpVKuQ!%Qy~f`yPgM5O zcti(^+n)Dggm?wcoRHeK51hd%I#a)P>Y1B#CjqO9y4^kYcQ=DXX1Eb|*R5Ya=tMC) zs;~LOerI2xi*`omu^5Z2&*8>jVcp%S4Xi+C&Jl4j#s+oFNTsq-Q(Yb@K7s~miB`VE z9zVwipbwcI=-UdL`HARgum)3dD>zLrio6=mmhY6g0|VRDOxF$y{1jbB{Lcut2Owu@e|@t0#=S~9MBUaN z?^+&eNcXPTI;PMhbnDvW-iYS`2X3Mz#DJX5PLjNtSWRc*-1t15d4rv;xoN!XBtOE zAok^|xM#1^pR<-e4EkE*U`@@_+)i{emgu6W4F?b}o(01L_FTLDKVRtkCbI!$2bCRfi^ok#2?}$&#-3j&d65-B7ozvOi z_gg=`^_-plcg)CWqRPwpz8C)H^q;*b`uf(}L)8#_x4vdgx9j*b`LaXh&YY!FA#5lS@uf@s*mx>77yH68V@&y*9zixHRGz9$P`;%-cx71$-uVD(7~mkWQxd_?HqTkx|t` z&#`+`iO@k`qUyRR{V)PN%c%}tVtgj&)R2?i$|rl_ZA9Re(Y=Bj5UFSBzw|f%8Yf-` z+JE7z41l+@#AqcvV!pGTiL?=VEz{D*+H|+`XSXZJQ%*ZOS%PS$9xoR=L_R96?vdYF zAZE+f#$t0bvHuEup1B5-5S_O2GIJ|WielK5EllI-K+RAwncVwbSn9souX>wZopQ|Y z`a}m03#{rOGItBFc~s0Jdodl~hlApu#^#=tbSULKJ8^qbseR5lE4^kuF48w21n)Fx z|7UF3F1*{lx(>XmzB1CtDOb5josLn*tmO7WokYbeAN}65tC@B;@Y%0cyzEGvpT`>J ztC#2@v7X~yp_iyz((O!I?{}2a3VV7654tY={Sk7Nen~(4pZXITLHT-Fd#pK52$PmW zwNxpcSztUn?0&=ycYfzGe>#)Z{$H^_Hi2uF#%CPj7lPMz{y{TE4%%t|m5^Kg^2g`zL&2IDYF%Fu?NMOBaRTjt5qXyMBRba%7Q3gjgv? znQ6Tq^y?73&m;JJJ;}FZU{2o@omQ_y^CzdibC=T~t}fa!R%K-BgUcB>q_@+7X(&0+ z=enZwi-t~RW>pO|yhKU6;i0tAS|}!FgHQOEAb}tKi|svnqv~!pFqVmNMlYCL&zyJS zsz|~Fv$^c#Uc8;;;NYu_wGf1_Q-+tdmi`+vWn4l%ONq;emZ5Y0>sTcZ> z@pnb_Xfa*=qAIB&U7t7N5y1f#*~Ct~xvS`w!r0)8=+<8B0v*<*p(+`}JzZrZ=c8{B zyz^!JR5+F;2Qf+`@`4HQfn6qSW}=Q1?A=1T%YTrC#5epZQ-_fyt>t!fGFGW##tJG% z=j*oI`!o3Y$M8Z))B}n95o`jZ)ozL z%xRy&r&cjpCJ!?Z!te&3i64?_>z*IXMj~dX;xaY0thvHV8A1}q--FNribCUe;;o&I-_DW7Nnfc}$D^4f6O(4xY zSePDkbRIygY#&JElX&Xv_oajy!1vBJaOO1X(;h{})Gx?P-syFV>=BF(@7dhlBtNJe zAsf2o^x&))vY)YRBw3a#O$mJ*f2FES6v$ya$=Nb}u#`^8OqJJo6uIk-Fa?m8%8SZ3y~od z3if8k?PbsJxP4Zi*LBff??I#u?UQJC!lsFWfz!6L>IGVBg@3dOKQ|Lmzo&Q%w?s9! zQ)oSPq#2xTDj7RlN!<+nArl8;ng&dsEW?GW{4517{aRV5GfV4mQ3 zd77CyBh+ntq`Y`GewNP-eER&%MQ?`I?Sgfki8q`^_M_L*B@v;kd#iODz1{U^81(e6 zM19lKO(risy$#;W?4XeyXlKWKH3h^Rvjd!v+RT%uWFGEsU08|5OwjG6pXh1snc#^~ z4%37wERmw7Vm2#W_IY&AtN@wB^vN`26K*vRshes;cPuIYwGW9}EC?Mmvs@K+hmNe* z(Ld?XDs70Ty{3MmCi2k)1~$sg{4)n@Xy}U8;PQ%izWqTyuaWf!K{PZ9E z=@^#k0(?Mjc!=fr(9!s;KkyJMGgaf78H?6~dFvYy7F+th5;UZ1_KD<(Fu% z#n`HBuoE%i!&Ac}v}A1>j651lcmbIn!DsZuGA4r4N`wbGfOR}Jv9XsoOdkB~Q!s7& z@VV==Bb`9-8L??+V5?Gt%(vsem%;)U#Z%MphJVll>jaSr<<~=pA)}MK`&zE(B zIieFWIq}^<0TnP0FP{_legYp_2g^b&3I0ngs!{&pXC6C& zpGxkb%UNK-*|S&hB|%Pe3P1HTN%x@HYQh%nXAknRx_)>#N!iUN`2Qu@!xqFQ=CgB~ zVaQjrQ`6vw!;rq^W;2%Z4A?9?|9T1?I+4$UwZk)y4-=b$+=M0UV}4|jHZ@q%Ei$N9EI*{EGa-h=K^ClbE{|%b7ICsvUp%<51dIGs#50i2b zUbir-?TCiCh_uG%c~-yx_eRp%v!|7LwgN=?`=djv!S(o+DR)dylLXlybDrHg2re3G z`k174tYO|FS;*(8gP*{?zO*)JY+*QM@y_h z;?g7Q(YSRfk*ln&Sy?WzGt(qNx)%aesX4Oc*;1$$bM(n z@9_I4PWL6;)>D3EYRn;TQA`o*5nlW!R}p$ z&1#Ovw}88frQ=p#W=}KVAAjQc$P(u?+M)sS5ohno9)Dzqr@;{Uby&}MWql)f*EIBp}kf(Y`S1yoCVPa9!xc@u(zL-P|qLE2l3V(Au z=R1)b(-Tya0ZYA^ofyj}Qhmo=%7Wg#z-iCsiJEeg({iJmV!vMVs?Nw|FBsun?Cf@Q z$#vM~+x(e6y=ZK|=aoNwBqq}N4t91oy3+5zFqh}pgigv$Ken&jtdVbl4tNu!=Q-9myc{ z`O_Pm@J94lBl2lmB2lyGD&LG9yn~)Pgyf&(#P9LRc{sHocl0RF{f5uk#YsqZr!frd z3|@7G8`%(#vmCeiF(;RoQ+Z6ihTnzD@c!pW)noQ!74J=fCHEs?bi_bTW5a_@1LN=F z1QwDz_YuZEg(W*W#?MJcv?6|a?-hHgqL3sSkXrc)CpYGV~e*F9? z_aqF?{w6nKEIafNiyDR&jSH&qdkxJ;g7Xj!`HGF}$(_uO_qCZdEkUODa^k=9dx&+F z=1JP~UcWniBGH(vsfIjbEmqfo-5dgsaquTMy@^xGLENATXxERWA4c2yoO4}Pdk9IT z%R7?PAMf)By56rg-oVXif*dSC4i2)O?VNF2PSBrKG!L9V8Lqn+STQ!brn+b&T_>H~ z>-IHy#l|1?++VS5uN562ZDTV=jk9^Y4C;*>Ltlw_>Z|%nN1pXwW^shh52N*3eFVPp z3Ud#cJ*@`dEBwPbrW7-DeYc((94Hp3pvQYp^-S~9t%sHAs4s|>ZYh0L#;0r7X|zsr zzULlH#U|uC7Q1y+y(Nbxy*M4}6>Jf9lL@GaL~65z$U#HS?}6EkCz@S!cG6<&$e1>d z>uME?MyN&b5@xlXq^IJNSuN#Bd!n`*jcf_xI`YEI!9NdIsh5rZ^Ks zBd5OX#eNPZFSP~{-V=OA9GIT0GJ(2B2JK&Jp==prhUc0OW>zq!y{q?1&$+1zN0tx}Vzt~%s4Cr3yc>kXwI ztF5PHPG$_ZVp4JANYA>VDh!;{+!+ebeuXG&1~h4P zEXNV<^8(ckPCJZgb#L@UojGbUxe;5@8risWbFd4E!~*XF%>Ki$|H2=J#|a-7{f_ev z?`t^tzIOlET~>bEk2sfyCcWDG3fO{xden-6-MDT_v*%tX>LzCE+u$P!OYf1f)r z5&l$^sc_M2@&87k8&cYAx)WW6202dXo!PExNuj1YK7w|fX`JhD8I!&OU6D*OA5Aiu z3H`lAv<-9*^a|zjBBD+*X>PK25qq(gIo$2!F>FR1dS`XAb<9okmySk^dW(T?;+a=r zTm9Y|mynh)G|(|@+IF=0Vz|I;=;%5kkxoF~UmRPH$=n+pfxf+o)tsx&*{oh1B1v_b z^U#xufOqU+W;vY<$41^cDlr>V4_8v=3>Nct(PQfYoqq1i?eGM{bn`$=Q9zG&*P95{ zn5o;IZ6)_p#gIz%C1+tf`IsB!5;Xz~RA0_V*Z+ftzX$&ljy;<}#$6<~C^MaGK8C0m z@jAOkmPbb}$AYdF`^f#i#%|^bT)`(OEsA>&;5>&j=erbMj*n?up{dHyX{o75ucoUs zCMMZc5$d`c2`_a=yRIu%d-$Zz4!4RoklMpoa0cH^DSU_~bUZ&oJUxr5!+z#a4$l~- zj>6H+bLx4;ysco-TlQBbfCTMMomFmR%H0lmiW@ix#v5z(oM-`K=l zAfGkltnOFx*wp)c^F|B7zzCGk$Y(mBlx`Q4<{5$M-yWZ@sf-+SeDMH|=9 zCG24x6UON{Ii~?LN-bnAUUWLu%@pO#dcBxVQRB&hkB#QM3oGx^`Rj)|Zg!%{j#B{{ z121&DO)oRRBaIUSIo+rBluR!l>Y{dtx6uC3P0b;*#w)6)aR=^$s)rFhs|I$H^fLc$ zYU{6P!o=i@R^`5wAlodle2(q)Geeq#vQA?Qi*ky?iHNji_U}EIfZDJ;_ncp3hwzU? zwwy28*PgFtkJ+$R(>d@&Q$K_Ks!OoK}L@Ri-v`kNEu5ZYjRHwdx zWfgR+8xA+4)HWwhn4%+5Kz$-lFD(q~U8b*Wp`U1Ke5Z98aT z#3M2mW{|nifqz?$&T+9)bHG^Sw~`N%8WcMVJZQvsraWBrMl*(v-H~2lu>YU1-@}M*Jd-=YiOnRE z4F0?kVln_sWD}xpZ_ExmfaxLWWj)o%X3&y(gwe!9eV?cAh6Nr&B(*D1ufpKA6nOJ3 zk?Q5Ju2;#$=}WHP1N{5|(W)Bajp;#+MHTb}cvpR=t6E_*+Pi4ZFlq4zc@*JT3zr<| zVzM2QVZ=DOSKT5CaKJWE9i;}(Op?W@jGK@Dkr)rN557)%Fz+;z-hLpPa*WuaB)h-* zC-SL9-a~J8w>JF25^!!QbdDdZ9!flxj6x^E9Fv`i13z{SQG2yRW+D#XLL^gzWKWgf zsVP>2Q<#LTi(!#2n^h{am>ud&d_7z(2u+r+l#~_aX_3;&K>m-UNloxUhhxQO8pjA5U*suY{*VbBsbtSnNw|fPG&iQCL>{O6o_^NnDVVY z=pB%AskiYb9~BV^)h*T5$)eBMzjO{=){czGsb|W%Y8y;eYjQ|xsJTvc!KBch4dvvCKl7 z-rGw(S#C3md-jJeXzGSyk$>k;f<1~y5VX_rf%cLWoLKwpaT>HrdCtEt&s9}b$1A!n z%7(eBs;mJz%F3r^BhJ#xOd(r$FWR6zdy^9VlhJIIKeS&*o}OJF#re|tj$5Cf={P+= zL{ssTYddY_5aR19qykUmZnwBYbldZ=^gWRcauLm2GKc>%H(fs)?GwJxIJ|`Ur?{)ndYJUKb@ z(TzQ*f_{jfyG%|6u~f4;=pKiMNhd*%=VJaCh`ThN?LmC2+H|{2NS#n}S-@UXEu5o) z$M`I{V6EQ6!OXzViN=)CC|kwbYG*MUsRoQq7tf(DXL;!ovB(UvmT%EOf+=}+T+Yc{es2c4hK#QK!N;Rbl zqEFzkNaBsvDQuTe9x&q(ub?+wuhFH{-Ke1^1sM7|IpN1uF4;(Kw+&SnIm_H|)9I(k za3^Q8jHimqEc|Q+_++y;P!|z<^%ykpX??|NcQ<^+57^+qx2!wmWV(W>b|C#7HHz!`ZrUGDxR6@7nRN%!@W-)dEO*8Md6*Z ziK3w5vUH7WWrl~=nQvh)y@jH6V3U0l${0NIZI&wR%oBUvBzWhmzvUMPgQG)TWprjz zzZBoh;ZO^tJGt^1(St7z1C6zirY%RhO?32s~nu1mAJ2XH!9i8S^R{dFdr&sI%^E{&W(F@hUc0 zdlaAdgWt<@XJ6Qp!PIzrYk$Oa-_enMPAI?FCYfff+M1at`7ko4>`T5EI$G^y&cq&>+es5h9@bK= zqNe8sGbPg5ykYT8FSW+3hlObl7yg}CQhnkNYkiFAUa@hJ_%ckFXwGe_1gg0LSBE`@ z4I|fFoJ1PiYnW?yp?ldrcBgS?%fgks!^1CyMasv#u2$YlJf-!ryL#mHGRM8*`nfxV z?vbqng`;)SC$R;yLe-dOK}Cf#flkG)jA6)))bQ=WJBF$pccL`J5KrPwy!Qfh0+=D+ z>q7F0Y6*ItDl3X6>Mt*qd8F54RmZ@PrGoqWL=N5x*tO;|pZv(Q^|a<|a1Oq49FWP# zU<0^@L!485ZsZI6>PYTWZSb3)@iIgtp_B1OFPB;>FWV#XZ@Nsrz)zmRvwB$J4EPo^ z;51*OSExR*r-@DkO=f)Harla>i4}auu6M<{#6?Smfhj8yciI8Z_Yb&fgKo>bsgC9p zGXeIhk$N|FtPXLsv1X~5O#h#>@}Mc`4%A28IA*rgI&t{Hz;>I|ee3Qr+nG(6)OMm{ zYkpOVK0B$A)C97EObj~R3*)vtY7J~ndMBH6PR_J9y#N;JoLtVDhC0LbN9lK3$Ou<9 zgma;*ChT4=x&+@~VoP@KqN)*CWb(mBF%yLqB?OVB#2^BtWk?DkVTSuNsT>?X33(0!=G0}=X;xUWCzIO?KTGOP`G=3jMCCZ(Du zmYE~dnCe7+F2jfW(|LQ7KeG^q<$p{aGU5e%Ko7^@pFG94^}7{R!#Al3-`*KSo?Ik% zy8H1^bKLBu0=_X5l3cMrG}+rt*XE7hK5w1LYJS+s(!%w`#ENcp>cV&G&{&?o59gW% zk9$1K7zwe;TJ;ZnGd7qt$cKsLa9=D@D?)h>^KQ#V4 z(a!QZ@>7keH`(ZjGaetk47cYRcZfPSF#uiHP9!F4>jTW+bLK&9mA&XoeMIf?!o@1p z6XZ}{CgoO88;Mpr&C_?s7W!Qt<6z%bkcU8hq$r~g>2NB#7Lny05b*)S!7+{ra zgNIDE9|yeK1`m;2x{XYSINYOm@YwxjS6Hi+;(I8KcU6CYF-Z;^P>XI#?XmHtsl$(; z&%hjROn@7c&z|QLYQdYeVBa!;jO&nh(2gwoaBxo$_Un-Sm6+HUZrNQt*ZV|#{VJ`Q zSp4~T0Dtk*DzMz0VBVMVir(}}fNUO*dJHvX;^Uic=v3E$wabh%WSW5^# zelXU{ulOeG6^-H7)|O*kyaM#Pjdy7N%T4wFeEh$!$nqtk6UmU{WMroP3NP+=LEH@s z*@K8eKt<1v<3^$fF<;j`74VBDlT1h|I=h_5P&V&qP+vg9!5w zv3Sc$oAT_hd4ij;7O6q^omtBv^30a;iaprcF~o)k5GCout5f0gB;mb1e{%l)iG$4M zUtSQ^^?SAXeS~(w!(Ag{Ai!sRK)tcZ7?5;XzC<+qJyKK~9dQBl?n`((o|2g~a3rZ= z+BzY>J+Tdq$lCI|3%)@f6_QvMc}xiF{U>L$3A{9s_-cCMaQ|cPCsH-}lFW$O?D7NR zc=KWHcCqhI$v5ahl>I-@U@7i?C*m{tU@JNA)gV|Y4%qtV0-jjGfPORQ* zeA0TvP`7ZKcHl4gwRe|z{%o+3uUOM%K65 zioNUdGlSzJQLRp7sa`aPe#Xlx6RWOAyr?qwem&3ojgz>``n#Y#lOUfZ=&L#wyjv5$ zsS??3E%;801SN-CAIP3JWgW3#-MZMdFo*+f37E2&Fd|b}%Mo_i&muVr;|m60_xx;@ z$4KB|B(Mpec#G$$jQ7zKxi5u{k3;P93P`LYan$ShVTag>S4P>wrVM{OVRCV!lz6Ql z5DSk@w}`*lozL913RJ!L6^jdr!$x9-TOhv@Td)h;z6I%PhHU0y&zG?lzn9lGB(n%i zi!o_Ig-hVZ6tAlnUa(3dVr`>X z>2h}Dy%|P!%2(uPE6?ilT`Q3!zY|z~?(h-(JBJ-Cfn?-nrzhacoJK}AqP<%nRg01L z63F)~?!^V9$!DI{@jZ{Zc}J0uvE=2wB8L7FZ@aZUK_>cfX8x47sbM!~m>JabRUkqz z)s&HjlwtPK2l+c4K%SX( zda}9WHO6nsrZt@3G`SM3OqX*NEv%v7=Re zxzeOF`NK+r-S&Ccz>9y_8S1l4siKL*W+&4|& zUd}f&h1)S-L{n5S%3dIynMehg%n`bU9i(oH6tcR`hh#L7;&bMR1QFapuYy@82hj=c zwliNI4O|6Z%va^;7(d_C^e&qY>O=5s=&iR>>^JAp5c~8!yuEfzadX^Cp}0&1dxbqY zCF+^c(Qm-hCV|5~VtR?b(N?OGfr8`>f71)mtEa?G?<>;&oS0885nWXU(Y0pY*>p6+ z68WBv4Q2IVb4-*XoBI-xh&N6?cOvniCnB-a+8%T3h(e})aI8)(dq=%7yF*Q#JIqbW zXYzVKB6f;$`h1Kfc8zQx5_u}HNj*W{W}!27!vTHe9^AB@yh~W%4DL1kmi|#9uuOke z706JGvTbZ)b5KvQE8Lq@Vf-3(AM)mn%}->hS_BaSIa@cD-9#;Fbm0BmaiDm=TlZ+R zMM7>{VX-LK#_dE$#qQFCUuT|l_P_?@qqsXr_cP0}Prn8?kbSiL$ohF(wAZze72 zKT%tUlVSWod<#|Z$W()8s%mB;A#^D-$FML%U@}h0-7)IJ_ZIZF;WMXWO2K=5PleeD z8Ws^By}cTRy{qEBlntF6PW<3xxS(O)XfL&M8H@GIJ4;S~Y_A`_4fB7wdoS>9o^j`V z{9cyW^EZ8tp7&$SM?961Fw@((W2KSLk*wl^=%nMDp<&IC!;rZnvzp)RS7H&vn1%U7 zr&Dj$$xu5y=Z?|8kyVmLe$}|5ERF048k#S%p@&+NKU+t{r59)~=6N;*Wk-=o-4FRsNWZHMW-or~eGudt zyFuNANlE7P7gOB|UfMtbQ^KjBx<_xX$J!ymCZQWlUG8l+IqRJ{=<PDfS6mkVR6W@qeS(EEf;?RSfaV?^2Jd7H@!OsPKUKDrFP z*GavR*hF#iRIix(dMxo|EtAQ4_{CY3$xB{r^mnn@G6nP6B&4xk5QZf9oXRB<6t$BdKZ9u-xhcyu+(E=h3L^V2!PGs}2xo^pCVK?p;NMk;MVt!~Ea+Epq3=FpYhJ)?ej*cg z4y@y^vH}>a2FQP(nF@}G=CqfiR0Ou=JKaks*{XER+6r%-o=S|I$mA2MahAv_qNd!< zj+X;<`H0s;RyWzoZbm)Li{U0=7GCVY;7}6zhIq$6$bLSia3M8LZ*t{6;j!&DNAZ$g zdr55(Bk2uxft-#7R2~$@a*W28Xbd|4fW+37oxz;lWKpvLet!W7I}zHTrz%9gLJwI` zo}v=&71c4N-~~H?HS)7pA3!nbr~p|)JY_0Rbr}sd4O!VFhZC3a>u^7U49dU)bt2Z) z*1hds)&JQq--XOf=1I|DYdEMIL}lB9-SXqDB!;7`L>*57@t5puvzfZ4flFrz{ViCQ zypU}o%A2XHGxM{OB-cz{#G?sBAE;A?9{`~YQ*mLvCeRhN1l0Z&`J3RnDH78k_?Kq9^{HZH(FJ>NNyUXDhDQBNvF_$d+{COtv_dB0G~=du0%1YJ9UY{I`o8yg*KG zZM?Fj$OC<3v8oC1#a^RNN+M6okeUrdv=?wE!pL@6LLT)%-qnG9djmeLgN$wEbQ3Z=*|T^abIjxTB7S!klw#|PfvJ-JxJJi3EzTneh;P>q2EDEvf|FVjDygMOMu!kH*KierU@1B8-#Kr!mMoN$3`+Vf) z(J2&7_nBvI%UQIB(OtzirsD>D$ImGTUzs0ICKGyM@Xt@p3D%j*y(+_d{7!Qj_}f*^ z;wWpM&5mYdeHM+;8K!jrXk{&H^0CnX?y_sVj0PZpk(a2KBsY`A+eGie| z7yM}zoaXTSeD5VOIdIaX#+Jp$ z+uW+=>7I57{9YZRT`|GTF`OE9oO4!oLQk}0V)GRFi__Hil7r`Ud9(Ffm%tX0YjhA(p9b0Dm$O2}fj;-jv;7(S4)UY3GiG}2DhsB&sWwjJRz1;bqiB3!+HE5t9 zwZ!eDNAJil$VL-cL)}7t_G8%+tHxl(hS>GfNMw6-Tq*SNMN^BuRU6P{2cnof>YdQ5 z-1Ryc_3|_ARxtH7>M9*s$W)dK+Q;2jU86FiXdz^Hy;*+DrWCC^x-iBBjdVgvklws(7mE5hKX!#xObQ>sW;8 zh3kmTjs<0;L2LRm1q2$b8HjVT2&V?+2fh9~(ly|Kc_}C2TalqC|KaxMr+Rj;er0ah zHEOC%0h4?$Jv5!oI}@5nqr8 ziDP{aO$uaY@2;x}J~soOT7hW&K77o3Ac9|+ptzoSZHvtr`GA{O!L&5wGe%W5>qT6h zRNk{?++$?NjkUkg-~A0$LjRMs%y;*p97GSKVytH~7|e9!4Ef!2_vqL9jf@w{WM?_o(O#CD zS_B5QJi0U`oerLRGerR=d@ZrpLb(IUWPZH$WG?x!A?chnssZKm_W>dy+5^M0X1w<^qx z!EPkOrWf;dtmxyNf`c2(IqoqXK!o4O(>(9|;%0TuJ73)XI+=4RgqO|)f#KYRBWOhI zl<`D6a@H#94Caj2Rp#=_P=mi!R6$GB{%Mg*V|nwKDOfDJc$iZ3$KQ&M{0@(|5^XdO zo!o~A(LFxV?^7EKELIqc>31gjMh)Xm+sZVx8BGc^TqYok;v$k!S6?IhqM4b4XZow? zq7!?EY+|>*jwN5(8uV6(sj`zVcaPpYH9-NfJGcbv|xmcAiuAX z^9~<6dTqRBO;N7)9c$|}F2!#Bb=52t?S z3iEGoQo~gduBIz^-cB=Oydo3 zb$N1B-_jxF7`)DM)545X2Sq!@Ocap)CNy1twDK0Pn-bI9@7(WoNmLLty)YV|YMlS%4*m2(p4too$*i=N&Of?w<#(Z>uTF6CpgO6=QMbDoKnGtf_&u@rOI zrAg??M8ry(iKC`WD4Q+h&JEpg`bKRrYt2kmU#AeqO#|4;Ieb=A@XspH>32|BEHvsV zPVRS|9$(~*w^}aKe-l%g3m<=*3Y2k7B=P4keMO39%d_@(?#)wj2?hDP`H4UEMgx)| zg=}r$21bDAAEA>T;jM@lyW*T~EU+2!Xd^S3z#U zJ^m#-I;{ZF5x<6{4!D<`esGqb$DD~>UJ0@;#Yy^=lTSdBIeB$$>_Y+m-yN_icX|>2#5I$UpKW2y{tSy$KeIS@kQwgh;7-I= zX5rLh;9dIj5ng~^-{AoqL{@I_bg4ioZ$RX+d8+E1o5R08A_mo))mhk{=OD)h#B%(o z&=c@#FC>0A-bzjo6uodj>G!d}(ZI~Hv9a~QzkY|mGrZ~v-+2aaXfNKv1orSZ@Z}|Z zfz!M~v+BpJ)$r$>JWo8d{sUh14h(BRZ6&yAC3*Kg);XFdu*k$Zb}lFU!8GLS8=5!= zeizxEc%aOB1|zS+yCU11nA2*aV{h;nVDLfI{mB>plbBU8VnGh4|3A(k8&A89Pff!+ zCgMkIWN)%ix3mJ!Z!E7U&8o_CzpE3iI>f(J;T%Tun*K<|38Y{h>&yeDFV6YG%;3>2 z=lKtjBXOAhy~_%R!TLnwGc__-me{3VbwtKCr`ZTTB`)6^VmJI+wVP;>^T^O`@KP!K zvUPm^7i8oU5sIZ)%4hhFepTc@NYzH3rt$F*3dh z^d1kk@E5-8G^Y{7Q{9XV#X>@xu)h!S7{>Ge!$?gI>{&G8Wi|Q6D589;z>u@>N+j(d2F9r*{l;ZN6$j@Ox%b$$o0C*e-*#P4Xu?YPgb_ru$I&3CuQ+EySV z_znN^io5w5O~piSKJz$dk{@LHf`6q82v$k4o3)9j#^EIWiHfWEmOh-7MQg9%{uYAc zNx<1BB5(6=;uf{I?}d4WW%yu)xv^8IF1*0H>kvKM4SP@q24Xq4Bp;0aa?buXEA~6J zyke&&;3<#B^PRnAb#(^|pCK<#V!BQ`*m}au^d>Y%RB~cN7g2Q{<7^$Y=di&BhOI zU=P57Z-raCP6p0=JkPuAe_z>_b1KfQNkdjwdG_QK@^F;grCaPkHzfTc`83PGv~bgKZr72Ng~;7XWT`N^s;^n9 z<_W(a$Yi+5OYUm57$2YkQ!Jk1ao=#pnF}%wIgCsWVrRR^)Z}&gea?p=WxYAu8~7WY z$w8d}3wVSq>Dpx7e1U;JZ~qMSfu&1eN|>6mg(>Gf67Th8>S{jf7;yC8O#v*oQfJ69 zeg`j5i*DD^q{|eo=xP8fn<7dnp^D*it}-XN5%fGnd!(czZ@9PA#__U75y$e9icr)j zLq}Ws3@4G{oJs|elAQZ)-V4*pJ_s=((yMHiFq7cBcSE!>MNDaVM#Qj_R90UsD)VS?*p}U;bNhe2Y+*wsiq26Wvqin5^YU>fOZ`t|)cer4exHfo&`=r3B!f#JN8}VN>7GjyIjuy+IE>mqf3IywS=^u+E)uivu zX=J<{>{@x#&#(I-2ft@nN4lMCm6N=*+?F%?SF-a(RMx;E*y}8VdjKvM?D26Jc<+{5X4DYc~oFogg z6!-ByEZYG%>zhb>Wq7Cvbud~Vb2;FJu26@z!h0tNaI&58BtOVuOm+8jH;Qt{pYmTl z^zAx)%eLV9HD;g;66H%mCzUmzy5nZTkN$Ri;AUhtvPBn|jY#D*9fwE(Tq1Jy)#w~4SHy*yqr9anuL7XBWDbN~+GB)!C+`|P_n5JoV< z?M$^$OB<%{*$G}1vWhOUvijt>Z^BL#!ZQ2Ws2#E8Rpk?}3-Pl?#QoZn|*`D@c+J`@wu?0pOB>G=*+C_eJSj)ubW{}(7%4i zG$vrf`D7yxsSfX&0QRVkrJBX-pR&d)+>xHx*H4_mFf4f?klCa`B{2CF)Nuhb9wT^AjtkaPc-gQNqTUe<#T;TyCBT>Gi=&y z-nor;_!->0`Po8YpD9^#)jqy?Uvs#xYEXK(+WWSptH?OgY{~-+nk%mj?mrdCAEu4H9%*8hD zrOWR7^F1%~&6SYQ8^j`>fhu+&S7X3SseIp;J8~OMoPyI^kCeQoXT=5fj;I^Y@9zYY zLV#D0q=B>EN`*p#3d)6N~tk zUA%S&rx=cI_yY}`iue0dQd`3FW@9hQuxAhXj7#i7Jh*`sysH&*{U>r*h#jBJUffredVZSSrgWes99LlfBye~cXaW%X3Cum_A*t8DSN&eikWc=#FQx)JWCnMk4@u8=& zmrrFhvz&8jHcZRgtVeVSEOnC&+#UbNYRum#}hwkNq8rhCk-l&iK{F&*LQP*yOC{weqI9hWE3)95i8mRSv$mCARCkSEd!O+CCW2__4TAb!+Y{^4zMF- z+3VS$?z7y(;UM`~KfOo4yNSQX$~^hM?9e&ZKL|ZngI&MCYBREio4oTmHr1a%{{fBl zjdvtNe-+`}&Jh(k$EV)KH#$t*?*UH}jeG9TmiD;Ut9bG`$Xp$CR!dGj8Jc)I?;FI< z#YRS(u{uBZE+)RgALt584B#`nvxp~E#D?bclQAH7zg9EE9ox(a_+2K-aDGj|@Xydt z{W&rJ6q0a1l5z_QB6XShCc*bz!&`X3nf{O4R0Hijg&p65jMo8SXGP~!Vr6;J?%!bL zk8#VXUFIeXaNaAqN7Q(;?zH@# z2=**L`lcG%XdQ8m2z1;nWUCP9z>ir)z^9Jj-W=l&`r2+j-#3P}MEtyuy}7e#c%DeU zqbZznNwl($($Db9FgT@m=#(3*X%M&D@2K8_6_4WYt2rZorg#XuR*hdv*!gvMBYua7 zKX_FIWHSz{Y$thWOZwLiEI%)=ypR1E88dD_M3 z;%U4#FZ#fr9(aIVp8{_8rwVjumkJQYJcsVMhjjEuN@mbmWDB3N4c)MYzwg4o>VoFq zft>qtQ<9rCnzL?)m(c>9K~6ndITic19sgx4uegfG=jXf>LU)GvJs-MyKd(xQ4GG|T zm1mWek+W4qD^qYM9`G;e=yeejly{U{a2UC%1#3}{&zyp$nuAr**qBn>trEybfd8k* z7Ek4=k8t{@iRkSGxyL~J%pd|t7CTnx3w!zwrr|YDA&{&V;IOctyn;p>U8AFqKe0D1 zuQ|`}+xhbZB>aPU$SL;b4&(vfq~=!6KqLAcbf)`O0>5B8dzlTa@C4K}3eA=iPuBMu zdXdF;m|Gr+<*J0dH$vk@pf$g7p9k`651Ad?mpr1s*pHZ0EmVS{nz(cvZsARY!;7v?>%vU zq(0UCG3W&yQ#PYJ@IKj%dZ-dGSi`CMEo(EG*;EpgQNNH~laqdlm1Gfe_9G?nESU?` z(pYZe?mo5$HB+g``l)Ek%kk>6bKP6ujkAeELuEO=(K#etb%$Ho%XzAzn=a~a`BB`H zJ4^>|MkDe$dXcwyPVP|Gbq>(oJ9CKDx1{UC7}JPaoI&vC^ppbK3?TmUnE6pv(9E&q zDJB~xkV!-rJf6a~4KamyWU$l~f5>Fvr=7aGiku~?+PCsL9e2x%VdkuV1F28RDEYBN;O2$n~}MHA5C`Rfm3yCYDj9Ky?iuyifor`^tEY5H4~gQaoI~c zxjAYQFngt&jl$y`#2lb;@)ntBQXepr9sfxi#)_8cgm6(Gmd!C6uVfaRdf|3 zzb+1WmV-qRB_d0}zttc|dhh(O`(~IFoR*LXIWvZ^?u#1ve zf5eUHKQ`2SVkYx?IhV}OQ;q@i-4Rx=Vk%)nky?*8&#*mzvNzYTDRbxtU6}rjvFP=C zM%JSt;}v` z%J1YLo_FZ}0H0q2oAryKHJ!~@fVI%c^>xs3I2~0KRdDvK=1Ir7(Y=lp6 zl};U!8~>0P%}nlNLM&=7EY&Vna1}piGS;#LHu^l8^jCbh8sfTo;NIg`>F+DtRxvx+ z-P9Cq(Ns*C5S_TQ$U-7x_zb+hqc*>HmyVGQe-zQB>3c9-MmV+T!5v@5Hhm*IMK$%p zO@wpS&B0B~pqJZ8k?p;>>UP)+u+SD&QYS+*^i-{2@dtqnCo$d0>YXM6y?q&r6AiXH zEvPe}xJM558nsuAR%^*ytnODgx)Y+le6MKgX%ltL?9f06WYQAKTi zI`((QPBDuJN$vxZWEMtb3vT=3V5I46qC}VAfnW|bkk0p8RitjI_j}20FS5hW{5b7e zbLLA*lSubgR|9=Kun9W1VIVj5gC3UQEzKhk^&*{$Nn6Yvkk~!C9VIa_)hTn`EpD$n z$;11bP9h5tmjTXQTPNCVp0|sv#bmAQOh^A-W6 z6@FwrR`QUY>ect|JJa^0aMbZ%V)X^oYoaDF1e5?Kp=m`X^(ZULDY9| zpaZ#lmx(>UAektcx2+cM*L{_>VHdeps6Z}7xtQjcEd%mhe^hEYM@{Y4f z$Rd4Uujo5O@#0Vsw##;nNNE$wN8W;n#m?`}?O=9lyLP}Z#;_-4QttN(kQ{SGK_LmT zDwWVzr^G<_Z}iM1uR`QW)k#eeU)&<5TzGn2R8}{?V@c0>^_@QQS@;=~#xx8221{zf zZ+R1CzOYGd1CZq+)j71s2y!r{!Zc;Z!*~h?Odw*LY9B{_ys5wbSQ!yv&W37u-Qjcm zxfrcsG76)eYT$wSQ)+U95)HU>HS(TNO^|C~cGr9E84Dft0E% zGWm;`uKH_MT|lWb8V=Z zDdu(wO?C>aXeyVfAR0Q$m}Ajb#qy@Ro9tg|A5(3g$h%B!h=EnuCFAqN<7_uo%pCKE zni^t7U<01A(S^dc1~ig74Ca_QcI0D>2dzvJjp?V=%AFx@mh#EdQ9$DCL(!M+E@kkQ zjyMO*2-ViUjQR)Wq&GP)Q_;Co<1P)X4lhd`?{rwcD9~oeZdY%}gc$+%oD5Ai+$?uW zIzzBS0lP+*QRC%vGOw$McwTk~^zVF^-@Gbe#ngI{ik=QNsqfh<`!M%mzUoR1@@}tA zC@melH` zsEs{MfF-Jiz1vF{oWkS++F)VX)NKbMq7S7m#LS8v@>yV+`Rcs4gJc$Q58RhPk2Fo( zYV=V458o{&IJiBwuz;9OPqth#5?8Rq=%<$Ze{sw<$0JZ!U$G4(2F36^3#6?_X@YAL&{V z7^-6tU1*Kwu4>~*X`bQ(6@(R=hj%(0pFABtTx=rT4s!(0!qqMzw`Gi1%}wjAFzvJs zmDUNV3(Fd|R+gb!X^Uy$zSAEf!`$EHEc;*R5LNhd`K%?ny;&=Nr5@@KnIl)+_Tc<= zVwHXhvLwdte49*NcH~bVMd+H|mI1-e-M@%oR=j#~U{Y!p|#>2Ww<9%7^3}#7A5EzEwhQvO|xO zw|X8qNZZIIEG4uyCK5KG2)tZ#*uC#~fbHSt{ESC`!t5Zps(WxA+d*faVOe{D&yK-o z_5i=8LbitUsgpp6alr4Rz@vk~F$?jIr(^TJp~r`y{jY*vCxhY7z^(MaJN$?b--Z+R zKx@b0XEJi)89?my_}e+GF`f!oy($5c$?%p}@R|$3gRH<4FNbV4A!{}Pd6u(5tbSZ| z8k!^w{I&>x@*ljiCHUickj*2!Zwp+{c)sr|tD4112E#>N;&%;8G6g$R5{6O}#jXGY z<7bPu0r`~y9jyo1Y~J`MRx}9&*xy@7Ge!vfZD6$=?-KU{Tk75Y~*hI z-=ScE4?NW&{HB6<_32=NI>R~-MmBHRweS=TVSJgj!gK%54h?5V)3eT(tRWuf_l)P= z!@fVjir(ZiO7i`+`1xDj(-f><9aek?=wUAW!w>c$Bj-OH4YiFOf6qPo$_`EAo&Nl{ z7G&c7&55qUv%kcerm#xCreY=Md4SdI;aiTf+o$+*PONAq_OCjhP#3=L13MOA-! zoN)Sw`CA4ooS%an9|lx|_nX0{HRBY1u&*bOm!a@>EAg_Kam4dXMFKQT#v^w11kXTi zH7E52B)pfbn%8_&1Ns)c;qzT$8tu6~9r>3&V5G0`OUL2L{Cd)rd|Oj=R~gp$1RWBc zEaQUc0iVB_j&{h&4Qz)5RRe=8-r^)ae;iLchaG8(@Ae)G78{Rr zDyNto$vnYx7vc2_VIrsVy%YI9KY!&E=ie5*{y&)gmSjytvX6Jrzgc<888E5-d~-kM zw33~F#k=$I)PD4S3Tu9fhkufK=RIKTBG}#btYa!S@j4i37pE~3DQ$@-<4d{XO#g;i zxXWiYL-t%%*0g-*|1 z`|Nw0$n7z9DieHVA@ss#_P7n*<+}2-9e5&Fkv+eAu*Wwp=03)T#hymiWNp?}m0Oe% zDW1%eMd2ZLMgwJqYg&(#wBgoFK$q;~3`&LUiQ}>wZ8t^BESnPFA8U`x!>VYYgwsrqGV8+5;sLxfIn4(#OAZxAe*^i`pB8VZ zHmIh%-6fWX{fE9Af{sXne5~Q-uf*>xtV)s5n?RAZAN8wK5AFGinGZoSA%9Vc=mARJ zh@1zP)I+oiTobLG@=@>50Qr#qW=NOMo%$8n`RH4ifO^t_lA&Sx&a+kc#JhD1;EuVmfdCVR@;lKCnbUOM#k>8;@QL(sR{-L+nOS-x# zue*O40jE_cDydy*YT6+9_Pnm6PT8xvL-+!_GE|K3e~H`$>5sLL3d;~^^(<3oT1zKT zP%mSeOHy@I_c1xpY_E~loW!K!l7}AMBm+T=v?oPur?j4h6#3|ush(metdPqz?1oXH zu()!+S_lHZ{^@1J#jnZb9sIL5OlY2mz3<A4uXorWeLb@(d4Bi}eM&{hf$+V)>`J zM|?U`CpLNI0C=$4=$2EBy->>SyZE!yQdgm)lg1o>r~ITC;>cGxMWqwZN$+ z`#P(FAM_0s(+Fob+0~z^OurSCRCZAT+knm$m2@IJkgZ~&IlwK*YcAN6re2_RARl-x z4(#N1Zj&M}Y2(LmKwaW2fOl! zJxj!&_A*($mc8U%F%v`_8}9BiZOO7vZi|~m_CI|*v{aTbjr3-l4-8p~{(~E3aUu}U zob7HdYGW_3i>?@`vzv)_h&wq{!mI9d(h1!LPB|OhU24i;qqovups<-smO(T101sm@ z^>+QqI_`@eZ7&X}bg&7}WG?1vcL2k50276SRb7_x{y$dvX?ORUw19m4SGVlRSn+O!M+m>^)ctg>X z7twhs^=Wv6G2E+bWWRpU6~h;Sj~=>TR8cX?6t&cy!l*SBZYZfK#9llEdvr8;O=+>t zgv2_1D45um(`W1vIOFp2CHf!&)=#Mah!riTljexfWBqq%6?rn7g6rtb`3qL;rHU!y z1_r78GCP?$`;-!Jo80nszjMp;V^6L113cObVlQ1#ORMklHi)*X8iQQZAr)<_ z+yW%**Fk-^y;TQ20hx?pM(TRZ#yso13mXvqwERrRw12(Lf!o3D`huOT2kLE3I%YAA zSL;EGwdq8bTcm;WF2nq?%I+q2zgZ$5!FJ!ZMY)v`ZhCUF&f81wesM!BAy=pitnoZO z%bV`Ca=V*NF&$IIJ0EsTq|on!A7uf$S)L2c)|n!b#Q0w*hm+l;a0*4gBT_jf10(pp zUEHjz>`5Z-)d1pW|0n4z;H|2*H9qHDdvCf+IwT}S8dOlaL0Y;L1f;vWK_mrfq!dKD z8>G9tL%KQV?7e2Z|Gw{cfA>8uoU`{@Ge&&l8{hCKp{Dg`t zG2;#LhiL8bIB|KDF%paI`J3wBL_BtRw;9g&7b+RG!UWb+3qjhaM3@>-X{syZR|)lTchQxEGlX8}!SJ7f7NIBjt=&|b z>cZ%c1yS9u`l6H&MKQAz@BB<9c9HxQ%Q%FXcmUB!1|pzei9<(-%HBOUF|Y=mKj*bY zEjEj)NXBYRbFi^J-2cORia2lDiCwli{Rk;-eEpM*;-dS>3_~!ZhLJVwhdDkr=3(g5 zXg384wu$UinByWiRyS6I!3>JqZXw6#q2Pe3>@%?t^@6l|j9(edy^pCOtIJcI*Q)l1 zNLP{D6;wymUO%;nl~2T$01Rm~ud2JECZN{3BYFbr7RhyPe}V2TSLz3HWnf&~R^7qv z3{S<2#n)rGKlQ_nWA1n-Q8Aqw{0argFJuKWlG(B>-nEAOB;1%B9R<9|3=`uw5?`8+ z^0MiqKevU&G&;EUy05)N@;!CH&8Aq)_qu@}K__{zO$jUSFEE{F^rGx#?&&Ab z9C5#TmEvkkBeTZl;Ivd^W?2_`)ts^O)XBggQxja`n5`_nQN`%No9JHxSG(^oafS6| z^aXc=tf$daBPk=bL#1W0U>oAx%Bqi!!J;Q8+gQ!1FG1C3G`ab8V(=uQz21zjYASI` zWm1p5$HCs-DiJUKBs4VtlsCM5uBdmz9=Pj_yiI;_&3}sR9AMtr6YfdeCVC5>dS{rOWzB}*W&Hyhe)s&>vAbm( z)xuV^GF)Ffdq~u0_T4?XgwCRsB8&df{u@jy|2D1M9eYGpk*|oxijd3wCQ_k}A@vB` zR^O95ydfqBb-yw?30cxfs*i)bw*Dvdb7i)bWKuc=ySSEt+A^0o8#v;w>QCaWu+gaf z1-Q~usya`}f>uz={vhh>U)?HOD-zYR{|xVZfNU#1)~PKXwlbD`fV*h+FgG^0H&-vv z!y_m4I`y?0>Nk+R)Z$2aI%eN$R15<1T%1m9S44_3n{GPp!uR_u;?f5?TDLF>%S{)rQSg-wxchoyc!%08f7aXl+GK@^s7AbX`^Xtvk@aSBOF-6_5-rwLf1`|? z$7)dI?8Ha=sl?}`n!d&jGA%^2@Nv^uJz_xOP_w{fA(t*mj97zQG(Q!Vfb}pQz+0zVt)YxTS9GH51+Zq0CwM zgB8@?BdY$jQ26+pla`N|=>QpZE~4#e#Oz946Pf+w_B~THMhFvsh@7cTxb|Kz^5dUW zZ{p{xXrgWwi@dc&P7TaZmx~E%%lMplun@lGPQ+3JTMwt>ikHoMsE;zyt-tv(GEH^} zWaC?fsss9E$+v~8PqE4i(lh9yr1)NU!-~558<4lk#eh9=Iu~l zz(nq=n5R z+nSrf>VZY7dZ3S2kXgvf^hz(RZ;%H(H%EyTGyJdixxjUh<%z{2E~ma>Q=?7s3$sWk z*qr#RT-20J*A&mD-} zX7dL+qx_J-#@gk6E+Wd4Xjz=rOr+pk?qH3lz*tXA4EeydMpyA$doNNbW@V)Q`}^(> zxyvsL6T7HL=OwaaI&k;yxIfHc>J#z2k7^*<>^ZMM82v7)^O>cin{=grl!#xF*=&M; z6phb+$1gFp6;5>(l&$-cmsMfj>~`~&uH@c%J1s0mZ-Sf-uQ`JlsY}ZGp*ymg8A}b+ zhbiz}&l0V})8tRKv1ueb$Qnf8ImnK;i@0D<+mh;43mctq^9%V$H88f%Y)V~6Efx0z zrB!D+Lk~lB=!U#T{^4|7cz)blls{2W6%%E-zzc7L8DM{o#6@b7OAZ0oJHZ*ANF)%Q zz;ncQQ8Up!7!)k;ZIbcP#lHx)wF~ZbX}tTt;IR*hNb;j?HjM79nx>1GuU-ennIDOv z*3*S$)JXbL|7AYvIci>Mq;Cx~HOfoly$UUje8$A^v?{m1RNs4h?qf3l0r^X3s<%sI z5*t61CoKAs$)S@@Aikq!kfrI{#@ISZxXLjU_~h|A0XLO}(=p{}!F< z{TwXr1~$AJxeM7ZSmR~ZD5{@dgOiq$|1@W%_rU(k$hn(9cD{sYsx*K4m5RCnZOjZ~ zA}@cM0XEkFRQf;8QWEgLo3J1cfM#_;Wve28>IvH_JNk6d&iqPbr%?{+958ScL&@2r zy-N%gu~ALutl&rO!As{7wJpRx#^>i+xhrJpPkF|CV3tRSEwh55*8+)K1}|eUv0ih| z{C$wWyyPQ~sW=?QwncHoX=JI{;L{Bx#xDl$&Dm+=hCSx4UvApcyzt?bT9&*Dus5p5=;F8dN6 z(SRNLzXn1KUwzr zJ^w2w-l#Zdr3-)F4ogm9K?)Ol~vP?NjiSuXy8VPu@FFsXKhvWu9*bt6U$v z{6{eD3GfJy@-w5!-g6TXWnf*S^PTc?9z0O_;V{E0aH5jC`0REqIQ!*@d>gZ>KZED} zP2Q3f{Incr)&p-@LH#3|eP!XvCQx-q1-?3qeEKxI*^1gtH=Zghtm*N1^TvEql#$C! zQ6{xGI0qv*F}+xgKR{`JqfR}M-0{8b?=rLR1^CRUzFt;VegZs)7;tL?%6ElTYQeL= z1T8+xJ6;B9o5Hg!CWc-^h5ZUqNOTrgJwE+CCp$CVJIZmL#A-$7{zaKAnlq7*Cpg26 z*h4l_iW3+BrOv?f{y;4vIdA%cyLOiEpAYWdok~zt&15ELGZ(wkkP~@>Rf^bJXfIcR z?@@;cWi)5{Gmy1HSmPPoh9n>~Yq>WKv6fFcn?G`v3i8>1uwn(kAsqWSn^Q6eyz)QZ zKMUt%6(|r>E;%o0SkW3h?@i7|Ie1o#bf(0^qx^viUuUK_&I93kO7BWj*DUVjd#<>pOQW$Diy`8Mc z0(Bj|rFHQ5cDkF`hk-BL6N*zUu zHz2limJ_05kz3_PsdXBC;4|GDs)JwZA6fY?KzfW_k41WpzVHpY=)dNk?P1kkqXcr6 z9<<}cXb!Eo+t!nHtd0C*c8F8x-(Lc^o+x_aC1+tLXS&YezSPds*ty`{l>_PVTSL&m z%=bT>^|Za<9>2d7`$`|8p1)T%iYpm=D3Zq<*B%wPFayR0J5WVA*m>beX z-Bi8wJM+@Clmb+Cww-Shh$4}1b??eCYbCUnC;y-kJaoDmq0$^Ue?3{={!;>`dzB z(Fwq#s2%NwuQwUrFjE+>K6fiRZ{n6dZXQt4h(ch%>pzJ>Xa)!Nf4hsfo1b?umWP=j@E8QBiTIHd;WT!h zwRCBIC-aggHkGwdBq%0(#Fo`rKkkZo@ZmSk`$nRKLR6&p;$@o%E4!&uks^Az{`|v4 zCU*T1FP;8f3Fmnaf`h%3-oSt%Zds)>*(abnc|kzxpt;Zs{A@RC6P*-3i~pt;-+MDv zK6yZ&)StN{UM~5W%)k&7kAg#0U0M_u>{xwY{`+w=8QACUUZ5-8!sG2T z@RbKxzT2k1w@uYSpXh`qi3h$?idg>g0MQYdzQ$hhtw^)M1kRQ*K&G zENoBMzI69{PsyRis-FVrl?5te=Q@V!(jogPHtGlbQ$J#!<3wl2n2)$yZA8KFn)1Xi zwir2eKJ_0rqc5u6Lu_j=yG$+$+lum}GH60vHlt-LH=Q?5r^kSzSbN(vSK0g}_5*&i z9thwu>bDcQ!CPG@5TULbA69tpd$#-Ug+AsCn`avptWwf$a{r%>1yG+l97giBw(H*nU?67IQX`m`EIBj)^ z@;8!;7h@mn7Iyn{$M z8Hfv$>4|=rn`}m)0rWjg%y%{u+3I6DzK#(G-KE0&FMIl){4|Cr=n#282D(i0!OU6d z*8AmbdD)tnbDF6Wlbkqctvx{OiiN-%#iB&ij@Yh)E2X|rub7BEgjM*{Z8f!+t~k{8 z1oPiPjr=ZE5U@)+sy-3LP)WIAy3)10R?gMctJ&#-68(U06)45tEEoStoo&zs;KjwH8n zau+kAsG`DOe$4yTrkYokJ&yLXJhW0Y~3LM*T$;c3G+GWHdv>7Um>ulFcJaC7pNb0^RRh z!A4hu-q~3#{RD<7Hk-oHgb|cmHK)7UJq{-`rWNB6} zi5hB>dsXE8z;!zWMb`<;2FVVlS{UV=HRb|-WTQ934Gt9`!)t4b>3^w>{wN>If$^FV zH>YufO?68bvOJ?g*!z*{OL(hiZAbBE+y(kn&S-%WXJuwXwlE+SirW#*~cDPBgBnjG>M6hb?(yJPInZftmiX{T@7 z$0|F%dAuvge2B^RbGu9bDer&)>>`T^!Qlvy-KD|%_a!@cWOl}Fwuep73?PSX6v-%x zF`oskdUr5*#%4BCS@9-JKv`;-)PUvrT5r&2L=Kb9_CY18u6stjIFhcStTLguo4mUl z9DxedS|;q_de0 zg_glqL@s)L43!Gz-9B>j(RlAw>YN*sxF^ikFmq=gi}=JN zZ_s3Z=FQ`VtqkTsJ@q*1sQ(giY^H)=k6bx59MDAesIY!(_`0Qauei@8w}xDKCbx7e z@yVa?!+VhL#`^ix7SlCu1YF@oMB6XGd~=Xne}{g1JMqFR#$=7X{Ooizgkyq@#R_i0R&8KamJy>jffvx42s6lzf6D&9fSLHUyurDL-MRi zJj($-?Ck^DKI=r?JxWhD_umI1KgR}WP zXjoaS-dPZy>BP7{P;cl4lO>9$mIoIY!E;Up8>vDB8bv<1S$L%KoP$%uphtK+#VI0w zCKA2H_ZH%lB>CS(GQ;^`@_Seb4Nei&a5&5RH02X+v!Wf@^@aRf_5a~h!$CBzf|^Mp z=12BtZ-*MH+vC~m!cnA`|DEQ2a*}C86_ld0g@3W8b^L=8dW>tl~SrrAG9K-^d245$)TNV29Wb zb~nn&Lyed$-~=9ZF?Y~_b$CRtzpzX5I5`u*bE3NVJN}0t72+Aou_n3`~2Va1ZWClUstpozp+%o>CBKa<;e+Lea=w~VK`1Lx;UBFN5Ir^Ku~3?mp{Q6~0dZfP#!wd^qE z=5f3Ffzg%ZTYF<8$Ai;6V$~P%z2ER#so)ROdj{HAmk8ru?j;imSfje&TPHa~(azmT zoS~wevyz+)qi|}6 z>?nGxO`&7Vd7H}TOd=OQp!$hoW|9Jh(~Dp%)fIK{8a4EE?o(Et>-K2hg!CT+Ai+v0_qn#6FjHA^UM?(2X6B~TX3H(_6O|Gr(&<9 zSIQO#+kI_!!3S%O?cQK_sLFJZb@YDnT2b3MC&!>D)Dc$SDp+I9nFsht`8J=O!u$t= ze^_FhtN2Wv%O|5+-{oN4?1m|HL-=^s&zzzrlGCHV%rd=;K7!09w>`t;iMsF+QW8Vv zl53e9_X+&3~MzBknG9uO^X!UY2>hcGO_%n9=HQnaf1v zNO}-cq2zjByw-mm#y*-=WWe`y?k;-A#tO1KZZst)o+KE*)U0 zyd%`wt}*BW z$JumP{1)P#IVrQiPpGZ)*o=A<)+`HWV-M<;d+Z3%^eLhSs!~bh1UW{=al)C_@IR(- z4*WoVky+J(^^jWbK;LmB9k|B=2K@IWZ}kc*x(G!07*Q)V9GQwb?o=Z665x_XHgvJJhcSAD ztS8Q(;F^?JA%VT=Lb4+I$?v!c>!}|-p!e$$GrETHf5+-$IzLuxj9HJ`KQ}8&46Y0OPb@5JjZ7=v1FTrua^-da|)``0_?n?bqSoi-&#; zl~VcT5bp`PSWE<@=dQQwYfEuw-@`&ague1h(^e1un3^8toZ(SS_+}P6$ii0DNzGHs znWgvxI+Dp$xp33)d6C2>j9Wm2Q!%!roGZ_UUYN$Q^I@2*mrc#r3FC!61~MmH>}~c2 z$a$uQe--aC8_T&IYu=a|R#a_pyPw||;RoSNkuh;oJY^e@K^|~%wzn8#XPO^iI1~sJ z^e*{VsHJ=#i4pbbOP+%TPmABFLA>Db0OMWCxFvBH;q!I1Oum_2+fJUUdSt_@?uNwfNAc~zWz~KGdPr+8fCW7 z9P!e;gg2GcPKo>7gwe5{o~WI71oq6IbR46U0`Gb*d;6dGEgXV#`I9+IKb4|y>6v;H zc_Ky!3P*0bWI7SEX$pEvgP(=g$}435$-_&;DS zuYz~W>;ff^v!mq;zZ$)BZOIE$+CAtt)S<_?C9_jYn0ib~{pdD}YTm!5N2FPJt4wLD zp*XDEis1Knv=3lXrQz_uW5r&(jqvRHQ#B08bo5IsAX@yO=YxPew~bW`xkpywC(e^2 zYv$d(1Pj_@3wXJ_nmQ4=fA2stmyxdY&US2IK0EjwXY@U5{1Q);3=jCNT}7;M7^S*l zZ{NN9$FJkR@yi8Lxwdw;_q9H&TZmioF4@O3PI4D;hw9k;+GeU<;I_jp`krTLMTUxw`nP9E| z#a44oKkSM-0=K+x@TWi@s&2dDg6LGVR42qyChB(K%&c{aIWI3_YDV7bm-1)sO9nL^ zzD7ZOU|k}-hrDqo;+5+3EB+=me6|eq)PAMTnbYPX_NM_X&uysj#-}E8lsGsK8BqhG zBuy2pA-g<9&xlOdXW2no;*!lSuW6;~)3qKDBjXN}nYW55f}e^iyQ4>? zHw7k`lrdW*e#stn>GVo}iiqb=`H(K&125Lx4UJIQT&>`HbRTk)MbsxY2rzBAIfz&e z8H`*Ii)}JG*ejVN7KKT%KX#BE8YuR@kU!lu`}l-9cNg|-8z(uc+?tS#C99Yl+d2G; z?v0Ln3ze7tyMx?2jjaq52@N4GZbKyb#$Oxh?=69k+dzyC=CFT&m@33`H<6t<-6v5? zQPg8#5u0(Yk1Pxl{>o+apMh6)f|++a*aUp(ufSL8AJ(uAeeMHo0l6A%q7ISud=QHp zCgg(V20r9*qzjh2hkYl$^y0uVP%x6uP3i`euyH>xjKK^Ce>OWhmY5k%=FU9yeICTgKl**>z% z-y>U!i~bQ1uD|sf>W+axGnw35A}@NK0w+aw)zRdaSwo!zGreVjSL(id8QE(t>p#s` z_8f6mYEFFzx6Slrg3?bYt8CLJh%)lRY_3Rra-A6x2f15Mu_e3W66y?OS7qcfv{CMO z)zop7j$Y*&-Za=w`@G*}O{S@xWTse6@X>Uf=;U%DGoY7K6%0nQ>$9$ZRoORXHh3_}TBtB7-v0#^xXlT57H*|4 zDya7ULGXg4RMyWE&u_;@2k`n!&2TfqrZ9D53RxSzi0V*OTkAX9O4b)`S&eaWp?dD$ zjol#Mix_HOW$YV#RbL{(72KKWc$iUSrF%YRdjI?b%KJpm1JrnRO_hi@mI;fuWsqLs z^6D&F@|EFztaPpAV$g!FvVb}-Z!>GQJ7;qcC`{8x1yw+l2}i;kKOFZ;Fl(u!jo1K4 zdl~syIBK$`z=BGP#ilp4j^b*oYVIu-cl|;BaYGlgDiIS)ug^8T+ux`L1z))mW-`c) zN9S^{Q1ZZM-g&(0SKcqS1t%2lG$`?S-efB?Wp>EI#GNBlchJhNHbbzpUjycHDNcPY zeTMkrd~6*4xij8AKR#-YSHg~sT!c*+->i;wGF#moTMl;U8kAt0Gd1HMxDqL;?)??= zQ1CwjA96Y~(O1X*>Ys&)cf;f|i^SKSCtTz=qW4k$zudW}!J;y|N#g$_PRG_I3g}E! zxW^5NMD@V-@m~q*(3`)jDVi9RyyU00zkCtMr&fFQ?8tXbVBQb(KJ{9NLh7Nai5kfb z@z5j$!`eb+dl^{aJlKHWsVS^reVIzu*$|GDjDx7q~U z+5%p^Kr`xX-_iXtz`S&?sUxhCFI^6zjK5qZP`!eWLE`#jUVY2^xY2TW)%>R_3r_C2@*Bj5xu3z=yx)kMTZHD6|uuNIMEStHQY zyQKz2QZYTGv;LmWvookC)HRRgD=({Q=?b8^-$BjSi@@T#5~01ravTF2yUFwo>Y@5$ zX5*oBm@m@n(t)2G?!VqrnH( zFjKHQy=_fF9LmerYFF?Sy&S&$&wQY_h>Az}N1F`gh0bIf$B0H6*rfD+{R+$Gg#Fo# zWL9TR@UFt{BByjdCvvLXN*8%ixO*PGr#GE6b?L_W!yjuC%bsGi$xKJ-r&K%6+GFsS z63X1fF;gsh)qZ*~t{zNKylw}YP73yhnd)bS@7PrqWGd$m_^nTvE3<^&)UU7sodady z*4FikcsKRp;21g9K85$aT#<8-uNt!71285pTXdALU{yqP!f7XNP1QdLV&6kCr@m}lZv z+-8uhQzpB7z};>qhncM=h25-kiyfjik)QG>`n~O7)M{F=%QdLWRYHMhG&S7XUQ73l zxlMi>?F_#z%Xwu~IXlL{GLsL@HX_?N)SkM!U(l*LXOg-#x;y@KCwD!sT1wXSA55hp z#2BZk)I?Q(U-?7*J4|~m3D;zs_tAw-wDaONCp_9~KM>6Q8RxZz%_2keJIBb5c8vZf z@QuDg|8hGbfp>8?OjT1ncDSG0?F`KWmFpLr;p&F^ng7rS%&HYpT3$Wdje@Cn%Y99* zk%UOK2q;od?zW~2@o}W0j32%UCV3>>g3KaF&XtA8ekj=2c#GDu?W3F96<#~r!`_93 z@D>(Md$}5P`>Okme(WDqfNrYn_PUB{b$%3y$;A6c**~IpFg`uJb+m7;*+?W7Wrx1x zSA)rF^Ric2qq-+B7nYDO_om~m1GsB(F#9j5Oh>yKa+AI6AP+1_jOcvzC!q zI=k1Lo@W^-E7q!?sg~tHFMT}-b_I7y@1-?iv36pm`O-X)|LTj(SWP9z5X+euVGv zkm#wf#~fbzUUE?1mgv$&*#gI;iq{w&nI3^&U`+)A=jjqDC)zTNbO1TaYt@;VpGEX3 zCBuv9iu}p%W^;3zy13G&7F;VPx`TH06yGw%X{Vd2FWaQqiUV)#jnzOQtMN__x5r7ExDf0*d<*<$^1Axp$e4fX*nfH>Ekwt zkG#pW+yBVlKadT6j9eEn<}-A=eiVCTLSh*fDzv{~5qH{*5 znszcA^j`Y0&Vfxcpym0AD`EbE6J&#Jlh!_qy%{spE{@p~xrYzDCNBm&f%;K!uHIt? zC8$i#`zvu8YC1=r1JCbg~D|AZa zro!|Lg#V_k>_s(mgCa>JKQTsZB!gFl3QZjEI{+s2IoAv=fho>JPTDo-49*ibI5$2# zxm#3&5>f#u&bxI16>E)p;&P(n(@a`x#cvLlOT5z{np>#>O-H38BPztx-3i;4Ij~vj zwEqWWQGq5kCx?GZ4d*qpb+?ksOk@tg4)e23N(aoANHw~6R>n@yOhT96kgs$`VJHQW z>QwleugQHfp&gZon7=o1?iexD#&Vb9yZdBE<+u_3!O=F6Zv?PIxv9R@)H_W_`2x1% zKGfyQ(IvBh^R^gehK$|^7`~~{h|GmYT0teOEpgE(kgF=#gMLKY``kdV`i^Q7{6Kg| zWOFO&A-%#+t%i5riSKHJ-_HzJyF6Gr^>*U-{X~6^*s3}|e;=GBs;}IfTe^w*=ysyc zxpdzRj??r*_#lrHjc03!w%ngFFa~znt)i}K183BS^BQ2^7gI9`*}r(&x-kD{*$PBp z*SJZwMKzt2w@V?ji>qXJNz7QcP&SpL>4Lr^6OoY?mksF^=pV=%e5N0Xbb%XksS<&0 zV8uJB#buy|ofAZ=IbGrr^yogqXgDFGvpWQH=l)ZbK`N&5Oz&aipSG>3Q=OsXwTzwQ z%Avt9hP8Rh%~(bjJdrx=LoDn^?IiwhYWg*s z5|1=O!?Bh4nU2K}tkU>YSDC(tB5NI@%Ui*=fzGh+mcii*$2*~VfI_Di^YzupZe5QU zDlPNr=pGmInM`n#81pWX#iz{BY6L#_mY9nf&77lU_K7TkR@-;M=Q1ZMXzO5r$I_Sm z+{#RaBk{lNr*M%3+gbEf7o1->fNIJVl$%DK)=CAkY~d`8TW|F~B%f0zccEnOKI z%t)pNqpe9aF4P2=;7L^?W9bfCkAB~moV|A-AC=@*bB_ zMM->WW~O0W)q7Zv$%ffQaP^S89$_ys$Vp64ye$@>WVM$3>kWMR++gwFnYmO+ z^XRQ+n^%!4OkJu(7F=>4we{JYpyqt{U6A7WrXN`J4j4LJsR(_bViBEa^s~3cuF;p` z9RlrX!TBC0ce`Z%9sLi#y~(aJUr@Ol0-tM@JqrR^9ftB=nOmlEM`FUd5uW}HDwo?q zu*QmfC_AK-f4C)7bRO}y*_clJ9aGWDxdo;h-aV?jGyQ*FJ-h4aCG}#=IkJr;AXw*3 zD*R?@a!7|KuTRzdCOFtv-XYaTJy9XABpB2RIgDB zCt#VUC2}d~uBqqZBAxHsMJlr6wW^2rvzN=AQN?6$UCzs@exS0r!tH>kJ6mT4HLD!H z;IL-YN_}imd38{J?S-*&dx=ppGI?dM+eB?~DctdAd@mRjoDfC7=Q#7TE~GxiQm3MJ zR@)}k*Wq(cc8$z@^Q{}{Cp2Z*)w*;i%oXKa7TD)L|GPGOy_8De6?4~gm$96aSJW66 z@-2<2?H3}(d5mRA0gkqpew32rml^4uI!LWQ%Hs$VcP}UZ><;?!Ei8aAw)!fS@B931 zbPmv8tl|pn=qe(SqhLkFdCO76B~xI+%qA;J13P3G|8|F%s}V?2L!zxk?A{%G!~;&p zZt4Krs9i*Lvkp-OuLaL#FW%!k9C)ToqG)c$AHfO_CAMusewmG@ zXv|7yf*~;i{J$h8ImC)Y*n4 zSlQZONMpeLqnhUZiNP*`#w{g+NKLoW9JFGHH?VRg;iNrhm$vYc=}p^Xu*WTy$cc z;!H&6Acy$OuXsylMDkt9V9~T>>I-q39b4<#11NwmLe4yzZPW0sMB`~OK&yWyWKVYh4ZsgL+`DJlaC$?F@l zr?Xh^LA>{U_$8~@sXTa@0eqig-OKX~FI_df%oBXcr$h+p@M@cR-icHuJ97@(fNZ>k zZ|1YY5Ag)CtY}H@z-G?pF3!?1PWB1b1eI3Mj#=z_fOi~CT_HPNT@Cr|?EFn0a)H~Z zQOtr7^8)YOfOq=QW8W&X#P--LZ0#m)@l`!e%H(P?^hV4bvN*PECg<}Z5zK50T-Deg_=4=Uj4{9W>b zt>*tK1$?Prn1#4lJPMq1f5{?hj&3Gy120Jp z9VxHCZ8NK0E~#uUi^~k4ty^6U`Dega3DFJM%(FI?v#45~=Z-{RYqaI>4zuRp_*$J8 zeQXNwkM$;_>1xu+e_7Fl^dmHqu`s||+GM7Jz8$YDtde*rC=3a-3tZw}?#J>*F^Mkt zAsA79zDSO@?OZ;SGtynWip%*ve!O?0d;HQOxz{b&65ZG=I>bSFO|L$_v~`NBSf{3xG-US3rt{r>cgyj5ShilNDY#Kh$ZJYN@!wDQKP`3iOtQPOW% zvM2TO-6qGlIW{HQ9wrtheMsUn;4^sBgg?PZkh z)jKeOZkg=fBir9J3HUaoNBh5-R$v4x=P{0F06lL5%hMD`L!lm zs3*(mbW|^Lij(nQ#BU@1^L>>$a1JK^2<8h{i)At*x$9q|kJrO%9J%SAexKGZG$}xI z3yDs$7q(>pR`Ltxwob%meI(jh2&R-7bYed5H4UYyAI)VuE%Gsf0+%Qh>f?v__i5&# z&WyerHA6gnCG`%whlUfFSSvK;mVr@NN_y8^@S|-%sG7uLf$6J<#$G6=bF)-UXbxsaeYh^t-4g6!LcNR?^dWX4jPr2t^ zvH$gnguaHi(kZl8HWSTUvp}i1B;H!`msUjXQ$S&|aZ6iqiw~LO_7TW%eX*K(u|3d2 z&)~1NUE-GO+tfai)5~_xf8;)An(0GovxDVJGLa;rnLJ@PVi#Ju<#K~)YCexN6hD9L zXxavop#XGFGFMl4N{ZjRYaMDZEKF0U!}b-day+C`qooFE*-R5SEPe;_N&V%sv8Jq}HoiZU?~LqmG_ zD#B%rkB!VkzS)gTrjDP$HJAG%o!vm4&=0~~sDZ_gs)-ubYB=_;7M3iGFBwc8AFVRX z0Tr%lHC2Z7WVzM-Gm#@Oa}L?LWPRJnO|zM4X0y6yKE}p~DyD91w!mGT z-ZSS#lFMDXL*%gD5}p&e3zz1Sf0!<+PX3=ngx%Fz^8A8!joIWq(FcNk0!e~3q?BV^ z3%ga`$Mz^JeO@f$c&uqwy!KVU4eNZ^6_h==Pu2}FMakTDqQ=#O{`B7DRuj=6?Be<* zz9Y~0JN$Fxly}r+Y;|VN?rCDG{9x?9TcO+PwI5GVA*(Isnh|D(UMCN`?)sM5sz!w> zizA|()O4wq()(Zs4rER0pk*)#?(_ikD8DBHcnZpx!Icl5P>aHQu`d0=Gz*zV;*$JJ zjg+}jYKXWZUP>bB5>%7!iIV6iFZGwlowYUeR+V1;BmabRFv^`mF)O?5D*p2hqxRo} zxxd%!E3D)dsyaJWGx$p{dFmR(NxfAK@mF{RUhcF{jN`9Xc>+6`h!8_}?@vqxID-8- z8rdB#6*&}MM~$!o*yk-b!!N13n}hiF4yL3<69Yu-6ML0dsx{c*Q!AUk>1?V-PTb$nDb}r6L5bffvlz05$Hb~C6kWV>jszzdMLE#bM{|Aqz9P=p~ zgApepvMSA~{u@>uGo3u?jU%3UM&`JbJhG79Zz=}nCC(W}g9bFWfm}sJ@2;s7?jaMx z=o~MCSdxAEC-CSuvXXrxuVHCZ27~|eq?z$T?T7}xA*xtGZII4(sw9(9&}>W`dsVja z=BfK~xcDV}6eY1&u{X$;W-yC%+^?AJC70pf^Xgs(mk^De_L0`Ak4#41T$XB*p@Opp&fY_D(f$Lnu@0Yolsdp5H`E>u zJyD;50_1ixY#rGhWd8zDM|2KP5$ZYL5viUahABwS)sk9M4LQ{-h9~@#|7L?vcAdCk zAD@4KyVu9Ynk&pLDGht$GN*oGToxx#<47I(i#xmugs=s3zx&b4-BFgo7NnB3>18NR zWxOPx2P#Y?juIit=1D~ZlSJLM)lC8uD++pX)Dwo#uX!AAb(p@{yJQjbVP`*c>*?XF ziJnYHVyd}JTHk^1p9yQ}xbgyRnYr;$RuwhfKChlE;XZ@OkXJWD@uVV(&KX2r^O}!8U4vyU(gklifM%(OlESDAZX_{Zg>+S zj1WSdy_yFZ9a+j)h&M-iraMdp#K^dr9fmfWf;k#JL@!Fld?`#JsT zRavJvnFgeGkzW9PwFI)07!+7$7rJaVy~*J3)buKw-L?m)+z{~CK9=5O&H?O9Yy(r4 zRSQ@VK5;(<^1~+Ob}XKt69QEQ71DOXNOr%Z^8TEW4iUSCS*xulrrTug-(Ig9g~P;@gVnXEJaLo2LhE3?n`K}l!^5%)51w>o5-tBGfx+Fs#wvZ!uI z^x}Q1tNp}H$yp}BFstKjmLnrA#58?GzwjgBQXq@*>B@P9 zI>1!9+LqNR&EKX!s84F9zXb5$FJMmBC;FO49$E<&=v>h+@Q*CQSsfz&QjZjRSy<46 z<|th(_4F!x=HnR?2D!cGUIv)csTzg$zZ+>YJHp0M(|u;a`3$zo*EA ziQJ7gUVXBWLsVbe*%*4I3;MIj=T~7ZGm>=&qun=H;L9)%`jCarfypu;mpiPV%6)1)8qD>GPNxvj&PPB0DVhFA(N8`1CJ}8E z7G>oO|E8Hm&H6WZ1Vic8DB`~0nXbbN?Mx<}4c&wZuokMS#c*lE#5)z}UCWIULtlJn zBiM_-V%v)mD<%dX$VK1AX)}_H=oAs~7w#Bc9DP8@CyBef_j#M#?02goJe3zY(6m$gR8}t^T>Udx z)Fg5|b&cxq3UQewM~5s954kNpcKNIUnKI;*g&sS%t)2U*yY zRF>b{4R#2b-c7uCdGeN65b81Vg8ATX$=do0xEAMZc7GN92lYS%-m%70@Ok86Vp(7( zx$zb6cXIk_+?GN9Sg*dfCz8&s)YDm~GUgL6m8@x(!anlcE_2(>*M+>cOmi-vDw)sB z_x{&$XUT-3+L+7mf;Yhql9A`#1e=VweW0R)i6&z3KvI{Gxm5MYH_V)Frw_-X$Yb}T zkTns_&-2vPmx~Q%3Q81Pi6BQ{+lr7k3<8I_L~ltsb*vHw1Bt^~ z1INADk#3PqaQnv+k^G20&>lMx)VD4vJ4@r=~nX6>&M}!XcSjWR5FEc05gXmW!!6brvtwSEdGRh5~wz*hg*Z zysjg=1dobXG2W{$%EQcAZMG91eKc?Fzf@Z%igWssn5*K+cEmLy@QEC>h#IcK{FjfL8eL+@n66oX+G7iENym4c@QRdRf#ymN!fWIZ(|s zZK(NGqT{6;h*$~Y(^FVk3o^Nu`gNDU8JSj2LEZcf`k?`_&;CZg^ho)ZwbhbYJDm6J zssZ0$8jg`8lf7q4W1V*}wKmI+_hvVq+XpHxHX{-2-%M2*7#@bka~szWUQrMo<^pn))npY(y!0qMH}vX78KUMl zw5DCSAhG)f-OS#hkE5H^@_<)>HBQe2$ZhVL?hQ|G8cf%}VU+cAm#8=AklpF{`^O8Q zfcq2{+w_1{wXik6!}hr8*JMAg=;E?42gsX_)sZ@3{rRIVgEtK!f6JD3P}| zG+8Azon#7;2z`_{Hlt09qR_p#eCYGFG(+Iw&*%N(qOrDmtm+blr$7^+G^piRJmLen zPA-7q_A+JwnNbtJQ(Tpgzvz?xQJ2U(7ENR#dLi!FS#b^h`1DuJ15G-p`|6&N_i}$@u`dv7YQwNP<^J@u&;gKM#z(z-2Q}D5U<*CLMdx7whZ4W< zfm`pP?Rd^R!m~&0Y~tqI_KHYg%173FMFU6uz4|9NgZT=bRS(@kwDHD#SWJ&*eQMbu z5Q%rPP*mj>OMOQ)Mr9x!DBw~mAUVRVBA5M^x{Ya$XSzp@d>cmb5^&jQO{W~UtpSlp z2)1SlDlbtscy=PxE&uCP%J$tE=jy96Jj91&?*7(4^jMEQqJ1Y#FQUX@p6 zu#?e%8U}`2!HqFsy|@wf3PiiyBv1BXm1;U zSL}#Zp#weDO8Qt*xc^L)v3pbgK~*3YYgK@^Th4nXBj3zN{k%UaxTV!O@zQH39z>qf z0nrK8{WRyf=JsbTWkR}OIyg{cZsG%*J5ml${v57aZuy0}8E&SU>V{rb5$9i|8h?>4 z>`ip+^fZZNh#8Yd;6hKq-cPbm)MA+#2FM8cn^#DtN7J;Ly(MqUq=CMn3a|tdsnIr< z+81mmtNV%Yz2$@5&C>92?(co{zqX2A0rae8M5h87E+Pu~L*Zcz^-J03{uBK+kY0bo zl(y2Mk=h2vTKDY)@0jno*K$8^+t7XsS~wHbdpVrF6mq;eB9E(=WQzec4mFKW)H^eT zT)Zb-gdJeXKfBNUFWB|XD6L%}avDx2?IoMqn?|JG8)Pnt+ih>_^rnCg=?wnEC|4lT zl$dm@yN%*VYy9_F{MKS>NV&)d(nl2%WCiZs7vSK<0kVO=zmLy1MA zSYSDufvVybnJ|7*x^jxqUtGiqV&~1Gqdx~m=V1N99*pFo3o5UuMV?XAmW*O%#LTIP zS1<7sFHltPhHBmh7Y{9`NbD@0ZM^DFb#XSRIO>T^Z-}qTGWYkWI}k}H*P-i@N8GW+ z==O}$r`)LE4`O&+KW`!#c1fMX?Zt0L=OyhU#yv@vvzK^xg6T%>nl22r-1PKfz5HHQ zF$xY|OL`9O;}u__LX=XLl_|l+Hmkkdw!`Fg!)8 zDR!NyjHh%5RY@xH_wdmzK@~b<X>3^Z3lO?88EK zGDw9i6ZYq(3)}nL@@IIaN8ny1K&K91J#NWTV1U2Tb=B34_NK$4s_u6KE&PRAQ4Y}0 zO!6!Fx10+5^0;|G)$tc>>&?gt@~%>11AXq%2^l|ARG`TsU%V7v5GwWqL z{83a9q`$aCu5uB)q_rHTi>Md$DJ|pftTtZ5b-*@7^=TB=X94|$pQ<`?u6YIzVjXdOWm$%(CnfsH-!i%Gyxl^l>o58( zJ|{C3ko;ayF2i%RG!wyMsd?J=RLKue`_3cD@vsj?Z6adY>^##2)deI}hyP_x@a4FY zk*sjbdUz?}!1WRpyv6D`e3vQyP}@{xvw7@Heu}(YA2Ks!0^+JR$|vb(0x8G4lr?eWu&&N=HEiMDkpyTye%OcitKQ#zr*IV^~SfjZ&z^wyNhIAYOHq+?>stwN9cOb3zjqjCc%B`)Sc<{{13x1?<`oVhBhBqb>!hK(a!AZ#(?v|RDP=+G%$2EBGyrk?l$!V)R`5r$ zlO4GM0(MM~cPC+a4S`)Y((W+t^<4b$aH^kK)J44O_aKI|-8-hNR$t1W&LeGu>5vpXR@Zv%73E(_Qttzd3|yo-#Muy;Na;UM%f;c%0Lr%?0>+ z|3e=Rawak}Tk!pi&Kc{j96+x78Wwd?CJk31a+VtY%nIzLE2bJyt6ohmrqc2Yep|SB zZ_LuI@Cozj=gw~R^v6w=nMg}8^^`xXvtl}L%B3?^kBNh5m5xN7?^0jf3fn0mmU~}z8!Uj{YUvv~!kdA7Q4?wmchIZnL#KRz z8Y)uJu@_IF5gCIectMxJXfrHU^Pvg9Wt$N?VUOjP*M z(N>I#iL89T8m#V+2X#E} z_-0Z33i6duw?=BabyD*=*Loly3IxN@=4rWEcWCGV-tWU7X)&JKTfGh% z{>%;#cYkUrD6$8&Wm9;ETi>*nXHO%-I1N8$1rVQvKM-J41vbZc+IwQID}nfZ&OH~} z?+AT;!6MoO27fyddLr1w*;98i+ykB06S5V80Ko+WYB8_3%belmskAL9gr!RCD4Qid)pKe&wGEbb1M z8PIq9|6f_s5ho4FQ2^6Yx z&Nc8#b#kR{t({vT_QCm%R@(%=bHfQGxG}X0P*+x>58<4l3towU8n^L{AmVaskqoDZ zkL=(s^SDDHa1+5#nqmhhhTGi9MoWN>J7;Vq5uX86BOc^)d!XP~_*#+RWd=EgmQc`F#6;kc-#xDuOLe*!6ETE zl?|5`2a4l&Oo7)VYk3}E^N z)xFjE#$9al^vs5Lr+GEvcQfG5AxO^ZNb>#2m_k$sl8@sX-@)E>&V3EqN{enN#A%pG z2n`NKmd}KH0?8uJ<=lCZ{XftYA#iI5k~J%l#`Ivq%#9Jd#`U3Bh z{5%sR2{n0otpiTunYKBAvowRpYyx>y^ST-htu|=`e`rJR2b^*jikuW zTY~lh*KVPi}EukL0v9;s50L$K`=V9c1N6dMOtHnPogJAMhQD zl#Qjc;~D(11ev)S3Aqm~wuj9!`LR(HoIDGu@)B9G1pT9RByR~oN^e3+w_t8$G9xjX zwkniy1c-Fu2@W)t-MGz9%X6wA;P67{hbLXBT#0X=*QGdPRepMcI+l7+^D^M$?&Qww zq=HTx1BXud4n25&5wufkXdoOcJOp;#klpU2gCRh>lj9?bRuw(v?)KgXuFA46U=MP* zE!Zhe9&ViRm`N`-QPgjGDO44UJZue&qruDsr1%8zdIxLwhpq>;=SGfS;8Snl!f4%* z{1tUhyzds(sm$lp@TczUOrl!hIy@gA?fC+E)ry$p7@pb6uz8LjsJ+zZI8ALdMH#qv z2GRLA{S>Ke605m~EZ7N8l|crUMq`CJS^n5IgW!b{$kb%ma|eLnEhPSaJfKT(K^xbb z<(r>%HfU)*GGhT2pj*ve1&J>3DxPxs=KLvyHfCBR{y=XuVgcxrsey1rD4$$`p7;nA zmVp~v5$y}(W;5WLOW2}0$m2$EqEm3L1s=2Vgw=Eg{p9zjuvykZ348S$qUUa%d^B|Y zKO{pWILnNkl?CE&^65;hD`yvouKCmvn?b13uHxf#J-rDT#yl z0v>iXd3pR{18bY9El9uG&~Y>!Aeqq850Ij64fJzhN1q6F6t{r;QP-w~M;7tae$d$m zsB;(8^#PjtO_ zV>W=_f7b($r_Yh~N3b4pV5O&l=Qn_t3`n8=z}l_2u12&x8MIJ?pX7i)%aDb503EOA ziSG9B>rOV{{1xrckO-4&X%*#MM|t}L@U@Zm6{aTm2zjaIP>TY}RFHE2oam83+*pAZ zn==F>gCoF4IeH$@3s{QBsPy{8W~SQ44)$#&v`znD9Ubc@wzX7Mu!gGKR#IyUfx6~I z9)hW!E3eCNi(z`4UKN)Y+d79aQoQ89Q{@M{zAq_rnM3hym(a~QA1be*i-^WX4p{`- zbB`gpO)0$a?R4s7bXK#g>4vdDj<%zTM7ez_zd3O)>Z2~BHPa4R+{-Azi;5}6;RN7O11?LQ|!#p&SI5A1RD$dHJx*M7P-YA^vB=V9{m3< z);@8;U)p)9zOX~%r9NhL1$Ha3Nt5vJ0b-Nh43D?;Z?ne|%W3Bf^c155I~jd9+r>R` zL(bMuJVnh%aSwD$W2Pr0x*^e)C%@}EqnTECN7OKjAzd;tH$8^lpAJq(Z=`=A6Dm4F zZ=LCf9i~rMsXbTiY&wIyWvz|Y6|-1J$^_%@^M|MQ$C_v>Zhs& zGPyWgppKK{C@MQn0ev7IKdXk2#>_DppUntd-L>toi7Mzg04L;wE-O z7}Q$;+4hyczonD5i@Hj6_HnY6TiChKO>VVo=>zJZ=NwjHK0A@oMO)&xZs9DmGpSk* z^L@#Tg?R8@^f_(L_*eW+XK+ycJ{}2^B4RmLt$GSMl{PXOFD#ZJ@k=)tmxf!?BY3Qj&K06zhPcghA zwECQJtw${OhtA7? XFIvcGk(EC{&SrbXr{k3jRy!sZtms=M( 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�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 qqO&}9GB;7RX>cZ+`Q*F2rY}gNva%Hu|3&r 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 J9KIoi{X4VsBj5l4 literal 0 HcmV?d00001 diff --git a/wai/live-conformance/avatar-face/identities/face2.wfi b/wai/live-conformance/avatar-face/identities/face2.wfi new file mode 100644 index 0000000000000000000000000000000000000000..21cf9e491268187212f8d0580a19c9e7d5ed5750 GIT binary patch literal 12362 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 Ag8%>k literal 0 HcmV?d00001 diff --git a/wai/live-conformance/avatar-face/seeded.wfg b/wai/live-conformance/avatar-face/seeded.wfg new file mode 100644 index 0000000000000000000000000000000000000000..e9a5f61b92ca74ddcf4cbe35575aabb7359d1441 GIT binary patch literal 5822 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 literal 0 HcmV?d00001 diff --git a/wai/live-conformance/avatar-face/seeded.wfi b/wai/live-conformance/avatar-face/seeded.wfi new file mode 100644 index 0000000000000000000000000000000000000000..8ac3d0d10619f820321afab19209ec81d7ab9d61 GIT binary patch literal 790 zcmZXST}s116op6JKsVt6+=6Y>#{TPrKKkgROAss~A|fJ+h=_>zgNSGm5!aA5e-(G* z7Y@A)DTLvk$u}ow?#)IJE-x%rT*Ym=ZTIN5+^!qB^`GCh@9E%cI{2Kf?ELlL^WIzD zd(FBp8M5<~bsp08ecHZDk*%8qm;$&z$^|2VyU&?mByj6C0badM(mW0%h`uHWfLSuVLrPcFcP@7A618u R{yKkx76Z(jfsxg{=pQaZvxEQu literal 0 HcmV?d00001 diff --git a/wai/live-conformance/avatar-face/tracks/face1.json b/wai/live-conformance/avatar-face/tracks/face1.json new file mode 100644 index 000000000..eeefb29b8 --- /dev/null +++ b/wai/live-conformance/avatar-face/tracks/face1.json @@ -0,0 +1 @@ +{"identity":"face1","keypoints":[[[313,520],[548,495],[312,510],[547,485],[320,519],[556,494],[426,622],[360,746],[573,723],[457,725],[464,780],[520,892],[304,447],[539,422],[196,594],[773,533]],[[315,519],[549,494],[315,515],[549,490],[319,519],[554,494],[428,621],[362,745],[574,722],[459,726],[465,770],[521,891],[306,448],[540,422],[198,595],[773,533]],[[318,519],[552,493],[317,506],[550,480],[319,519],[552,493],[431,619],[365,744],[575,721],[462,727],[466,756],[522,890],[309,448],[542,423],[200,596],[773,534]]]} diff --git a/wai/live-conformance/avatar-face/tracks/face2.json b/wai/live-conformance/avatar-face/tracks/face2.json new file mode 100644 index 000000000..b4b98bf21 --- /dev/null +++ b/wai/live-conformance/avatar-face/tracks/face2.json @@ -0,0 +1 @@ +{"identity":"face2","keypoints":[[[310,501],[583,488],[309,481],[582,468],[313,499],[586,486],[440,612],[354,707],[572,697],[457,693],[460,745],[503,876],[305,419],[578,406],[181,570],[787,541]],[[309,507],[583,492],[308,497],[582,481],[310,506],[585,491],[441,618],[355,713],[573,701],[458,697],[463,756],[505,879],[303,425],[577,410],[178,575],[786,540]],[[308,513],[584,495],[308,509],[584,491],[309,512],[584,495],[443,623],[357,718],[575,704],[460,703],[465,755],[506,882],[302,430],[577,413],[176,579],[786,540]]]} diff --git a/wai/live-conformance/avatar-face/training.json b/wai/live-conformance/avatar-face/training.json new file mode 100644 index 000000000..d0e6c601f --- /dev/null +++ b/wai/live-conformance/avatar-face/training.json @@ -0,0 +1,849 @@ +{ + "args": { + "batch": 32, + "bg": 6554, + "c1": 24, + "c2": 48, + "c3": 64, + "corpus_frames": 3, + "det_pool": 6000, + "det_steps": 12000, + "gauss": 1024, + "identities": 6000, + "lr": 0.001, + "measure_frames": 100, + "measure_identities": 12, + "measure_only": true, + "preview": false, + "qat_steps": 12000, + "seed": 20261022, + "size": 64, + "steps": 60000, + "workers": 16 + }, + "corpus_inputs": { + "faces": { + "face1": { + "frames": 3, + "wfi_sha256": "42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c" + }, + "face2": { + "frames": 3, + "wfi_sha256": "d87d24a2e5200d7c7cdc83215a73b649f3ac8fbdc6922949ef211ed9dc1c5e1b" + } + }, + "generator": "2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6" + }, + "generator": { + "bytes": 645062, + "sha256": "2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6" + }, + "keypoints": [ + "left eye", + "right eye", + "left upper lid", + "right upper lid", + "left pupil", + "right pupil", + "nose tip", + "mouth left", + "mouth right", + "upper lip", + "lower lip", + "chin", + "left brow", + "right brow", + "left cheek", + "right cheek" + ], + "losses": [ + [ + "float", + 500, + 27.151752574920653 + ], + [ + "float", + 1000, + 23.85670919036865 + ], + [ + "float", + 1500, + 22.036643028259277 + ], + [ + "float", + 2000, + 20.944748672485353 + ], + [ + "float", + 2500, + 20.30609174346924 + ], + [ + "float", + 3000, + 19.73117545700073 + ], + [ + "float", + 3500, + 19.27394813156128 + ], + [ + "float", + 4000, + 18.907171504974364 + ], + [ + "float", + 4500, + 18.685619831085205 + ], + [ + "float", + 5000, + 18.38530214500427 + ], + [ + "float", + 5500, + 18.223270933151245 + ], + [ + "float", + 6000, + 17.99156442451477 + ], + [ + "float", + 6500, + 17.791475742340086 + ], + [ + "float", + 7000, + 17.70211919593811 + ], + [ + "float", + 7500, + 17.477314556121826 + ], + [ + "float", + 8000, + 17.357114025115965 + ], + [ + "float", + 8500, + 17.22368954849243 + ], + [ + "float", + 9000, + 17.053622373580932 + ], + [ + "float", + 9500, + 16.95669003868103 + ], + [ + "float", + 10000, + 16.95623669052124 + ], + [ + "float", + 10500, + 16.732215753555298 + ], + [ + "float", + 11000, + 16.718940135955812 + ], + [ + "float", + 11500, + 16.66236025619507 + ], + [ + "float", + 12000, + 16.619711278915407 + ], + [ + "float", + 12500, + 16.50397487449646 + ], + [ + "float", + 13000, + 16.443286348342895 + ], + [ + "float", + 13500, + 16.406625579833985 + ], + [ + "float", + 14000, + 16.323882095336913 + ], + [ + "float", + 14500, + 16.244671186447142 + ], + [ + "float", + 15000, + 16.19172692298889 + ], + [ + "float", + 15500, + 16.105244045257567 + ], + [ + "float", + 16000, + 16.118682167053223 + ], + [ + "float", + 16500, + 16.04195178794861 + ], + [ + "float", + 17000, + 16.04371593093872 + ], + [ + "float", + 17500, + 15.984518419265747 + ], + [ + "float", + 18000, + 15.914796007156372 + ], + [ + "float", + 18500, + 15.774532398223878 + ], + [ + "float", + 19000, + 15.768841537475586 + ], + [ + "float", + 19500, + 15.769487701416015 + ], + [ + "float", + 20000, + 15.730250566482544 + ], + [ + "float", + 20500, + 15.575015892028809 + ], + [ + "float", + 21000, + 15.544691051483154 + ], + [ + "float", + 21500, + 15.55491438293457 + ], + [ + "float", + 22000, + 15.502726543426514 + ], + [ + "float", + 22500, + 15.463130422592164 + ], + [ + "float", + 23000, + 15.428230583190919 + ], + [ + "float", + 23500, + 15.33780820274353 + ], + [ + "float", + 24000, + 15.333562908172608 + ], + [ + "float", + 24500, + 15.204111749649048 + ], + [ + "float", + 25000, + 15.304020065307617 + ], + [ + "float", + 25500, + 15.17666136932373 + ], + [ + "float", + 26000, + 15.189624069213867 + ], + [ + "float", + 26500, + 14.954974006652831 + ], + [ + "float", + 27000, + 15.089864854812623 + ], + [ + "float", + 27500, + 14.969446798324585 + ], + [ + "float", + 28000, + 14.968342918395996 + ], + [ + "float", + 28500, + 15.012799970626832 + ], + [ + "float", + 29000, + 14.964520719528199 + ], + [ + "float", + 29500, + 14.947222476959228 + ], + [ + "float", + 30000, + 14.867027870178223 + ], + [ + "float", + 30500, + 14.741253574371338 + ], + [ + "float", + 31000, + 14.733581634521485 + ], + [ + "float", + 31500, + 14.740610712051392 + ], + [ + "float", + 32000, + 14.66535903930664 + ], + [ + "float", + 32500, + 14.55187226676941 + ], + [ + "float", + 33000, + 14.551790964126587 + ], + [ + "float", + 33500, + 14.53164478302002 + ], + [ + "float", + 34000, + 14.464250160217285 + ], + [ + "float", + 34500, + 14.445624458312988 + ], + [ + "float", + 35000, + 14.430744682312012 + ], + [ + "float", + 35500, + 14.380697816848755 + ], + [ + "float", + 36000, + 14.359273040771484 + ], + [ + "float", + 36500, + 14.322196342468262 + ], + [ + "float", + 37000, + 14.275692388534546 + ], + [ + "float", + 37500, + 14.21953385925293 + ], + [ + "float", + 38000, + 14.24616582107544 + ], + [ + "float", + 38500, + 14.158753313064576 + ], + [ + "float", + 39000, + 14.219896963119506 + ], + [ + "float", + 39500, + 14.135765733718872 + ], + [ + "float", + 40000, + 14.085213237762451 + ], + [ + "float", + 40500, + 14.085187175750733 + ], + [ + "float", + 41000, + 14.176372022628785 + ], + [ + "float", + 41500, + 14.003269695281983 + ], + [ + "float", + 42000, + 14.009160997390747 + ], + [ + "float", + 42500, + 13.952764253616333 + ], + [ + "float", + 43000, + 13.969861848831176 + ], + [ + "float", + 43500, + 13.939196210861207 + ], + [ + "float", + 44000, + 13.935250818252564 + ], + [ + "float", + 44500, + 13.909332416534424 + ], + [ + "float", + 45000, + 13.85144864654541 + ], + [ + "float", + 45500, + 13.852508171081542 + ], + [ + "float", + 46000, + 13.7885528049469 + ], + [ + "float", + 46500, + 13.804462120056153 + ], + [ + "float", + 47000, + 13.818707866668701 + ], + [ + "float", + 47500, + 13.771334112167358 + ], + [ + "float", + 48000, + 13.746336385726929 + ], + [ + "float", + 48500, + 13.72332737159729 + ], + [ + "float", + 49000, + 13.725092630386353 + ], + [ + "float", + 49500, + 13.725937417984008 + ], + [ + "float", + 50000, + 13.647756734848022 + ], + [ + "float", + 50500, + 13.641091567993165 + ], + [ + "float", + 51000, + 13.656556690216064 + ], + [ + "float", + 51500, + 13.628758916854858 + ], + [ + "float", + 52000, + 13.657432209014893 + ], + [ + "float", + 52500, + 13.600390180587768 + ], + [ + "float", + 53000, + 13.657493410110474 + ], + [ + "float", + 53500, + 13.637611457824708 + ], + [ + "float", + 54000, + 13.569153196334838 + ], + [ + "float", + 54500, + 13.539488958358765 + ], + [ + "float", + 55000, + 13.579807476043701 + ], + [ + "float", + 55500, + 13.55737220954895 + ], + [ + "float", + 56000, + 13.569390859603882 + ], + [ + "float", + 56500, + 13.531196420669556 + ], + [ + "float", + 57000, + 13.543482408523559 + ], + [ + "float", + 57500, + 13.602288497924805 + ], + [ + "float", + 58000, + 13.532362588882446 + ], + [ + "float", + 58500, + 13.594776334762573 + ], + [ + "float", + 59000, + 13.57904026222229 + ], + [ + "float", + 59500, + 13.576265884399414 + ], + [ + "float", + 60000, + 13.620992069244386 + ], + [ + "qat", + 500, + 13.88184126663208 + ], + [ + "qat", + 1000, + 13.80476714706421 + ], + [ + "qat", + 1500, + 13.83170743560791 + ], + [ + "qat", + 2000, + 13.798523811340331 + ], + [ + "qat", + 2500, + 13.809308740615844 + ], + [ + "qat", + 3000, + 13.791981969833374 + ], + [ + "qat", + 3500, + 13.7376819896698 + ], + [ + "qat", + 4000, + 13.780694259643555 + ], + [ + "qat", + 4500, + 13.663243137359618 + ], + [ + "qat", + 5000, + 13.646981540679931 + ], + [ + "qat", + 5500, + 13.672169563293457 + ], + [ + "qat", + 6000, + 13.595416633605957 + ], + [ + "qat", + 6500, + 13.609622970581055 + ], + [ + "qat", + 7000, + 13.570122692108153 + ], + [ + "qat", + 7500, + 13.573381341934205 + ], + [ + "qat", + 8000, + 13.542006971359253 + ], + [ + "qat", + 8500, + 13.616445919036865 + ], + [ + "qat", + 9000, + 13.454367050170898 + ], + [ + "qat", + 9500, + 13.528221797943115 + ], + [ + "qat", + 10000, + 13.475721578598023 + ], + [ + "qat", + 10500, + 13.465976043701172 + ], + [ + "qat", + 11000, + 13.468398401260377 + ], + [ + "qat", + 11500, + 13.520018699645997 + ], + [ + "qat", + 12000, + 13.428632768630981 + ] + ], + "measurement": { + "fps": 25, + "frames_each": 100, + "identities": 12, + "keypoint_rms_px": { + "float": 0.569, + "int": 0.575, + "truth": 0.111 + }, + "mouth_keypoint_rms_px": { + "float": 0.547, + "int": 0.542, + "truth": 0.092 + }, + "numpy_int_seconds_per_frame": 0.205, + "psnr_db": { + "float_truth": { + "identical_frames": 0, + "mean": 28.22, + "min": 21.11 + }, + "int_float": { + "identical_frames": 0, + "mean": 48.54, + "min": 39.42 + }, + "int_truth": { + "identical_frames": 0, + "mean": 28.24, + "min": 21.27 + }, + "warp_truth": { + "identical_frames": 0, + "mean": 22.89, + "min": 13.99 + } + }, + "size": [ + 64, + 64 + ], + "ssim": { + "float_truth": { + "mean": 0.923, + "min": 0.8546 + }, + "int_float": { + "mean": 0.9988, + "min": 0.9953 + }, + "int_truth": { + "mean": 0.9238, + "min": 0.8558 + }, + "warp_truth": { + "mean": 0.7635, + "min": 0.3499 + } + }, + "track_kbit_s": { + "detected_step1": 4.413, + "detected_step4": 2.96, + "truth_step1": 4.386, + "truth_step4": 2.942 + } + }, + "seed": 20261022, + "train_seconds": 3324 +} diff --git a/wai/live-conformance/avatar-face/vectors.json b/wai/live-conformance/avatar-face/vectors.json new file mode 100644 index 000000000..5c773a1e6 --- /dev/null +++ b/wai/live-conformance/avatar-face/vectors.json @@ -0,0 +1,1967 @@ +{ + "capability": "wai.avatar.face", + "lowering": "face-int/1", + "inputs": [ + { + "file": "generator.wfg", + "sha256": "2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6" + }, + { + "file": "identities/face1.wfi", + "sha256": "42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c" + }, + { + "file": "identities/face2.wfi", + "sha256": "d87d24a2e5200d7c7cdc83215a73b649f3ac8fbdc6922949ef211ed9dc1c5e1b" + }, + { + "file": "tracks/face1.json", + "sha256": "d33cf7f64b54dee34f6a25635ab31970fe6ce96a1de1a77abbc8f4df0bd9dc64" + }, + { + "file": "tracks/face2.json", + "sha256": "6feb10c037d4b09b4fba1f23b9b8f35957248b01d2f4039f970bfbe7c1f5ccda" + }, + { + "file": "training.json", + "sha256": "464d28f404d74d8a7b3569836a1ea447c702ce7e0fd227195a729b4e2ab60744" + } + ], + "written": [ + { + "file": "seeded.wfg", + "sha256": "e1900a11223158fe4ba24f20d436ee69e94daaddad480cfca64e13dd9dbbc87b" + }, + { + "file": "seeded.wfi", + "sha256": "5767443c2fe69b2b1ea6fdd7237cbad192d58a8681d85f54b560226e5167b5dc" + } + ], + "segment_step": 4, + "forms": [ + { + "name": "generator", + "form": "generator", + "hex": "57464731010104000400010001000100010001000000f9fbc8fc430249fcb1075606e1026dfd48fa38004907b2002dfc23024705d70222054a0782016cfd56fc6603cbfb60f90e05affdc1fe45000205bc03b5f92c02c5fb10fafd0440fa81078ff8f2fe33fcbe058f04e9f8c7024efd4e05e104eaf9b8f88400d2068003e8fb01fc25fd7df92906ddfad600a602bb0522fb620700d00b00460215fccb074806350583fe6901a10526fb000802fe6bfa2a032b045e05ae006ff837070ffe78fb00cc0afe16f8de0744014c0342fb9dff0a029104180000ac970005ffbf00e2059ef89efdf806bb00910175fc00385d004f005501c5fc1500b5f870f8eafa99076807defe73036ffb1ffbe506fb06fbfefd005b021ffebafad60059fc5afcdf06faff57fcb5f83bfd85fd2502c5fed6ff65fa3cfc3cfa1d0300908efe005c47ff00c89a01007478fe0307d2fa99003cf90dfd16fff8f88c053302a2fec9fbba0135ff1ffd1201d6f8fefd100300dcf5fed2fd5907c5f93cfa05063aff5afe8bfdfc0754fb4107e503d5fb56fab2fb31fe7d015c05dd01b9fc11023d03dffb3904cdfcea04d8fb76067bf8a00146fb6ef97a003f034f065602005064ff00bcc7fe00680dfe009c76004f06ec023806a303280134fe8c0343ff9803dd04cc07ef030efa2c03f8f8d3fcba0572fa00384fff620398fccfff660250033dfd84f9d0fa2afc840404fcb806c0fe52041703b903cf07a0f99bfc8b00510135033400a2050a019b0017f8440596072ffeb6fe3cfce9fadd017b019d02004414fe00f455ff003c6aff00c88dff", + "outcome": "read", + "read": { + "k": 1, + "width": 4, + "height": 4, + "c1": 1, + "c2": 1, + "c3": 1, + "gauss": 1, + "bg": 1 + } + }, + { + "name": "generator_bad_magic", + "form": "generator", + "hex": "57464732010104000400010001000100010001000000f9fbc8fc430249fcb1075606e1026dfd48fa38004907b2002dfc23024705d70222054a0782016cfd56fc6603cbfb60f90e05affdc1fe45000205bc03b5f92c02c5fb10fafd0440fa81078ff8f2fe33fcbe058f04e9f8c7024efd4e05e104eaf9b8f88400d2068003e8fb01fc25fd7df92906ddfad600a602bb0522fb620700d00b00460215fccb074806350583fe6901a10526fb000802fe6bfa2a032b045e05ae006ff837070ffe78fb00cc0afe16f8de0744014c0342fb9dff0a029104180000ac970005ffbf00e2059ef89efdf806bb00910175fc00385d004f005501c5fc1500b5f870f8eafa99076807defe73036ffb1ffbe506fb06fbfefd005b021ffebafad60059fc5afcdf06faff57fcb5f83bfd85fd2502c5fed6ff65fa3cfc3cfa1d0300908efe005c47ff00c89a01007478fe0307d2fa99003cf90dfd16fff8f88c053302a2fec9fbba0135ff1ffd1201d6f8fefd100300dcf5fed2fd5907c5f93cfa05063aff5afe8bfdfc0754fb4107e503d5fb56fab2fb31fe7d015c05dd01b9fc11023d03dffb3904cdfcea04d8fb76067bf8a00146fb6ef97a003f034f065602005064ff00bcc7fe00680dfe009c76004f06ec023806a303280134fe8c0343ff9803dd04cc07ef030efa2c03f8f8d3fcba0572fa00384fff620398fccfff660250033dfd84f9d0fa2afc840404fcb806c0fe52041703b903cf07a0f99bfc8b00510135033400a2050a019b0017f8440596072ffeb6fe3cfce9fadd017b019d02004414fe00f455ff003c6aff00c88dff", + "outcome": "refuse", + "reason": "bad_magic" + }, + { + "name": "generator_version", + "form": "generator", + "hex": "57464731020104000400010001000100010001000000f9fbc8fc430249fcb1075606e1026dfd48fa38004907b2002dfc23024705d70222054a0782016cfd56fc6603cbfb60f90e05affdc1fe45000205bc03b5f92c02c5fb10fafd0440fa81078ff8f2fe33fcbe058f04e9f8c7024efd4e05e104eaf9b8f88400d2068003e8fb01fc25fd7df92906ddfad600a602bb0522fb620700d00b00460215fccb074806350583fe6901a10526fb000802fe6bfa2a032b045e05ae006ff837070ffe78fb00cc0afe16f8de0744014c0342fb9dff0a029104180000ac970005ffbf00e2059ef89efdf806bb00910175fc00385d004f005501c5fc1500b5f870f8eafa99076807defe73036ffb1ffbe506fb06fbfefd005b021ffebafad60059fc5afcdf06faff57fcb5f83bfd85fd2502c5fed6ff65fa3cfc3cfa1d0300908efe005c47ff00c89a01007478fe0307d2fa99003cf90dfd16fff8f88c053302a2fec9fbba0135ff1ffd1201d6f8fefd100300dcf5fed2fd5907c5f93cfa05063aff5afe8bfdfc0754fb4107e503d5fb56fab2fb31fe7d015c05dd01b9fc11023d03dffb3904cdfcea04d8fb76067bf8a00146fb6ef97a003f034f065602005064ff00bcc7fe00680dfe009c76004f06ec023806a303280134fe8c0343ff9803dd04cc07ef030efa2c03f8f8d3fcba0572fa00384fff620398fccfff660250033dfd84f9d0fa2afc840404fcb806c0fe52041703b903cf07a0f99bfc8b00510135033400a2050a019b0017f8440596072ffeb6fe3cfce9fadd017b019d02004414fe00f455ff003c6aff00c88dff", + "outcome": "refuse", + "reason": "version" + }, + { + "name": "generator_width_not_a_multiple_of_4", + "form": "generator", + "hex": "57464731010106000400010001000100010001000000f9fbc8fc430249fcb1075606e1026dfd48fa38004907b2002dfc23024705d70222054a0782016cfd56fc6603cbfb60f90e05affdc1fe45000205bc03b5f92c02c5fb10fafd0440fa81078ff8f2fe33fcbe058f04e9f8c7024efd4e05e104eaf9b8f88400d2068003e8fb01fc25fd7df92906ddfad600a602bb0522fb620700d00b00460215fccb074806350583fe6901a10526fb000802fe6bfa2a032b045e05ae006ff837070ffe78fb00cc0afe16f8de0744014c0342fb9dff0a029104180000ac970005ffbf00e2059ef89efdf806bb00910175fc00385d004f005501c5fc1500b5f870f8eafa99076807defe73036ffb1ffbe506fb06fbfefd005b021ffebafad60059fc5afcdf06faff57fcb5f83bfd85fd2502c5fed6ff65fa3cfc3cfa1d0300908efe005c47ff00c89a01007478fe0307d2fa99003cf90dfd16fff8f88c053302a2fec9fbba0135ff1ffd1201d6f8fefd100300dcf5fed2fd5907c5f93cfa05063aff5afe8bfdfc0754fb4107e503d5fb56fab2fb31fe7d015c05dd01b9fc11023d03dffb3904cdfcea04d8fb76067bf8a00146fb6ef97a003f034f065602005064ff00bcc7fe00680dfe009c76004f06ec023806a303280134fe8c0343ff9803dd04cc07ef030efa2c03f8f8d3fcba0572fa00384fff620398fccfff660250033dfd84f9d0fa2afc840404fcb806c0fe52041703b903cf07a0f99bfc8b00510135033400a2050a019b0017f8440596072ffeb6fe3cfce9fadd017b019d02004414fe00f455ff003c6aff00c88dff", + "outcome": "refuse", + "reason": "field_range" + }, + { + "name": "generator_no_channels", + "form": "generator", + "hex": "57464731010104000400000001000100010001000000f9fbc8fc430249fcb1075606e1026dfd48fa38004907b2002dfc23024705d70222054a0782016cfd56fc6603cbfb60f90e05affdc1fe45000205bc03b5f92c02c5fb10fafd0440fa81078ff8f2fe33fcbe058f04e9f8c7024efd4e05e104eaf9b8f88400d2068003e8fb01fc25fd7df92906ddfad600a602bb0522fb620700d00b00460215fccb074806350583fe6901a10526fb000802fe6bfa2a032b045e05ae006ff837070ffe78fb00cc0afe16f8de0744014c0342fb9dff0a029104180000ac970005ffbf00e2059ef89efdf806bb00910175fc00385d004f005501c5fc1500b5f870f8eafa99076807defe73036ffb1ffbe506fb06fbfefd005b021ffebafad60059fc5afcdf06faff57fcb5f83bfd85fd2502c5fed6ff65fa3cfc3cfa1d0300908efe005c47ff00c89a01007478fe0307d2fa99003cf90dfd16fff8f88c053302a2fec9fbba0135ff1ffd1201d6f8fefd100300dcf5fed2fd5907c5f93cfa05063aff5afe8bfdfc0754fb4107e503d5fb56fab2fb31fe7d015c05dd01b9fc11023d03dffb3904cdfcea04d8fb76067bf8a00146fb6ef97a003f034f065602005064ff00bcc7fe00680dfe009c76004f06ec023806a303280134fe8c0343ff9803dd04cc07ef030efa2c03f8f8d3fcba0572fa00384fff620398fccfff660250033dfd84f9d0fa2afc840404fcb806c0fe52041703b903cf07a0f99bfc8b00510135033400a2050a019b0017f8440596072ffeb6fe3cfce9fadd017b019d02004414fe00f455ff003c6aff00c88dff", + "outcome": "refuse", + "reason": "field_range" + }, + { + "name": "generator_gauss_zero", + "form": "generator", + "hex": "57464731010104000400010001000100000001000000f9fbc8fc430249fcb1075606e1026dfd48fa38004907b2002dfc23024705d70222054a0782016cfd56fc6603cbfb60f90e05affdc1fe45000205bc03b5f92c02c5fb10fafd0440fa81078ff8f2fe33fcbe058f04e9f8c7024efd4e05e104eaf9b8f88400d2068003e8fb01fc25fd7df92906ddfad600a602bb0522fb620700d00b00460215fccb074806350583fe6901a10526fb000802fe6bfa2a032b045e05ae006ff837070ffe78fb00cc0afe16f8de0744014c0342fb9dff0a029104180000ac970005ffbf00e2059ef89efdf806bb00910175fc00385d004f005501c5fc1500b5f870f8eafa99076807defe73036ffb1ffbe506fb06fbfefd005b021ffebafad60059fc5afcdf06faff57fcb5f83bfd85fd2502c5fed6ff65fa3cfc3cfa1d0300908efe005c47ff00c89a01007478fe0307d2fa99003cf90dfd16fff8f88c053302a2fec9fbba0135ff1ffd1201d6f8fefd100300dcf5fed2fd5907c5f93cfa05063aff5afe8bfdfc0754fb4107e503d5fb56fab2fb31fe7d015c05dd01b9fc11023d03dffb3904cdfcea04d8fb76067bf8a00146fb6ef97a003f034f065602005064ff00bcc7fe00680dfe009c76004f06ec023806a303280134fe8c0343ff9803dd04cc07ef030efa2c03f8f8d3fcba0572fa00384fff620398fccfff660250033dfd84f9d0fa2afc840404fcb806c0fe52041703b903cf07a0f99bfc8b00510135033400a2050a019b0017f8440596072ffeb6fe3cfce9fadd017b019d02004414fe00f455ff003c6aff00c88dff", + "outcome": "refuse", + "reason": "field_range" + }, + { + "name": "generator_truncated", + "form": "generator", + "hex": "57464731010104000400010001000100010001000000f9fbc8fc430249fcb1075606e1026dfd48fa38004907b2002dfc23024705d70222054a0782016cfd56fc6603cbfb60f90e05affdc1fe45000205bc03b5f92c02c5fb10fafd0440fa81078ff8f2fe33fcbe058f04e9f8c7024efd4e05e104eaf9b8f88400d2068003e8fb01fc25fd7df92906ddfad600a602bb0522fb620700d00b00460215fccb074806350583fe6901a10526fb000802fe6bfa2a032b045e05ae006ff837070ffe78fb00cc0afe16f8de0744014c0342fb9dff0a029104180000ac970005ffbf00e2059ef89efdf806bb00910175fc00385d004f005501c5fc1500b5f870f8eafa99076807defe73036ffb1ffbe506fb06fbfefd005b021ffebafad60059fc5afcdf06faff57fcb5f83bfd85fd2502c5fed6ff65fa3cfc3cfa1d0300908efe005c47ff00c89a01007478fe0307d2fa99003cf90dfd16fff8f88c053302a2fec9fbba0135ff1ffd1201d6f8fefd100300dcf5fed2fd5907c5f93cfa05063aff5afe8bfdfc0754fb4107e503d5fb56fab2fb31fe7d015c05dd01b9fc11023d03dffb3904cdfcea04d8fb76067bf8a00146fb6ef97a003f034f065602005064ff00bcc7fe00680dfe009c76004f06ec023806a303280134fe8c0343ff9803dd04cc07ef030efa2c03f8f8d3fcba0572fa00384fff620398fccfff660250033dfd84f9d0fa2afc840404fcb806c0fe52041703b903cf07a0f99bfc8b00510135033400a2050a019b0017f8440596072ffeb6fe3cfce9fadd017b019d02004414fe00f455ff003c6aff00c88d", + "outcome": "refuse", + "reason": "truncated" + }, + { + "name": "generator_trailing", + "form": "generator", + "hex": "57464731010104000400010001000100010001000000f9fbc8fc430249fcb1075606e1026dfd48fa38004907b2002dfc23024705d70222054a0782016cfd56fc6603cbfb60f90e05affdc1fe45000205bc03b5f92c02c5fb10fafd0440fa81078ff8f2fe33fcbe058f04e9f8c7024efd4e05e104eaf9b8f88400d2068003e8fb01fc25fd7df92906ddfad600a602bb0522fb620700d00b00460215fccb074806350583fe6901a10526fb000802fe6bfa2a032b045e05ae006ff837070ffe78fb00cc0afe16f8de0744014c0342fb9dff0a029104180000ac970005ffbf00e2059ef89efdf806bb00910175fc00385d004f005501c5fc1500b5f870f8eafa99076807defe73036ffb1ffbe506fb06fbfefd005b021ffebafad60059fc5afcdf06faff57fcb5f83bfd85fd2502c5fed6ff65fa3cfc3cfa1d0300908efe005c47ff00c89a01007478fe0307d2fa99003cf90dfd16fff8f88c053302a2fec9fbba0135ff1ffd1201d6f8fefd100300dcf5fed2fd5907c5f93cfa05063aff5afe8bfdfc0754fb4107e503d5fb56fab2fb31fe7d015c05dd01b9fc11023d03dffb3904cdfcea04d8fb76067bf8a00146fb6ef97a003f034f065602005064ff00bcc7fe00680dfe009c76004f06ec023806a303280134fe8c0343ff9803dd04cc07ef030efa2c03f8f8d3fcba0572fa00384fff620398fccfff660250033dfd84f9d0fa2afc840404fcb806c0fe52041703b903cf07a0f99bfc8b00510135033400a2050a019b0017f8440596072ffeb6fe3cfce9fadd017b019d02004414fe00f455ff003c6aff00c88dff00", + "outcome": "refuse", + "reason": "trailing" + }, + { + "name": "identity", + "form": "identity", + "hex": "5746493101031000100058006800a80060008000b40053bcd752bad651b8d651b6d64270d950b2d54fb0d54faed44eacd44270d94da9d44da7d34ca5d34ca3d34270d94b9fd252bad64270d951b6d650b4d550b2d54fb0d54270d94eacd44eaad44da9d44da7d34270d94ca3d34ba1d24b9fd24a9dd251b8d651b6d650b4d54270d94fb0d54faed44eacd44eaad44270d94da7d34ca5d34ca3d34ba1d24270d94a9dd2499bd14270d950b4d550b2d54fb0d54faed44270d94eaad44da9d44da7d34ca5d34270d94ba1d24b9fd24a9dd2499bd14270d950b4d550b2d54270d94faed44eacd44eaad44da9d44270d9d1b325d1b3254ba1d24b9fd24270d9499bd14999d14897d150b2d54fb0d54faed44eacd44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325499bd14999d14270d94896d14fb0d54270d94eacd44eaad4d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b3254897d14896d14794d04faed44eacd44eaad44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94794d04792d04270d94eaad44da9d44da7d3d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b3254794d04792d04270d94eaad44da9d44270d94ca5d34ca3d3d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94792d04690d0468ecf4da9d44da7d34ca5d34ca3d34270d94b9fd24a9dd2d1b325d1b3254270d94896d14794d04792d04690d04270d9458ccf4da7d34270d94ca3d34ba1d24b9fd24a9dd24270d94999d14897d14896d14794d04270d94690d0468ecf458ccf458acf4ca5d34ca3d34ba1d24270d94a9dd2499bd14999d14897d14270d94794d04792d04690d0468ecf4270d9458acf4488ce4270d94ba1d24b9fd24a9dd2499bd14270d94897d14896d14794d04792d04270d9468ecf458ccf458acf4488ce4270d94ba1d24b9fd24270d9499bd14999d14897d14896d14270d94792d04690d0468ecf458ccf4270d94488ce4486ce4384ce4b9fd24a9dd2499bd14999d14270d94896d14794d04792d04690d04270d9458ccf458acf4488ce4486ce4270d94383ce", + "outcome": "read", + "read": { + "k": 3, + "width": 16, + "height": 16, + "keypoints": [ + [ + 88, + 104 + ], + [ + 168, + 96 + ], + [ + 128, + 180 + ] + ], + "rgb_sha256": "9c4b416a911ec0e294cf51ce1606ec3f49b0dc78e134c583d800180d85b0a153" + } + }, + { + "name": "identity_bad_magic", + "form": "identity", + "hex": "5746473101031000100058006800a80060008000b40053bcd752bad651b8d651b6d64270d950b2d54fb0d54faed44eacd44270d94da9d44da7d34ca5d34ca3d34270d94b9fd252bad64270d951b6d650b4d550b2d54fb0d54270d94eacd44eaad44da9d44da7d34270d94ca3d34ba1d24b9fd24a9dd251b8d651b6d650b4d54270d94fb0d54faed44eacd44eaad44270d94da7d34ca5d34ca3d34ba1d24270d94a9dd2499bd14270d950b4d550b2d54fb0d54faed44270d94eaad44da9d44da7d34ca5d34270d94ba1d24b9fd24a9dd2499bd14270d950b4d550b2d54270d94faed44eacd44eaad44da9d44270d9d1b325d1b3254ba1d24b9fd24270d9499bd14999d14897d150b2d54fb0d54faed44eacd44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325499bd14999d14270d94896d14fb0d54270d94eacd44eaad4d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b3254897d14896d14794d04faed44eacd44eaad44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94794d04792d04270d94eaad44da9d44da7d3d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b3254794d04792d04270d94eaad44da9d44270d94ca5d34ca3d3d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94792d04690d0468ecf4da9d44da7d34ca5d34ca3d34270d94b9fd24a9dd2d1b325d1b3254270d94896d14794d04792d04690d04270d9458ccf4da7d34270d94ca3d34ba1d24b9fd24a9dd24270d94999d14897d14896d14794d04270d94690d0468ecf458ccf458acf4ca5d34ca3d34ba1d24270d94a9dd2499bd14999d14897d14270d94794d04792d04690d0468ecf4270d9458acf4488ce4270d94ba1d24b9fd24a9dd2499bd14270d94897d14896d14794d04792d04270d9468ecf458ccf458acf4488ce4270d94ba1d24b9fd24270d9499bd14999d14897d14896d14270d94792d04690d0468ecf458ccf4270d94488ce4486ce4384ce4b9fd24a9dd2499bd14999d14270d94896d14794d04792d04690d04270d9458ccf458acf4488ce4486ce4270d94383ce", + "outcome": "refuse", + "reason": "bad_magic" + }, + { + "name": "identity_version", + "form": "identity", + "hex": "5746493100031000100058006800a80060008000b40053bcd752bad651b8d651b6d64270d950b2d54fb0d54faed44eacd44270d94da9d44da7d34ca5d34ca3d34270d94b9fd252bad64270d951b6d650b4d550b2d54fb0d54270d94eacd44eaad44da9d44da7d34270d94ca3d34ba1d24b9fd24a9dd251b8d651b6d650b4d54270d94fb0d54faed44eacd44eaad44270d94da7d34ca5d34ca3d34ba1d24270d94a9dd2499bd14270d950b4d550b2d54fb0d54faed44270d94eaad44da9d44da7d34ca5d34270d94ba1d24b9fd24a9dd2499bd14270d950b4d550b2d54270d94faed44eacd44eaad44da9d44270d9d1b325d1b3254ba1d24b9fd24270d9499bd14999d14897d150b2d54fb0d54faed44eacd44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325499bd14999d14270d94896d14fb0d54270d94eacd44eaad4d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b3254897d14896d14794d04faed44eacd44eaad44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94794d04792d04270d94eaad44da9d44da7d3d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b3254794d04792d04270d94eaad44da9d44270d94ca5d34ca3d3d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94792d04690d0468ecf4da9d44da7d34ca5d34ca3d34270d94b9fd24a9dd2d1b325d1b3254270d94896d14794d04792d04690d04270d9458ccf4da7d34270d94ca3d34ba1d24b9fd24a9dd24270d94999d14897d14896d14794d04270d94690d0468ecf458ccf458acf4ca5d34ca3d34ba1d24270d94a9dd2499bd14999d14897d14270d94794d04792d04690d0468ecf4270d9458acf4488ce4270d94ba1d24b9fd24a9dd2499bd14270d94897d14896d14794d04792d04270d9468ecf458ccf458acf4488ce4270d94ba1d24b9fd24270d9499bd14999d14897d14896d14270d94792d04690d0468ecf458ccf4270d94488ce4486ce4384ce4b9fd24a9dd2499bd14999d14270d94896d14794d04792d04690d04270d9458ccf458acf4488ce4486ce4270d94383ce", + "outcome": "refuse", + "reason": "version" + }, + { + "name": "identity_no_keypoints", + "form": "identity", + "hex": "5746493101001000100058006800a80060008000b40053bcd752bad651b8d651b6d64270d950b2d54fb0d54faed44eacd44270d94da9d44da7d34ca5d34ca3d34270d94b9fd252bad64270d951b6d650b4d550b2d54fb0d54270d94eacd44eaad44da9d44da7d34270d94ca3d34ba1d24b9fd24a9dd251b8d651b6d650b4d54270d94fb0d54faed44eacd44eaad44270d94da7d34ca5d34ca3d34ba1d24270d94a9dd2499bd14270d950b4d550b2d54fb0d54faed44270d94eaad44da9d44da7d34ca5d34270d94ba1d24b9fd24a9dd2499bd14270d950b4d550b2d54270d94faed44eacd44eaad44da9d44270d9d1b325d1b3254ba1d24b9fd24270d9499bd14999d14897d150b2d54fb0d54faed44eacd44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325499bd14999d14270d94896d14fb0d54270d94eacd44eaad4d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b3254897d14896d14794d04faed44eacd44eaad44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94794d04792d04270d94eaad44da9d44da7d3d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b3254794d04792d04270d94eaad44da9d44270d94ca5d34ca3d3d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94792d04690d0468ecf4da9d44da7d34ca5d34ca3d34270d94b9fd24a9dd2d1b325d1b3254270d94896d14794d04792d04690d04270d9458ccf4da7d34270d94ca3d34ba1d24b9fd24a9dd24270d94999d14897d14896d14794d04270d94690d0468ecf458ccf458acf4ca5d34ca3d34ba1d24270d94a9dd2499bd14999d14897d14270d94794d04792d04690d0468ecf4270d9458acf4488ce4270d94ba1d24b9fd24a9dd2499bd14270d94897d14896d14794d04792d04270d9468ecf458ccf458acf4488ce4270d94ba1d24b9fd24270d9499bd14999d14897d14896d14270d94792d04690d0468ecf458ccf4270d94488ce4486ce4384ce4b9fd24a9dd2499bd14999d14270d94896d14794d04792d04690d04270d9458ccf458acf4488ce4486ce4270d94383ce", + "outcome": "refuse", + "reason": "field_range" + }, + { + "name": "identity_truncated", + "form": "identity", + "hex": "5746493101031000100058006800a80060008000b40053bcd752bad651b8d651b6d64270d950b2d54fb0d54faed44eacd44270d94da9d44da7d34ca5d34ca3d34270d94b9fd252bad64270d951b6d650b4d550b2d54fb0d54270d94eacd44eaad44da9d44da7d34270d94ca3d34ba1d24b9fd24a9dd251b8d651b6d650b4d54270d94fb0d54faed44eacd44eaad44270d94da7d34ca5d34ca3d34ba1d24270d94a9dd2499bd14270d950b4d550b2d54fb0d54faed44270d94eaad44da9d44da7d34ca5d34270d94ba1d24b9fd24a9dd2499bd14270d950b4d550b2d54270d94faed44eacd44eaad44da9d44270d9d1b325d1b3254ba1d24b9fd24270d9499bd14999d14897d150b2d54fb0d54faed44eacd44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325499bd14999d14270d94896d14fb0d54270d94eacd44eaad4d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b3254897d14896d14794d04faed44eacd44eaad44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94794d04792d04270d94eaad44da9d44da7d3d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b3254794d04792d04270d94eaad44da9d44270d94ca5d34ca3d3d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94792d04690d0468ecf4da9d44da7d34ca5d34ca3d34270d94b9fd24a9dd2d1b325d1b3254270d94896d14794d04792d04690d04270d9458ccf4da7d34270d94ca3d34ba1d24b9fd24a9dd24270d94999d14897d14896d14794d04270d94690d0468ecf458ccf458acf4ca5d34ca3d34ba1d24270d94a9dd2499bd14999d14897d14270d94794d04792d04690d0468ecf4270d9458acf4488ce4270d94ba1d24b9fd24a9dd2499bd14270d94897d14896d14794d04792d04270d9468ecf458ccf458acf4488ce4270d94ba1d24b9fd24270d9499bd14999d14897d14896d14270d94792d04690d0468ecf458ccf4270d94488ce4486ce4384ce4b9fd24a9dd2499bd14999d14270d94896d14794d04792d04690d04270d9458ccf458acf4488ce4486ce4270d94383", + "outcome": "refuse", + "reason": "truncated" + }, + { + "name": "identity_trailing", + "form": "identity", + "hex": "5746493101031000100058006800a80060008000b40053bcd752bad651b8d651b6d64270d950b2d54fb0d54faed44eacd44270d94da9d44da7d34ca5d34ca3d34270d94b9fd252bad64270d951b6d650b4d550b2d54fb0d54270d94eacd44eaad44da9d44da7d34270d94ca3d34ba1d24b9fd24a9dd251b8d651b6d650b4d54270d94fb0d54faed44eacd44eaad44270d94da7d34ca5d34ca3d34ba1d24270d94a9dd2499bd14270d950b4d550b2d54fb0d54faed44270d94eaad44da9d44da7d34ca5d34270d94ba1d24b9fd24a9dd2499bd14270d950b4d550b2d54270d94faed44eacd44eaad44da9d44270d9d1b325d1b3254ba1d24b9fd24270d9499bd14999d14897d150b2d54fb0d54faed44eacd44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325499bd14999d14270d94896d14fb0d54270d94eacd44eaad4d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b3254897d14896d14794d04faed44eacd44eaad44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94794d04792d04270d94eaad44da9d44da7d3d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b3254794d04792d04270d94eaad44da9d44270d94ca5d34ca3d3d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94792d04690d0468ecf4da9d44da7d34ca5d34ca3d34270d94b9fd24a9dd2d1b325d1b3254270d94896d14794d04792d04690d04270d9458ccf4da7d34270d94ca3d34ba1d24b9fd24a9dd24270d94999d14897d14896d14794d04270d94690d0468ecf458ccf458acf4ca5d34ca3d34ba1d24270d94a9dd2499bd14999d14897d14270d94794d04792d04690d0468ecf4270d9458acf4488ce4270d94ba1d24b9fd24a9dd2499bd14270d94897d14896d14794d04792d04270d9468ecf458ccf458acf4488ce4270d94ba1d24b9fd24270d9499bd14999d14897d14896d14270d94792d04690d0468ecf458ccf4270d94488ce4486ce4384ce4b9fd24a9dd2499bd14999d14270d94896d14794d04792d04690d04270d9458ccf458acf4488ce4486ce4270d94383ce07", + "outcome": "refuse", + "reason": "trailing" + }, + { + "name": "segment", + "form": "segment", + "hex": "57464b3101030200257318fbc5d8bffc4d29b789995590bdd51d6ab919356e789761661d99ffc3ccef517f8f6899001849c65c2ca8164c3bd0d9006e931aaee5248ba3f01ff59f861900000005003075e90300002c0034005400300040005a007a9ce4562ff0094000f3d0003fac8000805e01040310", + "outcome": "read", + "read": { + "k": 3, + "step": 2, + "order": 0, + "generator": "257318fbc5d8bffc4d29b789995590bdd51d6ab919356e789761661d99ffc3cc", + "identity": "ef517f8f6899001849c65c2ca8164c3bd0d9006e931aaee5248ba3f01ff59f86", + "first": 25, + "rate": [ + 30000, + 1001 + ], + "keypoints": [ + [ + [ + 88, + 104 + ], + [ + 168, + 96 + ], + [ + 128, + 180 + ] + ], + [ + [ + 90, + 104 + ], + [ + 168, + 94 + ], + [ + 128, + 186 + ] + ], + [ + [ + 96, + 100 + ], + [ + 166, + 94 + ], + [ + 130, + 196 + ] + ], + [ + [ + 96, + 100 + ], + [ + 166, + 94 + ], + [ + 130, + 196 + ] + ], + [ + [ + -200, + 4000 + ], + [ + 32766, + -32768 + ], + [ + 0, + 0 + ] + ] + ] + } + }, + { + "name": "segment_bad_magic", + "form": "segment", + "hex": "57464b3201030200257318fbc5d8bffc4d29b789995590bdd51d6ab919356e789761661d99ffc3ccef517f8f6899001849c65c2ca8164c3bd0d9006e931aaee5248ba3f01ff59f861900000005003075e90300002c0034005400300040005a007a9ce4562ff0094000f3d0003fac8000805e01040310", + "outcome": "refuse", + "reason": "bad_magic" + }, + { + "name": "segment_version", + "form": "segment", + "hex": "57464b3102030200257318fbc5d8bffc4d29b789995590bdd51d6ab919356e789761661d99ffc3ccef517f8f6899001849c65c2ca8164c3bd0d9006e931aaee5248ba3f01ff59f861900000005003075e90300002c0034005400300040005a007a9ce4562ff0094000f3d0003fac8000805e01040310", + "outcome": "refuse", + "reason": "version" + }, + { + "name": "segment_step_zero", + "form": "segment", + "hex": "57464b3101030000257318fbc5d8bffc4d29b789995590bdd51d6ab919356e789761661d99ffc3ccef517f8f6899001849c65c2ca8164c3bd0d9006e931aaee5248ba3f01ff59f861900000005003075e90300002c0034005400300040005a007a9ce4562ff0094000f3d0003fac8000805e01040310", + "outcome": "refuse", + "reason": "field_range" + }, + { + "name": "segment_order_16", + "form": "segment", + "hex": "57464b3101030210257318fbc5d8bffc4d29b789995590bdd51d6ab919356e789761661d99ffc3ccef517f8f6899001849c65c2ca8164c3bd0d9006e931aaee5248ba3f01ff59f861900000005003075e90300002c0034005400300040005a007a9ce4562ff0094000f3d0003fac8000805e01040310", + "outcome": "refuse", + "reason": "field_range" + }, + { + "name": "segment_no_frames", + "form": "segment", + "hex": "57464b3101030200257318fbc5d8bffc4d29b789995590bdd51d6ab919356e789761661d99ffc3ccef517f8f6899001849c65c2ca8164c3bd0d9006e931aaee5248ba3f01ff59f861900000000003075e90300002c0034005400300040005a007a9ce4562ff0094000f3d0003fac8000805e01040310", + "outcome": "refuse", + "reason": "field_range" + }, + { + "name": "segment_reserved", + "form": "segment", + "hex": "57464b3101030200257318fbc5d8bffc4d29b789995590bdd51d6ab919356e789761661d99ffc3ccef517f8f6899001849c65c2ca8164c3bd0d9006e931aaee5248ba3f01ff59f861900000005003075e90301002c0034005400300040005a007a9ce4562ff0094000f3d0003fac8000805e01040310", + "outcome": "refuse", + "reason": "field_range" + }, + { + "name": "segment_first_frame_out_of_range", + "form": "segment", + "hex": "57464b3101030200257318fbc5d8bffc4d29b789995590bdd51d6ab919356e789761661d99ffc3ccef517f8f6899001849c65c2ca8164c3bd0d9006e931aaee5248ba3f01ff59f861900000005003075e9030000e0b134005400300040005a007a9ce4562ff0094000f3d0003fac8000805e01040310", + "outcome": "refuse", + "reason": "field_range" + }, + { + "name": "segment_truncated", + "form": "segment", + "hex": "57464b3101030200257318fbc5d8bffc4d29b789995590bdd51d6ab919356e789761661d99ffc3ccef517f8f6899001849c65c2ca8164c3bd0d9006e931aaee5248ba3f01ff59f861900000005003075e90300002c0034005400300040005a007a9ce4562ff0094000f3d0003fac8000805e0104", + "outcome": "refuse", + "reason": "truncated" + }, + { + "name": "segment_trailing", + "form": "segment", + "hex": "57464b3101030200257318fbc5d8bffc4d29b789995590bdd51d6ab919356e789761661d99ffc3ccef517f8f6899001849c65c2ca8164c3bd0d9006e931aaee5248ba3f01ff59f861900000005003075e90300002c0034005400300040005a007a9ce4562ff0094000f3d0003fac8000805e0104031000", + "outcome": "refuse", + "reason": "trailing" + }, + { + "name": "segment_two_frames", + "form": "segment", + "hex": "57464b31010110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020019000100000000000000001fff", + "outcome": "read", + "read": { + "k": 1, + "step": 16, + "order": 0, + "generator": "0000000000000000000000000000000000000000000000000000000000000000", + "identity": "0000000000000000000000000000000000000000000000000000000000000000", + "first": 0, + "rate": [ + 25, + 1 + ], + "keypoints": [ + [ + [ + 0, + 0 + ] + ], + [ + [ + 32752, + 0 + ] + ] + ] + } + }, + { + "name": "segment_delta_out_of_range", + "form": "segment", + "hex": "57464b31010110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020019000100000002000000001fff", + "outcome": "refuse", + "reason": "field_range" + }, + { + "name": "segment_code_too_long", + "form": "segment", + "hex": "57464b310101100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200190001000000000000000000000080", + "outcome": "refuse", + "reason": "field_range" + }, + { + "name": "segment_padding_not_zero", + "form": "segment", + "hex": "57464b3101011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002001900010000000000000071", + "outcome": "refuse", + "reason": "noncanonical" + } + ], + "frames": [ + { + "name": "seeded_at_rest", + "generator": "seeded.wfg", + "identity": "seeded.wfi", + "keypoints": [ + [ + 88, + 104 + ], + [ + 168, + 96 + ], + [ + 128, + 180 + ] + ], + "warped_sha256": "9c4b416a911ec0e294cf51ce1606ec3f49b0dc78e134c583d800180d85b0a153", + "maps_sha256": "92ed54a22abfcadf7012fe0c4512365ef92492b16e730616c708c5fe840eb5d4", + "frame_sha256": "b13abfc8b1369083c54a742121ce0b05b3cd76646c492c2e8f82d3ec847af24c", + "warped": "53bcd752bad651b8d651b6d64270d950b2d54fb0d54faed44eacd44270d94da9d44da7d34ca5d34ca3d34270d94b9fd252bad64270d951b6d650b4d550b2d54fb0d54270d94eacd44eaad44da9d44da7d34270d94ca3d34ba1d24b9fd24a9dd251b8d651b6d650b4d54270d94fb0d54faed44eacd44eaad44270d94da7d34ca5d34ca3d34ba1d24270d94a9dd2499bd14270d950b4d550b2d54fb0d54faed44270d94eaad44da9d44da7d34ca5d34270d94ba1d24b9fd24a9dd2499bd14270d950b4d550b2d54270d94faed44eacd44eaad44da9d44270d9d1b325d1b3254ba1d24b9fd24270d9499bd14999d14897d150b2d54fb0d54faed44eacd44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325499bd14999d14270d94896d14fb0d54270d94eacd44eaad4d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b3254897d14896d14794d04faed44eacd44eaad44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94794d04792d04270d94eaad44da9d44da7d3d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b3254794d04792d04270d94eaad44da9d44270d94ca5d34ca3d3d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94792d04690d0468ecf4da9d44da7d34ca5d34ca3d34270d94b9fd24a9dd2d1b325d1b3254270d94896d14794d04792d04690d04270d9458ccf4da7d34270d94ca3d34ba1d24b9fd24a9dd24270d94999d14897d14896d14794d04270d94690d0468ecf458ccf458acf4ca5d34ca3d34ba1d24270d94a9dd2499bd14999d14897d14270d94794d04792d04690d0468ecf4270d9458acf4488ce4270d94ba1d24b9fd24a9dd2499bd14270d94897d14896d14794d04792d04270d9468ecf458ccf458acf4488ce4270d94ba1d24b9fd24270d9499bd14999d14897d14896d14270d94792d04690d0468ecf458ccf4270d94488ce4486ce4384ce4b9fd24a9dd2499bd14999d14270d94896d14794d04792d04690d04270d9458ccf458acf4488ce4486ce4270d94383ce", + "frame": "59aecb368a743999b53875994f715e3580aa208fa53172ab4b7ba13c9eab2393a90889bd3364bf3f7d810f84803658ba4ba7a22996c93188e05270e94e61ba4093881c88c17071e7149697399adf389be30c9ee94f5fe72190a732a1c7473bd356aac938a49d4fa9d73eb8bc00e1ff1d90ff7682ff0bbfff07b2ff3988ff1cc0ff3a97ff1e80ff5c7fff49b2e4039cff4c978435c1ce8369ff255ce654a6cc0491af3f9dfc4d7cec1ebce91ca3eb0199ff4391ff2c84f62b75ff6c85fd377db956aace7498a712b6fb00b8ee459fe45c90dd4572ff00beff887eb48294be5a95cc0186ff5a60fe7572ac06acc41e67ff38ac7923a5e55f63f84ba7c4485ee370afb54f92e600aede406eff0295ff45b1f417b4ff0f82ff4c87ff329bf41566ff649bb00cc77e0d7cfc698cec0aaab02b92ff01b7ff7087f86877de3098d100a5f109a6ff7033fe538fb21dabc92847ff49b3a73399f6747add0e8fd234affc4d59d909bad23195ef1f97ff826cdf0061ff00efff29a3ff1a8fff10bbff3351ff4972cf29bb784588ea3fb6d107c8f900b3ff4c57ff3f89ea3aa6bd74aeb6348af7100fff8a4bfa559a9a23a3d0286fff45aa7a41a1d86c6ef84b9bd34294ff03cdfd3a7aff00b1ff4077ff4677e976d57700b8ff1e8cff24a5ff5786ff2661ff579cc62cdc71149df2587dff19b9d34173fe4399ff5d77fd4494f11695d30eb5ff34a1ff5048ff2b91da258cc0477ed24295ae41a6cf4860d7369dd92e89fb37b0db237ff5539eff2e6fff9933f0336cd600ffcd0182ff447ffe3597f05b56ff4d91a53da7c01a93d38143f30ba8c34e93d76170b54695d14890a1498ed24872ff6867f43398ff5d4ef36565bf3878af33999315b6c309a6ff239ff301acff3687f500bbfd3987ff25c5ff50c4ff00e7ff0b75ff3776ff4d8fbd0c8ef55d47f9598ebd6285b7348cde5865e26e71de4f76cc5a7cd24d72c24b83c2498ac55773d54974b64960cc4d7fc64872b43e5eb0459fca4a9cd14896d5498bd54670db4e89d54a8ed44d86d5498ad24b72dc4887d44887d24e7cd14682c83f77d76351d0" + }, + { + "name": "seeded_moved", + "generator": "seeded.wfg", + "identity": "seeded.wfi", + "keypoints": [ + [ + 100, + 96 + ], + [ + 176, + 104 + ], + [ + 120, + 200 + ] + ], + "warped_sha256": "45bfde6c14c2f6c50a14ec47bc4b340dd329f023548e0c5ccf318b2edb1f3bb3", + "maps_sha256": "f7f3a812bb405aa9f3e33ada70d80c25e7283136733476385855937d2ff49393", + "frame_sha256": "80e246300ea2764aceac6b152cbd42daac61cf3851ebc1fa9011f9cc82b8c788", + "warped": "53bcd750b2d750b0d651b7d64c9ed74a94d74fadd54fadd54fadd4488ed7488dd74da8d44ca6d34ca4d34683d7488dd552bad64c9ed74b98d74fadd64ea9d64fb1d54a97d6488cd74eabd44da8d44ca5d44991d6488fd54ca2d34a99d34a9ad34da6d74fadd650b5d54b9dd64993d74eaad54ea9d44eabd44991d6488dd64da6d34b9fd44a9cd3478dd54890d44a9dd24681d84a97d74eacd54eaad54fafd44a96d6488bd74eaad44da7d44ca3d34890d5488ed54ba1d24996d34996d3468bd450b4d550b3d54b9ad64992d64eaad451a9cf56aac9488dd7869384c0b13b81a48c4a9ad3478bd5478fd4499bd14790d350b2d54eabd54eabd54eadd44c90d18e9479d1b325c8b130c8af309094778f9a78b0ae50719b9f4894d2478bd4478cd34fb0d54990d74993d64ea9d498af72cdb12a8690849a996ad1b325bdaa3ec1ac399a996b8e9b796ea0a2478fd34790d24eaad44eadd44eabd44788d79c9b68d1b325c0ab3ac7ae328e9479aca253d1b325afa34fbdaa3e799294468dd24794d04374d94b99d54da9d44ca1d4a7ac5c879082afa34fd1b325aca253c0ab3a9a996bb8a745d1b32571939d468ed14585d34c9fd54da7d44686d74b9fd46ea8a7969d70c7ae32909477c2ac38d1b32597986ebfab3c9b9a695893bc4792d04585d34da9d44da8d34994d54ca2d3478bd68aa78196a971828f89ccb12b9a996a95a47195a6705386c2468ed14585d3458ad04da8d3488cd64ca4d34ca2d34582d64a9dd24d91cda7ab5ba6aa5b447cd74895d1468ad34791d04690d0447fd44589d04ca4d34685d64ba0d34993d44b9ed24998d2447ed64998d14790d24895d14792d04379d64690d04585d2458bcf4589cf4993d54ca2d34a9ad3457fd7499bd24896d24998d14790d2437ad64793d0468ed1468fd0458bd04378d64589cf437fd24686d64a9dd24998d34a9cd24791d3447cd74896d14794d14793d0468ad2437ad6468dcf4587d04589cf4485cf4278d54ba0d24996d34581d64999d14897d14897d1468ad4447bd64791d0468ed0468dcf4486d14378d54487ce4380d14384ce", + "frame": "58b2cd1a70852c88a6458bb55f7554368f951087ba2774aa4a6d9a429b8e2a91b60996b81e64c94072801683703754bb4eb6c31ab1c94489d1586dca2f709f3b7ea11a8d925386d81d87b927a1e34676ef1da9d04575c61984c01ba4bc4134d2539ac63ba5ae50b2d550b3a600f4e41199ff4d85ff4c94ff00dfff1595ff0bc2ff4d81ff3681ff5b8fff28bdea0597ff509a7f2dadc7815aff0c71d77699df00b5d035a5ff448ce209c4ff1984ff439eff4b7dfe12abdc3770fe7f79ff2a82d156aace6f98aa16a5f70d9ecd48a5db2aafdf446cff0aaeff697ed25c9ac84574fc12b5ff5c64f67083be04c0cc286bff3eac782db0dc5a5eff4c9cbe5763e831bcfa478dd501bff2288dff3685fd569cf00cb1ea148bff2ba2ff5687ff1476f7619db720c086058cf8558cef00afba0798ff448cfd8359f67c82b000b99e03b2fc4f7dff6d6de74a78bf06cebd2f4dfe4baf9e319ff37271e31197ca28b9f92f6ce803b5ee0eaafd3183fe6890ff3e87ec00c3ec0c9fff23a0ff48a3ff2d53ff4975cf2daf7a3f99e24fa0f804bbfc2ba4ff5441fe6f6bea37a4802abeb429aaff485dff814ffe4c9a911da7c30f75ff45a8793f9ed5874dff4a9db93495da15c5fa217eff2184f6878bcb5f99f500d9ff008aff4a79f8528ef7538ffb185cff579ec839d1740b99de507cfa539fc9357efe36b4fb6a5ad712a2ee04b2fc5f8bdb6987c7743ee73390db3aa4ac2b54f73b979d3fa8da5c52d74d89b714a3de23badc3980f46e78ff2687d7349dbd1c87e45a90ff4d6af32f99cb5591e53171fc518b9b31b1ab09abd67442ff11a6d34995d35e61bb18b0ba338aff6b4ffd6784bf1b9bac01a1fd8755f13985953d85d52dac9b27a6e812adff1ca4f000b0ff1f9bff17aaff3b7aff2690ff1bb4cc02baff3983ff1064ff7e6bc207b5c74a63ea5386bd518fbd3c9cda5c6cf28a59df6466d3399cd6388ed14388d15f73dc577ddd3395cc5863e07348844283cf6b2f96479fcb4a95d24681d84c8dd54b91d64d87d64a84d64a7bd7498dd2557bd3438bcf4c87d54a75d9448ac93c88d24b46d8" + }, + { + "name": "seeded_off_the_picture", + "generator": "seeded.wfg", + "identity": "seeded.wfi", + "keypoints": [ + [ + -40, + 300 + ], + [ + 400, + -24 + ], + [ + 128, + 128 + ] + ], + "warped_sha256": "f12a213ea76cff51f796b08c2c56dd5a0dbff10864d6ae4fadbe39f97f28954d", + "maps_sha256": "28053d4a4cf750b7d05a82ee6233a900d693419c2e0e3302e9fdc9165b476781", + "frame_sha256": "5cdf2de1fe5e7fde29b67a1e3f3a14d4b1fe4f9168f1a32895891ffd323c3561", + "warped": "52bbd64270d951b5d6488ed74fb1d54faed44eacd44eaad44373d94da7d34ca5d34b9cd44a9cd34ba0d24ba2d24a9bd351b8d651b5d650b3d54da8d64faed4457fd84eaad44994d67fac9276a99d477fd44a9bd34b9ed24b9fd24a9ad34891d44685d850b3d54374d94faed44a96d690af7d97af72929674d1b325818d8a8eaa7c7da7914b82ce4687d54892d3478cd450b3d54fb0d54faed44eabd4929674d1b3256f85a1d1b325d1b325a49e5dd1b32586908486a484569cc04893d24893d24faed54374d94eabd4488dd7d1b325d1b325b6a647d1b3255d7db7d1b325d1b325a49e5dcdb12a608eb44790d24894d14facd44eacd44da7d44992d5d1b3255d7db7d1b325d1b325bfab3cd1b3255d7db7d1b325d1b3255688bf468cd24792d0488ed74ea9d44891d64fa6cf6da7a8bfab3cd1b3254b74ced1b325d1b325bfab3cd1b3256f85a14b93cc4792d04582d44991d64a9cd54685d74ca2d3477fd45ca2bc529ec7c8af30d1b3254b74ce599abc619ab0468ad24690d04586d3468dd04da5d44a99d54ca4d34b9ed34891d44a9bd24376d8529bc65199c64894d24794d04377d74691d0468fcf437cd54589d04b9ed54a9ad44ca3d34a9ad34a9ed2499bd14892d24897d14272d94794d04792d0458ad2468fcf447dd5458bcf458acf4ca6d44a99d44ba2d24892d4499ad24689d44898d14896d14690d14792d04376d7468fcf458dcf447dd4458acf4488ce5e82b752a5cb4993d44a9bd2478cd44581d54793d2447dd64792d04691d04587d2458dcf437cd54589cf4488ce4376d6d1b325a29d60528cc74a9dd2447bd74790d2468bd34582d44690d0447dd5458dcf458bcf4379d54488ce4486ce437bd3c1ac39cdb12abfab3c488fd44892d24896d1468bd24791d0468cd14272d8458ccf458acf4488ce4486ce4378d54384cebfab3c8e9479d1b32588a2824893d2478dd34586d34791d04588d24274d8458ccf458acf4488ce4486ce4270d94383cebdaa3ed1b325b2a54c7d8c8e519ac64793d14587d34690d04482d44377d7458ccf458acf4488ce4486ce4270d94383ce", + "frame": "53b8d41b704d2c79bc5d59875f8ea9439c783669cc4b8e9b466dbd499c893f6ac65d5ca34d8fc449966d1786732d48bd47b7be3bb5d86762cb533d823d62ab5aaf892f5eb84a8cd3697e944780ad275fd95e6f9e507bbf5990ae3799c13d33c44c84c94a96a43978e09853b93382b65c809b4d83b57f2eaa5eaba36a72ba3486bd792f973e62d452815a4892d33b5aff5ba2922587a15643f529c3a02ec7bb2385b02087e56069cd0a83d122b4b6018aff31bfa7006aff08a3c56d7fe10088ee54a4cd708d4b297cea5d648bc6ad33549f55b0a14e1671c18063c961619b677ebb6e73989845e8556a852c9dca2552ff40ab783bc3df2387ff296cb33a7fc04d83c24e7fde00bac93b8bc413aeea1b8cc64c74fc02bdfe3569f85d9cea1d6fe5618eb126d79a01cfff4b59ff4a7cd43a86d216e2ff0db2df0066ff019edc5c4aff1ba78d4963d32582c84195c92158fd4ba9a31ea1f66b7cd70c99c13798ff2e7cd91e62eabeb03a5aa1f77e68ff00c4f709b4d60956ea457fdf5e84cf3869e1519dc91ebc9148a2d7648dff1789ff00c4f0304ffe448dcd20a3f700c3e42e50ff00c6c93f4fff4d38b83ea4cd2a91fd249a934494dd8e57eb489ed53489e12eb5d45838ff526bee5492f300ebdc328fff037df1076eff4b7fff7b577a316cb95d94882cad880392e75583e52895d26d7eed0b99ff19a3f213b6f73c85ff2dd7ff5f58ff4265ff2a7f9d4488cc4877cf4a83bb7a98c6355fbf588ea9147fd0759bf401b1ff6085ff1c94ef2ca2e53477f16cbaff3e78dd6550ae27bcaf2e75f6498590618e945081cb7965bf367eca9153b71baba54b78d81ca9c74a77d9687ca23c8bcd7257d9517dc65d88b0148ed11dc586119bd10db2fc1bacf500bfff276ff306aafe4f74e600b1f94d86d601adff6965ce1f5ee15587983777cc5753d6a597616a50e06097a37179b94c8fc25375ba4d77c55278a44d77c25e57a65370d5487faa5a51c14b7372416fd84751ab78a959cfb2276c69aa75859f5392ca4b8fd34a82d7478fd44581da4d78e04387d3468bd1517ad54385cb3f7cd31e60c6" + }, + { + "name": "face1_0", + "generator": "generator.wfg", + "identity": "identities/face1.wfi", + "keypoints": [ + [ + 312, + 520 + ], + [ + 548, + 496 + ], + [ + 312, + 512 + ], + [ + 548, + 484 + ], + [ + 320, + 520 + ], + [ + 556, + 496 + ], + [ + 428, + 624 + ], + [ + 360, + 748 + ], + [ + 572, + 724 + ], + [ + 456, + 724 + ], + [ + 464, + 780 + ], + [ + 520, + 892 + ], + [ + 304, + 448 + ], + [ + 540, + 424 + ], + [ + 196, + 596 + ], + [ + 772, + 532 + ] + ], + "warped_sha256": "e200c9426e769253b14e770037af220ca529b32699dee1fe222f542c2244906b", + "maps_sha256": "8ff1b1398be5ff782728bbca0a74e91e3bf58fa9c9857ab8ce68045459766d56", + "frame_sha256": "37e2ec4346647c7572345c0f78fe9e5b3ebcf9decd9ce9cfeace5271454d9739" + }, + { + "name": "face1_1", + "generator": "generator.wfg", + "identity": "identities/face1.wfi", + "keypoints": [ + [ + 316, + 520 + ], + [ + 548, + 496 + ], + [ + 316, + 516 + ], + [ + 548, + 492 + ], + [ + 320, + 520 + ], + [ + 556, + 496 + ], + [ + 428, + 620 + ], + [ + 364, + 744 + ], + [ + 576, + 724 + ], + [ + 460, + 728 + ], + [ + 464, + 772 + ], + [ + 520, + 892 + ], + [ + 308, + 448 + ], + [ + 540, + 424 + ], + [ + 200, + 596 + ], + [ + 772, + 532 + ] + ], + "warped_sha256": "8430f262f4ebb520e6ccb4fa352195383b42c89707478f3d165ad720693cb44c", + "maps_sha256": "544e26d18c6c8296e8ef581e4013e5e89d8af2e5013181279c423eacfe69628c", + "frame_sha256": "07053d900fdd34c7526a548e0ae3c0b885a89e9efb373b1052ecb023b53c874b" + }, + { + "name": "face1_2", + "generator": "generator.wfg", + "identity": "identities/face1.wfi", + "keypoints": [ + [ + 320, + 520 + ], + [ + 552, + 492 + ], + [ + 316, + 508 + ], + [ + 552, + 480 + ], + [ + 320, + 520 + ], + [ + 552, + 492 + ], + [ + 432, + 620 + ], + [ + 364, + 744 + ], + [ + 576, + 720 + ], + [ + 464, + 728 + ], + [ + 468, + 756 + ], + [ + 524, + 892 + ], + [ + 308, + 448 + ], + [ + 544, + 424 + ], + [ + 200, + 596 + ], + [ + 772, + 536 + ] + ], + "warped_sha256": "6e7ba1b5baea4a43bc5343001ab62d4d698d657581ad71360f50c237ea1b9ffd", + "maps_sha256": "af2ae8fefa15c1dd806844225e1ba617ec94a1f411dc99d5e29c8936317b92e8", + "frame_sha256": "e04c9b2a47d0b88cf2c980158a58e4056cb65ea026c64341b3d5a88637c58ba1" + }, + { + "name": "face2_0", + "generator": "generator.wfg", + "identity": "identities/face2.wfi", + "keypoints": [ + [ + 312, + 500 + ], + [ + 584, + 488 + ], + [ + 308, + 480 + ], + [ + 584, + 468 + ], + [ + 312, + 500 + ], + [ + 588, + 488 + ], + [ + 440, + 612 + ], + [ + 356, + 708 + ], + [ + 572, + 696 + ], + [ + 456, + 692 + ], + [ + 460, + 744 + ], + [ + 504, + 876 + ], + [ + 304, + 420 + ], + [ + 580, + 408 + ], + [ + 180, + 572 + ], + [ + 788, + 540 + ] + ], + "warped_sha256": "f0d62f68d06cc733358464be8998119e0f549671b29ed97d302292fa6db66810", + "maps_sha256": "35fa802daa5ef48b6ea1a513e7ebaef08d20e7caf49b9432ba769a81b2ad264b", + "frame_sha256": "52b15513dd3c672b14da6443b5db81887b935f13b056ed7a4e748f524baee0a1" + }, + { + "name": "face2_1", + "generator": "generator.wfg", + "identity": "identities/face2.wfi", + "keypoints": [ + [ + 308, + 508 + ], + [ + 584, + 492 + ], + [ + 308, + 496 + ], + [ + 584, + 480 + ], + [ + 312, + 508 + ], + [ + 584, + 492 + ], + [ + 440, + 620 + ], + [ + 356, + 712 + ], + [ + 572, + 700 + ], + [ + 460, + 696 + ], + [ + 464, + 756 + ], + [ + 504, + 880 + ], + [ + 304, + 424 + ], + [ + 576, + 412 + ], + [ + 180, + 576 + ], + [ + 788, + 540 + ] + ], + "warped_sha256": "368838d301b1baadadb839dd5b5651c27250f96e4e038275dcd2d0331074c208", + "maps_sha256": "6a9af4a10c06bd71fc16d78d3da8f117d94b4fa8bd9bd8224526802937f11d31", + "frame_sha256": "e0a23076ac6fcd7f078bab64559ab5b8f07572297040e459a221fc0383370bd9" + }, + { + "name": "face2_2", + "generator": "generator.wfg", + "identity": "identities/face2.wfi", + "keypoints": [ + [ + 308, + 512 + ], + [ + 584, + 496 + ], + [ + 308, + 508 + ], + [ + 584, + 492 + ], + [ + 308, + 512 + ], + [ + 584, + 496 + ], + [ + 444, + 624 + ], + [ + 356, + 720 + ], + [ + 576, + 704 + ], + [ + 460, + 704 + ], + [ + 464, + 756 + ], + [ + 508, + 884 + ], + [ + 304, + 432 + ], + [ + 576, + 412 + ], + [ + 176, + 580 + ], + [ + 788, + 540 + ] + ], + "warped_sha256": "4219eb812c20cb8cf8527c2d7eb8c8515a626783026e1aca8947311b8608c59b", + "maps_sha256": "411323c0a2bce35c739d1d04781e69ba7d66eb631ed75eadffe31cad1e3f2147", + "frame_sha256": "49d5c395d09a4a66a39a1df5a69b6cddf46950d739f700a7cff2f9f4ae5cb8be" + } + ], + "cases": [ + { + "name": "one_face_two_segments", + "about": "a participant's identity record and two segments of her keypoint track, each a track record that states its frames and a synthesized origin claim; the generator and the prior held, so both segments are proved; a player's log presents both at generation-equivalence with the label shown at first exposure (§6, §8, §9)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":[\"37e2ec4346647c7572345c0f78fe9e5b3ebcf9decd9ce9cfeace5271454d9739\",\"07053d900fdd34c7526a548e0ae3c0b885a89e9efb373b1052ecb023b53c874b\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"74676a381d3796f67de607e3801d608795c77b2fe2755b9aef4ea4b1745375bf0e758bfc954a5cf395448583422475801725c95659bf0e4d1ee1974893064a01\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}", + "{\"frames\":[\"e04c9b2a47d0b88cf2c980158a58e4056cb65ea026c64341b3d5a88637c58ba1\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"b8636d103036a725f44b8c2437fbc903b8db831da885eb7d1820abdca77990ac\",\"prev\":\"09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce\",\"seq\":1,\"sig\":\"70d2a568a538cd2d850978844f02290685a119018a9bc07ed5ca86f3474c9300e918bf52a615df48a26c95f098d5c265d71656a0225d0e1d057055066d678103\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce\",\"sig\":\"1e3bbbdeddc863b36402b304cce9ca61e01d3a3a4eb5580a124481421242a28169406e17d02cbf3c45d4eb71532b16bd7b48f7467eef1afca2f67f7e23273608\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"type\":\"object\"}}", + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"64445745627a935c95a67e17c5e5d400de36c2c4c2481036a97db68620705860\",\"sig\":\"e2c5a9d7ffdfa0e36d3d5ed6115d75c05d23e8106aece9905c5bbcebf8d2a5f31cb0ec95c30af79b86556fb76c354ab6fb447f16852777630ee1db0d73a1840f\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"b8636d103036a725f44b8c2437fbc903b8db831da885eb7d1820abdca77990ac\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": "{\"audio\":null,\"classes\":[\"synthesized\"],\"grace_us\":500000,\"interval_us\":10000000,\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-label-policy\",\"sig\":\"bab8544cee98aee81f08be063f3764438eeb0fc433400d9528969960483c9e17c730a3357261b3d511e28e989e5d8cf5674cca1c889f757c44d73d6be042f503\",\"signer_pubkey\":\"cacc7690016331baf28bf16ac58c1c10a3eb2bb82041b758aff950ec87c13ed6\",\"visual\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\"}", + "player": [ + "{\"at\":0,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"wai.avatar.face\",\"media\":\"video\",\"origin\":\"5165623a9049ceffb80369b4295f44893dde4b7a62948b56b0fe078d9e5930d8\",\"output\":\"73345136e963e8aa699f1cf85ebb80f7848c6c82c424e6ed4c420378efaf83b1\",\"subject\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"tier\":\"generation-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"e3957909c5363e3be89259688fb5313457f659ac5f994e3bbed53419402dc46e\",\"sig\":\"e2c528f58d4dea46db4a7085f1883a99ea65341fc68e640affafc94f159417757e0c8a46dd04530b4463bc2f0590dc571592c9304e23e8e80fc49eb10e03a207\",\"signer_pubkey\":\"83157859a973efc15c1171a1f7d44fbd2a482254499d4844ab5becc7ef0f2e0d\"}", + "{\"at\":40000,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"first-exposure\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":\"74366515748d1fe251d680721469880ad0f1a384744cfe91ee5d7a2dc71cf883\",\"seq\":1,\"session\":\"e3957909c5363e3be89259688fb5313457f659ac5f994e3bbed53419402dc46e\",\"sig\":\"278c3d35d892bb0d60cc2be556e519377de082325fca34edbf53215ce22193feb61b4ee2ec71f799c0d5c01aac70a3e7cfa834d61bbca84c0f88ae653b8cf408\",\"signer_pubkey\":\"83157859a973efc15c1171a1f7d44fbd2a482254499d4844ab5becc7ef0f2e0d\"}", + "{\"at\":80000,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"wai.avatar.face\",\"media\":\"video\",\"origin\":\"7f4bf336326a08d6869767601ed20c108f54f8839d34c53b0a051bbe7f9baa4b\",\"output\":\"e04c9b2a47d0b88cf2c980158a58e4056cb65ea026c64341b3d5a88637c58ba1\",\"subject\":\"b8636d103036a725f44b8c2437fbc903b8db831da885eb7d1820abdca77990ac\",\"tier\":\"generation-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"df52d0860bda1ff05fb5268f5a4e361a5f68b699e1ee79ae7c978ad0030c01ea\",\"seq\":2,\"session\":\"e3957909c5363e3be89259688fb5313457f659ac5f994e3bbed53419402dc46e\",\"sig\":\"2098887a87092582653c4adcb65bc0ecee9771114be463d48d82f21020d9bf305f3bd876f1930751cb28163549ab2640aaa084bfe5efefab5da1289de5e0e406\",\"signer_pubkey\":\"83157859a973efc15c1171a1f7d44fbd2a482254499d4844ab5becc7ef0f2e0d\"}" + ], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c", + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9400000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c020000000100190001000000500082008a007b004f007f008a007800500082008a007b006c009b005b00ba009000b4007400b6007500bd008300df004d00700088006a0032009500c1008600" + ], + "generators": [ + { + "file": "generator.wfg" + } + ], + "priors": [ + { + "file": "identities/face1.wfi" + } + ], + "outcome": "accept", + "statements": { + "identities": [ + { + "link": "95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888", + "identity": "42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c", + "prior": "read" + } + ], + "tracks": [ + { + "link": "09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce", + "object": "1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950", + "frames": 2, + "status": "proved", + "output": "73345136e963e8aa699f1cf85ebb80f7848c6c82c424e6ed4c420378efaf83b1" + }, + { + "link": "64445745627a935c95a67e17c5e5d400de36c2c4c2481036a97db68620705860", + "object": "b8636d103036a725f44b8c2437fbc903b8db831da885eb7d1820abdca77990ac", + "frames": 1, + "status": "proved", + "output": "e04c9b2a47d0b88cf2c980158a58e4056cb65ea026c64341b3d5a88637c58ba1" + } + ], + "claims": [ + { + "link": "5165623a9049ceffb80369b4295f44893dde4b7a62948b56b0fe078d9e5930d8", + "class": "synthesized" + }, + { + "link": "7f4bf336326a08d6869767601ed20c108f54f8839d34c53b0a051bbe7f9baa4b", + "class": "synthesized" + } + ], + "labels": [ + { + "entry": 0, + "trigger": "first-exposure", + "shown": 1 + }, + { + "entry": 2, + "trigger": "interval", + "shown": 1 + } + ], + "reviews_presented": [], + "visual_icon": "LABEL_AI GENERATED_black.svg" + } + }, + { + "name": "not_held", + "about": "the same case with neither the generator nor the prior held: accepted with both segments not recomputed (§9.2 step 3.8)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":[\"37e2ec4346647c7572345c0f78fe9e5b3ebcf9decd9ce9cfeace5271454d9739\",\"07053d900fdd34c7526a548e0ae3c0b885a89e9efb373b1052ecb023b53c874b\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"74676a381d3796f67de607e3801d608795c77b2fe2755b9aef4ea4b1745375bf0e758bfc954a5cf395448583422475801725c95659bf0e4d1ee1974893064a01\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}", + "{\"frames\":[\"e04c9b2a47d0b88cf2c980158a58e4056cb65ea026c64341b3d5a88637c58ba1\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"b8636d103036a725f44b8c2437fbc903b8db831da885eb7d1820abdca77990ac\",\"prev\":\"09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce\",\"seq\":1,\"sig\":\"70d2a568a538cd2d850978844f02290685a119018a9bc07ed5ca86f3474c9300e918bf52a615df48a26c95f098d5c265d71656a0225d0e1d057055066d678103\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce\",\"sig\":\"1e3bbbdeddc863b36402b304cce9ca61e01d3a3a4eb5580a124481421242a28169406e17d02cbf3c45d4eb71532b16bd7b48f7467eef1afca2f67f7e23273608\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"type\":\"object\"}}", + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"64445745627a935c95a67e17c5e5d400de36c2c4c2481036a97db68620705860\",\"sig\":\"e2c5a9d7ffdfa0e36d3d5ed6115d75c05d23e8106aece9905c5bbcebf8d2a5f31cb0ec95c30af79b86556fb76c354ab6fb447f16852777630ee1db0d73a1840f\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"b8636d103036a725f44b8c2437fbc903b8db831da885eb7d1820abdca77990ac\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": "{\"audio\":null,\"classes\":[\"synthesized\"],\"grace_us\":500000,\"interval_us\":10000000,\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-label-policy\",\"sig\":\"bab8544cee98aee81f08be063f3764438eeb0fc433400d9528969960483c9e17c730a3357261b3d511e28e989e5d8cf5674cca1c889f757c44d73d6be042f503\",\"signer_pubkey\":\"cacc7690016331baf28bf16ac58c1c10a3eb2bb82041b758aff950ec87c13ed6\",\"visual\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\"}", + "player": [ + "{\"at\":0,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"wai.avatar.face\",\"media\":\"video\",\"origin\":\"5165623a9049ceffb80369b4295f44893dde4b7a62948b56b0fe078d9e5930d8\",\"output\":\"73345136e963e8aa699f1cf85ebb80f7848c6c82c424e6ed4c420378efaf83b1\",\"subject\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"tier\":\"generation-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"e3957909c5363e3be89259688fb5313457f659ac5f994e3bbed53419402dc46e\",\"sig\":\"e2c528f58d4dea46db4a7085f1883a99ea65341fc68e640affafc94f159417757e0c8a46dd04530b4463bc2f0590dc571592c9304e23e8e80fc49eb10e03a207\",\"signer_pubkey\":\"83157859a973efc15c1171a1f7d44fbd2a482254499d4844ab5becc7ef0f2e0d\"}", + "{\"at\":40000,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"first-exposure\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":\"74366515748d1fe251d680721469880ad0f1a384744cfe91ee5d7a2dc71cf883\",\"seq\":1,\"session\":\"e3957909c5363e3be89259688fb5313457f659ac5f994e3bbed53419402dc46e\",\"sig\":\"278c3d35d892bb0d60cc2be556e519377de082325fca34edbf53215ce22193feb61b4ee2ec71f799c0d5c01aac70a3e7cfa834d61bbca84c0f88ae653b8cf408\",\"signer_pubkey\":\"83157859a973efc15c1171a1f7d44fbd2a482254499d4844ab5becc7ef0f2e0d\"}", + "{\"at\":80000,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"wai.avatar.face\",\"media\":\"video\",\"origin\":\"7f4bf336326a08d6869767601ed20c108f54f8839d34c53b0a051bbe7f9baa4b\",\"output\":\"e04c9b2a47d0b88cf2c980158a58e4056cb65ea026c64341b3d5a88637c58ba1\",\"subject\":\"b8636d103036a725f44b8c2437fbc903b8db831da885eb7d1820abdca77990ac\",\"tier\":\"generation-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":\"df52d0860bda1ff05fb5268f5a4e361a5f68b699e1ee79ae7c978ad0030c01ea\",\"seq\":2,\"session\":\"e3957909c5363e3be89259688fb5313457f659ac5f994e3bbed53419402dc46e\",\"sig\":\"2098887a87092582653c4adcb65bc0ecee9771114be463d48d82f21020d9bf305f3bd876f1930751cb28163549ab2640aaa084bfe5efefab5da1289de5e0e406\",\"signer_pubkey\":\"83157859a973efc15c1171a1f7d44fbd2a482254499d4844ab5becc7ef0f2e0d\"}" + ], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c", + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9400000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c020000000100190001000000500082008a007b004f007f008a007800500082008a007b006c009b005b00ba009000b4007400b6007500bd008300df004d00700088006a0032009500c1008600" + ], + "generators": [], + "priors": [], + "outcome": "accept", + "statements": { + "identities": [ + { + "link": "95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888", + "identity": "42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c", + "prior": "not-held" + } + ], + "tracks": [ + { + "link": "09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce", + "object": "1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950", + "frames": 2, + "status": "not-recomputed", + "output": null + }, + { + "link": "64445745627a935c95a67e17c5e5d400de36c2c4c2481036a97db68620705860", + "object": "b8636d103036a725f44b8c2437fbc903b8db831da885eb7d1820abdca77990ac", + "frames": 1, + "status": "not-recomputed", + "output": null + } + ], + "claims": [ + { + "link": "5165623a9049ceffb80369b4295f44893dde4b7a62948b56b0fe078d9e5930d8", + "class": "synthesized" + }, + { + "link": "7f4bf336326a08d6869767601ed20c108f54f8839d34c53b0a051bbe7f9baa4b", + "class": "synthesized" + } + ], + "labels": [ + { + "entry": 0, + "trigger": "first-exposure", + "shown": 1 + }, + { + "entry": 2, + "trigger": "interval", + "shown": 1 + } + ], + "reviews_presented": [], + "visual_icon": "LABEL_AI GENERATED_black.svg" + } + }, + { + "name": "two_faces", + "about": "two participants, each driving her own face under her own key, their segments interleaved (§8.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}", + "{\"identity\":\"d87d24a2e5200d7c7cdc83215a73b649f3ac8fbdc6922949ef211ed9dc1c5e1b\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"1422ffdd2434610f26d5258cf94f3e9dad94f9a478b506d5dc136257a280a0f502fd32d32ca6c4d452634549c955f36e7269820d08c83d640bcb613b6a8b4009\",\"signer_pubkey\":\"07c1966be87d4fc361914b76ac2416938dbd6817efd85650c26477fa0d0987e9\"}" + ], + "tracks": [ + "{\"frames\":[\"37e2ec4346647c7572345c0f78fe9e5b3ebcf9decd9ce9cfeace5271454d9739\",\"07053d900fdd34c7526a548e0ae3c0b885a89e9efb373b1052ecb023b53c874b\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"74676a381d3796f67de607e3801d608795c77b2fe2755b9aef4ea4b1745375bf0e758bfc954a5cf395448583422475801725c95659bf0e4d1ee1974893064a01\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}", + "{\"frames\":[\"52b15513dd3c672b14da6443b5db81887b935f13b056ed7a4e748f524baee0a1\",\"e0a23076ac6fcd7f078bab64559ab5b8f07572297040e459a221fc0383370bd9\",\"49d5c395d09a4a66a39a1df5a69b6cddf46950d739f700a7cff2f9f4ae5cb8be\"],\"identity\":\"a7c567e3c21b0d310aba98ae22e884f14a9c57438720850e61216e11d2a63c27\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"bf8ce1d0feb3a05e47b61e2d154c0930631ad8ed4d45e81fc85d7f1b04a11816\",\"prev\":null,\"seq\":0,\"sig\":\"db06c4c05e896f6a663552e96d32bb75aac864dfee7cf4de1cb6fc8a572e1ba23e2ae0a011eec4ae2f9ec252261acd9bf2d79d19d0e1f77f562458a7685a3901\",\"signer_pubkey\":\"07c1966be87d4fc361914b76ac2416938dbd6817efd85650c26477fa0d0987e9\"}", + "{\"frames\":[\"e04c9b2a47d0b88cf2c980158a58e4056cb65ea026c64341b3d5a88637c58ba1\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"b8636d103036a725f44b8c2437fbc903b8db831da885eb7d1820abdca77990ac\",\"prev\":\"09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce\",\"seq\":1,\"sig\":\"70d2a568a538cd2d850978844f02290685a119018a9bc07ed5ca86f3474c9300e918bf52a615df48a26c95f098d5c265d71656a0225d0e1d057055066d678103\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce\",\"sig\":\"1e3bbbdeddc863b36402b304cce9ca61e01d3a3a4eb5580a124481421242a28169406e17d02cbf3c45d4eb71532b16bd7b48f7467eef1afca2f67f7e23273608\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"type\":\"object\"}}", + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"f385208cd11f341946c8e4fd09fd8a078444079a8655bd2b872a2c17d5ca58da\",\"sig\":\"65ebec2a5f5236930c78aedeab8230e742d5afafc8407201830ffa6e850654ca2b44536bdfdd9152b904cb020f7d1465787f3908a43e3c460a842a8e4380c60b\",\"signer_pubkey\":\"07c1966be87d4fc361914b76ac2416938dbd6817efd85650c26477fa0d0987e9\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"bf8ce1d0feb3a05e47b61e2d154c0930631ad8ed4d45e81fc85d7f1b04a11816\",\"type\":\"object\"}}", + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"64445745627a935c95a67e17c5e5d400de36c2c4c2481036a97db68620705860\",\"sig\":\"e2c5a9d7ffdfa0e36d3d5ed6115d75c05d23e8106aece9905c5bbcebf8d2a5f31cb0ec95c30af79b86556fb76c354ab6fb447f16852777630ee1db0d73a1840f\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"b8636d103036a725f44b8c2437fbc903b8db831da885eb7d1820abdca77990ac\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c", + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7da900000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6d87d24a2e5200d7c7cdc83215a73b649f3ac8fbdc6922949ef211ed9dc1c5e1b0000000003001900010000004e007d0092007a004d007800920075004e007d0093007a006e0099005900b1008f00ae007200ad007300ba007e00db004c006900910066002d008f00c500870045b899e54e5bb6d9eed3beee79d3b6e56e5db974f0", + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9400000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c020000000100190001000000500082008a007b004f007f008a007800500082008a007b006c009b005b00ba009000b4007400b6007500bd008300df004d00700088006a0032009500c1008600" + ], + "generators": [ + { + "file": "generator.wfg" + } + ], + "priors": [ + { + "file": "identities/face1.wfi" + }, + { + "file": "identities/face2.wfi" + } + ], + "outcome": "accept", + "statements": { + "identities": [ + { + "link": "95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888", + "identity": "42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c", + "prior": "read" + }, + { + "link": "a7c567e3c21b0d310aba98ae22e884f14a9c57438720850e61216e11d2a63c27", + "identity": "d87d24a2e5200d7c7cdc83215a73b649f3ac8fbdc6922949ef211ed9dc1c5e1b", + "prior": "read" + } + ], + "tracks": [ + { + "link": "09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce", + "object": "1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950", + "frames": 2, + "status": "proved", + "output": "73345136e963e8aa699f1cf85ebb80f7848c6c82c424e6ed4c420378efaf83b1" + }, + { + "link": "f385208cd11f341946c8e4fd09fd8a078444079a8655bd2b872a2c17d5ca58da", + "object": "bf8ce1d0feb3a05e47b61e2d154c0930631ad8ed4d45e81fc85d7f1b04a11816", + "frames": 3, + "status": "proved", + "output": "8fc1f5198a28931bd06172cca32625cfbab435c8d16d158574228cf7c5cf6eef" + }, + { + "link": "64445745627a935c95a67e17c5e5d400de36c2c4c2481036a97db68620705860", + "object": "b8636d103036a725f44b8c2437fbc903b8db831da885eb7d1820abdca77990ac", + "frames": 1, + "status": "proved", + "output": "e04c9b2a47d0b88cf2c980158a58e4056cb65ea026c64341b3d5a88637c58ba1" + } + ], + "claims": [ + { + "link": "5165623a9049ceffb80369b4295f44893dde4b7a62948b56b0fe078d9e5930d8", + "class": "synthesized" + }, + { + "link": "8c6419a41467df032948ed2d8247fddc4a37bc9e65a89d1a73aa25b2b0170c42", + "class": "synthesized" + }, + { + "link": "7f4bf336326a08d6869767601ed20c108f54f8839d34c53b0a051bbe7f9baa4b", + "class": "synthesized" + } + ], + "labels": [], + "reviews_presented": [], + "visual_icon": null + } + }, + { + "name": "frames_unstated", + "about": "a track record that states no frames: the frames are recomputed all the same (§8.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":null,\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"d5e46672cf3bc2432fc000d575f09f495c2cecd09783ea67d36bd02ab655648d96cdb5ee54eca282c5a48e633048f7bfc9680ef23e18d07aba3a34cbd7a5ca06\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"033f45cab200eddbe10880d2e2636e993ef4a766beaa66606e06a9284e8e33f9\",\"sig\":\"bdbd06345b9addd25846b2780cf61870f175aa0bc0f92bf74256dfe99491964701879818126392db93217a5e6ab6756f38262b242cb872109c96b990e5d3d805\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c" + ], + "generators": [ + { + "file": "generator.wfg" + } + ], + "priors": [ + { + "file": "identities/face1.wfi" + } + ], + "outcome": "accept", + "statements": { + "identities": [ + { + "link": "95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888", + "identity": "42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c", + "prior": "read" + } + ], + "tracks": [ + { + "link": "033f45cab200eddbe10880d2e2636e993ef4a766beaa66606e06a9284e8e33f9", + "object": "1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950", + "frames": 2, + "status": "proved", + "output": "73345136e963e8aa699f1cf85ebb80f7848c6c82c424e6ed4c420378efaf83b1" + } + ], + "claims": [ + { + "link": "5cc3664ad441abab0350e500270a9f4122eb6ccfbcf68779173a5bdcb6b25223", + "class": "synthesized" + } + ], + "labels": [], + "reviews_presented": [], + "visual_icon": null + } + }, + { + "name": "record_malformed", + "about": "an identity record with a member §8.2 does not list", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"name\":\"first face\",\"sig\":\"747eaf43a76a3c957cd4b3c65449af561c1c5c344d344c5aa9ba0ab3e396bf9a1370baa1a623014d0be43543f099aedb51ba98be84c5f3492a47b8eaa77c5b04\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [], + "claims": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "generators": [], + "priors": [], + "outcome": "refuse", + "reason": "record_malformed", + "at": [ + "identity", + 0 + ] + }, + { + "name": "bad_key", + "about": "an identity record whose key is the identity point, of small order (SPEC §7 *Signatures* step 2)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"0100000000000000000000000000000000000000000000000000000000000000\"}" + ], + "tracks": [], + "claims": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "generators": [], + "priors": [], + "outcome": "refuse", + "reason": "bad_key", + "at": [ + "identity", + 0 + ] + }, + { + "name": "bad_signature", + "about": "a track record whose signature has one digit changed", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":[\"37e2ec4346647c7572345c0f78fe9e5b3ebcf9decd9ce9cfeace5271454d9739\",\"07053d900fdd34c7526a548e0ae3c0b885a89e9efb373b1052ecb023b53c874b\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"04676a381d3796f67de607e3801d608795c77b2fe2755b9aef4ea4b1745375bf0e758bfc954a5cf395448583422475801725c95659bf0e4d1ee1974893064a01\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce\",\"sig\":\"1e3bbbdeddc863b36402b304cce9ca61e01d3a3a4eb5580a124481421242a28169406e17d02cbf3c45d4eb71532b16bd7b48f7467eef1afca2f67f7e23273608\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c" + ], + "generators": [], + "priors": [], + "outcome": "refuse", + "reason": "bad_signature", + "at": [ + "track", + 0 + ] + }, + { + "name": "identity_invalid", + "about": "an identity record whose prior is held and does not read: the seeded prior with its last byte missing (§7.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"3034fb6778a47212468e1d5eeab1656c593ed4c53c788d85f3fc0eb7c0a1bb9b\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"bab97abd3daa780d006c12e27b550c39f8998a10c10e6bd34f5ba1686d0410cbbe5689688259216927b4f8907ea84c4d709652be5543b306cbaffa8aa091d706\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [], + "claims": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "generators": [], + "priors": [ + { + "hex": "5746493101031000100058006800a80060008000b40053bcd752bad651b8d651b6d64270d950b2d54fb0d54faed44eacd44270d94da9d44da7d34ca5d34ca3d34270d94b9fd252bad64270d951b6d650b4d550b2d54fb0d54270d94eacd44eaad44da9d44da7d34270d94ca3d34ba1d24b9fd24a9dd251b8d651b6d650b4d54270d94fb0d54faed44eacd44eaad44270d94da7d34ca5d34ca3d34ba1d24270d94a9dd2499bd14270d950b4d550b2d54fb0d54faed44270d94eaad44da9d44da7d34ca5d34270d94ba1d24b9fd24a9dd2499bd14270d950b4d550b2d54270d94faed44eacd44eaad44da9d44270d9d1b325d1b3254ba1d24b9fd24270d9499bd14999d14897d150b2d54fb0d54faed44eacd44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325499bd14999d14270d94896d14fb0d54270d94eacd44eaad4d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b3254897d14896d14794d04faed44eacd44eaad44270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94794d04792d04270d94eaad44da9d44da7d3d1b3254270d9d1b325d1b325d1b325d1b3254270d9d1b325d1b3254794d04792d04270d94eaad44da9d44270d94ca5d34ca3d3d1b325d1b3254270d9d1b325d1b325d1b325d1b3254270d94792d04690d0468ecf4da9d44da7d34ca5d34ca3d34270d94b9fd24a9dd2d1b325d1b3254270d94896d14794d04792d04690d04270d9458ccf4da7d34270d94ca3d34ba1d24b9fd24a9dd24270d94999d14897d14896d14794d04270d94690d0468ecf458ccf458acf4ca5d34ca3d34ba1d24270d94a9dd2499bd14999d14897d14270d94794d04792d04690d0468ecf4270d9458acf4488ce4270d94ba1d24b9fd24a9dd2499bd14270d94897d14896d14794d04792d04270d9468ecf458ccf458acf4488ce4270d94ba1d24b9fd24270d9499bd14999d14897d14896d14270d94792d04690d0468ecf458ccf4270d94488ce4486ce4384ce4b9fd24a9dd2499bd14999d14270d94896d14794d04792d04690d04270d9458ccf458acf4488ce4486ce4270d94383" + } + ], + "outcome": "refuse", + "reason": "identity_invalid", + "at": [ + "identity", + 0 + ] + }, + { + "name": "identity_unknown", + "about": "a track record whose identity record is not given", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [], + "tracks": [ + "{\"frames\":[\"37e2ec4346647c7572345c0f78fe9e5b3ebcf9decd9ce9cfeace5271454d9739\",\"07053d900fdd34c7526a548e0ae3c0b885a89e9efb373b1052ecb023b53c874b\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"74676a381d3796f67de607e3801d608795c77b2fe2755b9aef4ea4b1745375bf0e758bfc954a5cf395448583422475801725c95659bf0e4d1ee1974893064a01\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce\",\"sig\":\"1e3bbbdeddc863b36402b304cce9ca61e01d3a3a4eb5580a124481421242a28169406e17d02cbf3c45d4eb71532b16bd7b48f7467eef1afca2f67f7e23273608\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c" + ], + "generators": [], + "priors": [], + "outcome": "refuse", + "reason": "identity_unknown", + "at": [ + "track", + 0 + ] + }, + { + "name": "driver_key_mismatch", + "about": "a second participant signs a segment over the first one's face: the track record's key is not the identity record's (§8.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":[\"37e2ec4346647c7572345c0f78fe9e5b3ebcf9decd9ce9cfeace5271454d9739\",\"07053d900fdd34c7526a548e0ae3c0b885a89e9efb373b1052ecb023b53c874b\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"5b1985db2e6e3f5b2981efea8fe220cd8ed2800cbe47b02d790333caf897fe0259e023de964f5f6b931cd1246e6416be0a6851f61423a994dd238f651f53ad03\",\"signer_pubkey\":\"07c1966be87d4fc361914b76ac2416938dbd6817efd85650c26477fa0d0987e9\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce\",\"sig\":\"1e3bbbdeddc863b36402b304cce9ca61e01d3a3a4eb5580a124481421242a28169406e17d02cbf3c45d4eb71532b16bd7b48f7467eef1afca2f67f7e23273608\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c" + ], + "generators": [], + "priors": [], + "outcome": "refuse", + "reason": "driver_key_mismatch", + "at": [ + "track", + 0 + ] + }, + { + "name": "track_chain_broken", + "about": "a face's first track record has sequence number 1 (§8.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":[\"e04c9b2a47d0b88cf2c980158a58e4056cb65ea026c64341b3d5a88637c58ba1\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"b8636d103036a725f44b8c2437fbc903b8db831da885eb7d1820abdca77990ac\",\"prev\":null,\"seq\":1,\"sig\":\"c5336c7ce4e3f32ea9dac53dcf6c6c917a649f33c33bee89936fbc7a9eaca030b9015605f9129c8a069bac01d0ddd4d375ad1890763e0d03e2b3b37668649007\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"64445745627a935c95a67e17c5e5d400de36c2c4c2481036a97db68620705860\",\"sig\":\"e2c5a9d7ffdfa0e36d3d5ed6115d75c05d23e8106aece9905c5bbcebf8d2a5f31cb0ec95c30af79b86556fb76c354ab6fb447f16852777630ee1db0d73a1840f\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"b8636d103036a725f44b8c2437fbc903b8db831da885eb7d1820abdca77990ac\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9400000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c020000000100190001000000500082008a007b004f007f008a007800500082008a007b006c009b005b00ba009000b4007400b6007500bd008300df004d00700088006a0032009500c1008600" + ], + "generators": [], + "priors": [], + "outcome": "refuse", + "reason": "track_chain_broken", + "at": [ + "track", + 0 + ] + }, + { + "name": "object_unknown", + "about": "a track record whose segment object is not held", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":[\"37e2ec4346647c7572345c0f78fe9e5b3ebcf9decd9ce9cfeace5271454d9739\",\"07053d900fdd34c7526a548e0ae3c0b885a89e9efb373b1052ecb023b53c874b\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"74676a381d3796f67de607e3801d608795c77b2fe2755b9aef4ea4b1745375bf0e758bfc954a5cf395448583422475801725c95659bf0e4d1ee1974893064a01\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce\",\"sig\":\"1e3bbbdeddc863b36402b304cce9ca61e01d3a3a4eb5580a124481421242a28169406e17d02cbf3c45d4eb71532b16bd7b48f7467eef1afca2f67f7e23273608\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [], + "generators": [], + "priors": [], + "outcome": "refuse", + "reason": "object_unknown", + "at": [ + "track", + 0 + ] + }, + { + "name": "payload_invalid", + "about": "a segment whose payload has a byte after its padding (§7.4, trailing)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":null,\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"c584fb5c38ea01d99025d8e1e07c1a340c28f0266b2ca17d38e0a408c8d98bfd\",\"prev\":null,\"seq\":0,\"sig\":\"72e59ad7823a4c07dc456727d8387cfa4afe007cc8e087f3e61d77235b8746a213a1fc1bde9636781b6643e499c2a3c3c23eda2f688a5b0686225c2d349f530a\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"4eaf5d6521a079fd952454bc988e6b2fc028169ae99d2d97462df1fb3df866f8\",\"sig\":\"58ea2056309a3eb82b9a4f79e5c12d8ee14a9c86d0f09c83c4ee48ec688f5a549a5df4d3509f31d247d87f8f73869c9b04b11c73c876ea4de137a8b84f896b0a\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"c584fb5c38ea01d99025d8e1e07c1a340c28f0266b2ca17d38e0a408c8d98bfd\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9d00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c00" + ], + "generators": [], + "priors": [], + "outcome": "refuse", + "reason": "payload_invalid", + "at": [ + "track", + 0 + ] + }, + { + "name": "generator_mismatch", + "about": "a segment whose envelope pins another generator than its payload names (§7.4)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":null,\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"452f2e636e7359d1889f4f8f508af47c3f8a4c958c546a721e684dd1ddfc5ec3\",\"prev\":null,\"seq\":0,\"sig\":\"3e12ea41f630c2b9d54a84132639ac233f4cdf48e4513701b84bde859b687caed10c52b69260eeaad9feca7d1055adf17136977db9c6196b11caff763d955801\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"9a7c2b135a488f65b6b70757eea838bb2214503a6fdfe1eb58313dbc0d73769e\",\"sig\":\"ed5b277a0e299b8b6c2831ce95813cca0751a6135cd50daa739cf32021a69717ef5ba894ef3122a5bc1f31bb23acb51d90b3f21c4c6c131c1b9982629d37600a\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"452f2e636e7359d1889f4f8f508af47c3f8a4c958c546a721e684dd1ddfc5ec3\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2265313930306131313232333135386665346261323466323064343336656536396539346461616464616434383063666361363465313364643964626263383762222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c" + ], + "generators": [], + "priors": [], + "outcome": "refuse", + "reason": "generator_mismatch", + "at": [ + "track", + 0 + ] + }, + { + "name": "identity_mismatch", + "about": "a participant's segment whose payload names the other participant's identity prior (§8.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":null,\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"67a960de9478f7bdd0399eb501ac38c50454c416804810cd012795bb1803d354\",\"prev\":null,\"seq\":0,\"sig\":\"fb750bb156811020b5853724f26c7852a1f023ba50f14b9246141e204107f0b4333540f882012864d81b5bf32940ae2ffe03818330a74e6c434052f55ca0900a\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"54ac4086a59a6e391217b5a54c68e43e619fe76948c5cfe04be2f1f006a50880\",\"sig\":\"8b082a891c384a505380e255136b96f8a2ce51ea8f389efe68af03fe27ff06e6c8aab9f083b1273c36fcbf7d019d9566f566b796f9d24e229194880dac0f930a\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"67a960de9478f7bdd0399eb501ac38c50454c416804810cd012795bb1803d354\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6d87d24a2e5200d7c7cdc83215a73b649f3ac8fbdc6922949ef211ed9dc1c5e1b0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c" + ], + "generators": [ + { + "file": "generator.wfg" + } + ], + "priors": [ + { + "file": "identities/face1.wfi" + }, + { + "file": "identities/face2.wfi" + } + ], + "outcome": "refuse", + "reason": "identity_mismatch", + "at": [ + "track", + 0 + ] + }, + { + "name": "generator_invalid", + "about": "a segment that names a generator the verifier holds and that does not read: the seeded generator with its last byte missing (§7.2)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":null,\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"2a851fdfb26d5e15db837c38a1e776a98325f36f07072b70a10eb2d8d0871223\",\"prev\":null,\"seq\":0,\"sig\":\"ca90287d258e7b6ad57e3f6029a23a03b8a74d5343df966a6f923332846d275d0141b1d15cdada5c339b329f8a505733909108e457f136937ff39e3920978501\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"1285e333a6406d80b80b42b1c3e8343615282ec5f237774305734fa5f9099d59\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"04d734d268325cf8d42b1e8c0b01b1b456865251347f258248be040393ce69ce\",\"sig\":\"7453e468539f019d597041baa21c08f72213aae52fce824d1d70e90829c00a8ebcdc6fc5c1b2e0efb5ffd34b86925636ad0f816fca534d98218496dd47047101\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"2a851fdfb26d5e15db837c38a1e776a98325f36f07072b70a10eb2d8d0871223\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2231323835653333336136343036643830623830623432623163336538333433363135323832656335663233373737343330353733346661356639303939643539222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9400000057464b31011004001285e333a6406d80b80b42b1c3e8343615282ec5f237774305734fa5f9099d5942e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000001001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c1008500" + ], + "generators": [ + { + "hex": "5746473101031000100004000400040000089a1900009e074800ad02fef9c801e6fb44fd7c01d102b5050907ad0032fbabfab40068fe5cfe6305bffd58f9ec022c00ec0230046f079dfc96fcd6f9fbfef6fe0efabb075b010406befca202c700c0f8befaa7ff06fde0fdd6048dfd42fe4d0057043ff965fe9d0558fe2cfe38f9fc05b9061702f5f840f96d0643024cfe9805f902d3fcba0636037906d905a9fd2a0756fbb90365f849fded03defa8f050305de00a9fcc2fb40043dfce106bdf93af896fcbafb94fa1f00900757fb22ff460562fafafda7ffb4fed7078d03d702bb010c05c702cd07290607fe22038000cd072b075a045efdd9071e05ce004206bd02b0fc35071c00ca072c02f4059402f3fbf6fa4bfaf2f842077cfbedffaa01b600100367fe88fb9c0458f82f074904bffecefde30686071afaa900330415fd2206b8fda0fc31025401befe400499010df90d0090fed2043df8b4fe1cf871fc60f86e024d0223fe110550fffbfd43fd6c05810308fce40510f9740447ffab03d805910349075e06effde206910100fa08f82c06bbfd08fda9fdcc0514fc65fdb70589078f04ea05360554fd140775063607cdfd46079df8bafef306e4ff790246040703b404a2fa68034b076906c9044bfa17063302c9019dfd7207a0026e0029f93a0289fdfc063bfcfefa6600b8f87003e7044902890346f92504f70134fb04045afdb305ddfd60038e0195fab103a4ff6502f0fd850677f8c6ffcf03400700fba9fd7d05320791f9dfff090273064104020736f8fd07b1f9e0f9d30590fc93ffeefca100b20334fa45fb18fa92feb60608fe66ffb10716fa92ffdffc1204ac047afcc205c6fc990754fac7073804f5fda1fe3af994fc390019fc15fff4fb9afce1f9f5fb6f036e019fff01f9aefa8afcf6fc7b005106d3f8c3fe37fa001ca7ff00e46efe00a04b010028a1fe49f9ee03590663fe09fd89057efc3102d3f84000cffca7ff3bf8c80186fca9fdca03fcfceafdfffe29f9eb073cf8e6fadffa8a0412fd60ffac06f50076fe7c06cb02660001f99402b4f9ea023afc300424f94cfc0d00630670fe16fa5706c1f8e9f955f80e03b5f8000415fe8efd51075e0673ff96fb5b026e057a0533fd7b0105fa3b0710039d01c905190269faad033aff82055c04d6fae6066c049507020227fbbcf9d601390214f86904d4063dfd4e07c103ad05cbf8ecfa39fe350613fe2a05c6ff94027e03150471f92205cb04ea073d0058fb5e03d307090196fe1af80cfc70f951ff3efb29fd640519fef4051ef9eef9f9fa82055ffae9f9fe07a1017a0011fc5002ce0161f841fe7efc040604f83aff09fe6903e3fc9604ccfa3903004c550000941601009c0000002465004b06ad009c04a60376fc16ff7d02cc06d5f91507f5f86efbc30492fda8017dfa2b0684f847fe2df821fd4dfefffcf502450202f96dfe85013802d9fee9fbba0786fea2077b0090fd8df9900372ffdfffb90440fb9cfe74011bfd52fc16f9fdfa67ffebfa3ff887fe50fea3fffd03a100c3041005befb18ff580006fe6f076105cf03a706a4024cfc16f81ffad5007c01f0fb6a031301b3fd4405c302860551fe78fa07fc15f8a7016102330410040e053306770230fb4700b601a5ff94feac06330664017302cefaa9fc3eff30f9e606c7fee3fe02fc85f8db05e8008eff8e0795fffffafe07dffc9a004804adf8cf04b4faa1fb1604d2058502f503d007dd06b2fe3a0749fc85fcefffdbfadef9e6071cffaf0452056df9420167078007e507000097fe00b899fe00346b00002cb7ff41061dfbe4f8e505f5ff2dfcc6078efa99f88d0773fc45fb99fc49fc180675fb85008a06ad06f1ff19fc31fd6e04350286fb05f883f85403e7fcc9fee6fb99fde9fa5ffa6b069a0448f809fed30095fba2fe8bff4efc8c03af00fffc1afbc7fb91fc55fb48fa22ffeffb89fff7fa9c01fdfa0107ab02d5fba00244fc97004d02bffc9bfab9009dfb1a053301bf0510febdfc9c0161fd6d06b0014dfa2f04ba0034004b0752f89506f3fbf6fd46006aff62fa7907f30726fb20fcf003ee02b300def84802df01aafa3f03c2fc45fbc5f936ff16028107b9fda107b0fa15fba4ff9b025dfeb7f839039b054cfa76077102cdfca7fcac01ca03aaff99058a066efca2f87f04cd01a5fee600830195fe6f05d60100fb68002607d8f84505ceff51010004a60100f459000078af01006872012e0781fb5afaadf87df9f40376fac4fe890297fcbdff8007fdfec4feb0fc7f00e6fe1d019f0358fe7b02b0fe16fc7cf8c4f9d90703fbf9fc7afc75f8540489fed903c7fe63017e03ca034fffe6fd06fb090500009ef82f00bbfbeafb74fa79018bf9abfbe6f963046e03fd0711fb6a0728fa3e0603ff29f80efcd206c80213ffc5fb88fad604a5fea1fb750535fa09ffc202b303050405fac5fd19fde2fe5a068efd1a06b8034e0633fe94069100e200ff02eff89c04c1ff8d0626fda902040349f82b049bf92d01b003a7f8170602fcadff870758059efe2fffc00133069c0723f8490234ff9a0033011902a9fe13fda9061f0073f9d107f2ffaff813fa8efda6049dfafcff2ff97105dbfdb5faef052ff840012cfaef073907f003a9fd23fc00c87cfe007418fe006c4b010024df00c7fdb5fc65fd8bfd9b0394fa4efc9af802fa9902f1022806e5072b0686fb2f02fff99a0617fdf3f9bc0274fa9b06df0499fa9f011e072d05a8f82dff860521fa8efa310706fcf902fefb7d023eff4af9dd029a043707aeff0f06fe04f003ee04edf90a055dffe7fe88ffa5f8bd0551f8fbf93801f0fd86029506a6f839fb840781fd5704b7fc6203d60313fa120229fccafdea07b2fa21fc7bf90f06f4fb70076df962053705c5fb80fbe50550000d0355fe290338fde4fbf30561f94af8e6fc21fa2203a7ff6b0366f8f2fd3cfb830742fabc0009fc08f898fac8fa5600130747ff1b0631f97ffff7f8e40468fb4a04ae05f0fad3f9460482f940f9540119fcdbffcd054cfd6205c4fb13fab503e701c9fa5d07b803930314fec1049a02ea0503fcc4f90b05b3fbea0466035602db010cffb5ff1ffe8df972043bff100175fe66fb790146f8e2ff68f83c07bbfa7f0639061507b6f88604d4fd94008c0670fe89025afb00fcd4fa55fe08ff0700b7f93104cc0560fd0e07bf0302070d056d057efa55fde407bcfac106d4fa3f023e072c00a5f986f947015b03e7023d04e6002804f2062bf828f9d0fd2105b5fa74fd0e02f2015bfffe0190f9b7fa83fcd903620713fee9fcd3fcd9f94afe7403a4ffd8012f04b1feadfae4f9590795014cf8b100d1fc16fb94fd30fd06fcf303c9f866f959f843ff720649f906067302a0fcf3fe92fd6503f30060fb3d007ff8280540fa52faa5f950ff6a001dfe8f065500b801eafe07faa007ddf9bcfa55f92e02b0002efb000558fc430355ffdd0624056e058c05e900cbfe99072af82bfaf1fe30ffa5054b070405effb84003efaccfb83045806140758fa04fa8afbfafd00fa4a0603f9a40499fad0fdbf071b0178facdfdf7fe38ff96f806ffea04c404c1ff780777044df986064104b9fa200149fad0fc18025d0675fb9300e9ffb705ddf9d3f96c031bfad10796f802f80b07a4f93202ac0755fda304ff02bdfe8d077d0487fc6cff70f8fe026e00c2051a039700f100a70236061c03c801e8fd72fd000694077fffc100b2000001bafbe9062c0075058b06eff98403c4003dfe3ef9b9fd980355fa70fe7f01ae0382fcc5fdf8fc9f03ac0261fc9fff7a07c7f93d01a401c5fc810348fd91047a03c2fd57fce104b2fe84f8ce0356058bfc7801fefd830532fd1af938fcfcf95b0224022f00c9f988fc2503670411ff9c04aaf82a006d01fe04d2fb49f8a102ff0736fc92fdee05a0f840fb9efb21f803fcdef822fd8bf880037dfd1606df03f5fcb5f838050f0036073afe6ffa88ff4c05dcfda5075502f1075dfeec0164034403a806befd73ffaafbde04c8071eff76ff74f8a001a0fbc2fff5fef1fdd50506029f04e80558026503b3061ffdc4fc0afb9afeebfcda0611f912fa3afcc3059afcfbfd27fb2502ca05510607fddb0266fa04ffccf982003dfc95010efe3dfe5efd7406360055fb2efd6d063007c50305ff80f8ee011f05c405e9ff3c07bbfc88f8f8027701b3f85505bd03dbfc18fe97fe80013dfeb6f86605bc009603660156f956f9c70667fb0eff57043affa3fcadf8a7f8b9fd7201a0fbdbfed3064802cc069106eb0488fa009056fe00f4440100341e0000b4800000b8c6ff0088d90000e8fdfe00181dff00143e0100183ffe00508ffe00fc7eff00e0dbfe0038cfff002c94ff0098670197f9d9f979fd110761fca9fba20255fdd800ee015400a101a4f91dfba1fabffb20fa2002650181fc780324f807f84df9ee062c03e4056ffeffff56fcadfe920708fe79fe40fad7f882078706460648ff6e04b7021b07e0012a07a20130fb8400f3fa8df969f8b7fb790458f980050df8e0fbe4fefd0080015bfc70fe71fb5603dff8c8ff26f9f4fd1b01d6023c02f8056bfd290519ffb0fdd80520fabe04f8fa1005d3fdbffd71046b067803def9def8da0714071a07880359fb4ef946fb7206adfd32fea105350678fa58fefc004801e9fd75fee4fe09fbeff9b5fc560259fe96fb34feb3f92e05a8fcd5fc4d042cfee9fec70283fc06fe99fe01fd4c01affb3bfaee06140768fa500600018bfdb90338027406c003e803da02cd0738fec3016bff6101f0faf4f8a2f801fcba01a9fa7607effabc022a0597fcc8f85e056807c9013c061c0704fed9fc7bfd16fd5cf84bfdee04e4fcbbfa52fecb008a03fdf9d4f8a80098043202d1067802fc067f055c0461f9540503fa89066ffdb200cefc1501e904ddfc32049206b8ff2603ec009d02b5fd5e0736fa42fda50339074203c304ea028000120644fc5b05d0fe990615f9b9007304ff0777fc6ffccbfdba02b7fa91fc20fb61fb9e0197060df8abf89efcc9f9c50347fde1fdc1fa7406fefa5f00fd053a07a1fd6103f4030e06eefc37032ff90afa6800fff9ae0221018803cdfbe70158044901a6ff1f04220263fc17ff92fed0f94500df065406840010021afe58067afe9901a9002afbbafc13faae004ffea5f96d079ff975079afdf5fc00441f0100cc210000f0c10100f818014dfb27ff20fbd5fd4f053607de0622f987fcc101bffc4bfa2efc1afd33fb25ff6af9b1fa54057af93504ce0581f834fe47ffd0fa1a05a004a3fdc9fb2b0163faf804d4ff06ffde06ae043407d00452fbc5ffa9fa7107fd008e04b4026e001ff975fd20015207a904880464034affdafdc5029ffd20fb5af8eefbbc04b9f9f4f99df8130235f9ee06be04e303b0f87cf911025df87f05cc059206c0fa8307970164fed707b207640678072001aef8ebfe730715023f053dfd98f887fe1f06750685fd27f96bfbab013100dc067dfeac05de033602d4fec4fc3bfc64ff99fd8504aefefb008aff07054b0426ff8ffefd0552018404c8012406ca066bf99105f205940381f80efff1fcfefe3d03d50187f954030306affbb5f8470449f9dafce0f96407440564fadd0599fcd3fa56039b0421032af90604e9f8780321fe6f0560074c0464f8e2feaa0283fcc901cf04faf9600698fd8bf9a6f8fb015af97aff830549ff88fa16fe7e008102eb06050476fd610015fd1bfca8015500ad01fbfa78fa29020efff50458fcd6070005bc0287fda3fddf063afaa0fca20067fb46057b00b1fa1e05d3fa46ffcafecefecb00f6fd27fa9cfc9a0239078e0616fe5ef91afd5b04f5fb8dfee807ce0127febcfafa07b2052200e5fb6e01b20493062e018f0263ffe2fcd3fff70100fcd5f93a041dfae7fa27ffd0f89206990103fd52fa70024afd5106670073fc57ff8dfae5010904230498074ffdf9054b018e06690504fd8c037e06240315faf8020f06a6fe4907b607dbfc32fecefa9e0637fbe2ff4a0167f83ffb12fb04046401b3fad90088f9eb0781059100abfce50251f89c000b032bfe470742fbaa06d30018033701fe0326fca20422fe09f8b7f92ffc7801cb0504007bfc5dfd73055c01b40340fd1f073306f801dbf8a501b1076302bef805fc1100450178f9c6053bf82d005d06ce00ea037b03bcfe21faeaf8040144fb9006e4fe6e03ce0329fd5cfdfe03af0132fa0a07e3fa02f86aff1203fa007fff640307ff0705e8fee504a10416fb750513fd83f970027b0383f80007af074c0035fc2300f5facb02080170fac6fcc306ddfdf1fd47fce5007106ee0675042dfa85fa9902e005c002bcfb96f8fbf943fa1c02bd0237fc5b052e05a9f9a0000ffd75ff4cfef1fb29f845f9dd070af899fae701070753fb9901cffe0206f9fccf03fb0334faaf04480574001405c60712ffd502b506d3fcad071c00d205e20774fb7004ddfae80631f94efd5203b405b9fec80460ffd80441fb1705e007bcfa860153fbcd0383fd0b0347071ffbeefac7f9fb075d01dc03dafe60fed5f94b0125f9e30665fb520280fc6407fcfb87f8290158078cfd6b0054019f0756034906b3ff900530f843fe0d038b050205710643fa5e0608f984053505fef8ad0272068b012905a4f99efa11fed0f861fc4ffacf02e705a2fc96f9cd01f701cc02c8033d028afb4603f303aa042cf89cff88fb62033af949feed058fff550545f8c6f97afd1600bdfe6c02840154035b05f0056b0551ff69fd4302cafa500314fc3cfa0d0798078f0688f85b04c5ff280713fa2ef9ebfff20783fcf001f7fb5404d30501f805fb00ecbf0000246b0000cce2010038b301009c6b010028380000b03fff00dc29010044350000dcec01004096010010d4fe00d466fe0068a9fe0068f3ff0004c9015306a205f40368065dfcb8fca6f870076b0008002dfff7fab7fadafb8805de00a8fe83ff9efbeb0233ff3d060602a50736fcf9faaf071502ad05aaff18faf6fa81fec207bbfcf5fdc402d90453fd8c0491fc8101a4fe3b0553fc34fe9f01cffec805d303e0fdfff89902d302db0166fe70073ffe2f00f6fdcb0063035bfc74fe3b03cdfaa0fe4a03b2007cfe5ef944f86affbb0229fc9f01f9f97a00bb069103c0f98efd27fdbefd690708f871facbfd5904c203a9fb8500daf8d8037f06a6fc05fec1fc08f8e0f8aef9c1028601defbeffd25065001acfc000777feb1fcd8fe99fc5c02ed0170f8bafa360670fedcf82ffd41041a005af995ff0900d600b7070e0714fa5af91c02f501c5fdab037a0164f9c7010f02da056503d7fadaf953fc6bfcfefe01fef3022b0779f98500f1fdb0ffa607d807b4077903cdff1efe74fb07ffeaf96b057d022ffdba0017fa0efb59fb46038104300496fa7202680315f922f98001d8fbc8ffb0fb19faf2fd5c018c0160f97f0443fc30fc83034807d7fb67ff47fcd8fe690251070505a2fd9b07c5f9bd024efcc50360fe2efaedfc16fc9902c00335ff56044ef9e7026105720041fc1a045802f9f9b9fda7fefc0536fdb4ff8a02ea0490fcb1007f0390f9d404f50326020cf92dfd3afb4cff5ffcfffb2ffaf5f8d4fd38037af8a2fbde05a7001a01f905f2007bf9270238f82f04e8f8ed06000005fb4d055bfcc00483f9eefd74f8d406a0ffcdf8d2f9ca0421f811038cfd8bf9c5fbc9f857fe25053e015607f6059e0272026206f0fa86f893f886030008d6ff006459fe00f033fe0088f800a7073bfbebfb67ff71fd7302bdfe8ff9000092fb93f869041c0444ff6003370023fdd205bafd8cfa3d0613fad2024df8680304fb72f931fd3df9f7fd9bf86203b1ffc400b7f8da0293f91e044803a4fd1303920022001701010520fd0306410609fb4bfca7fdef06c6fa05f80700fe07a8fd80fe56fcb2075bfe59f9200322055f050dfa44f8c1fc1606f800b1fbcdfd870594052c007b017effbf0459076a0463fd0102df007701cbf920046bfaebf92b0130fb0e056706f40410004006c8ff50fa38035dfab505dcfbe3fb740444feabfb97038f05f70009fd0905effe5901a7f9f00113faecf8dbfb46019afe16fd9bf953043f0197fa8d00b9fcb7f9f0fd6804eafa65fbcc0580fcb503e8ff2ef88b071707cafc810526ff6a012e016c0300fc43fe000478fe00c4d8ff003cd0" + } + ], + "priors": [ + { + "file": "identities/face1.wfi" + } + ], + "outcome": "refuse", + "reason": "generator_invalid", + "at": [ + "track", + 0 + ] + }, + { + "name": "shape_mismatch", + "about": "a segment under the seeded generator (three keypoints, 16 × 16) over a face of sixteen keypoints at 64 × 64 (§7.7)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":null,\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"7b17ba955aaf012cccdeb822d0f8161c89b5241d353c7492c4e4aed6e08e76d5\",\"prev\":null,\"seq\":0,\"sig\":\"8cf5d0db46cdf985aaa60c38e78e808e57542dcb682e4debec00223204eee9f124219ee035337029d29037ab4a6374adf8ca969ec04cdfcf1d496f1e20a36c0e\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"e1900a11223158fe4ba24f20d436ee69e94daaddad480cfca64e13dd9dbbc87b\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"6f520cfb8976323a322ac632935196cf1d475de545f803bb4d29bfc051a5e13c\",\"sig\":\"09afa7a6a57acc398d63babc03daf9ed28b96e0c91649c9944c4a3b9bfdbb2ec1fed50bf848383e942babde0bd7542055657518cf0654448b95f1b7df6163c02\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"7b17ba955aaf012cccdeb822d0f8161c89b5241d353c7492c4e4aed6e08e76d5\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2265313930306131313232333135386665346261323466323064343336656536396539346461616464616434383063666361363465313364643964626263383762222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9400000057464b3101100400e1900a11223158fe4ba24f20d436ee69e94daaddad480cfca64e13dd9dbbc87b42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000001001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c1008500" + ], + "generators": [ + { + "file": "seeded.wfg" + } + ], + "priors": [ + { + "file": "identities/face1.wfi" + } + ], + "outcome": "refuse", + "reason": "shape_mismatch", + "at": [ + "track", + 0 + ] + }, + { + "name": "frame_mismatch", + "about": "a track record that states the other participant's frames: the generation gives others (§8.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":[\"52b15513dd3c672b14da6443b5db81887b935f13b056ed7a4e748f524baee0a1\",\"e0a23076ac6fcd7f078bab64559ab5b8f07572297040e459a221fc0383370bd9\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"c8ef60c5b95f3e09210abeaa7d28bd965530958d8d439c2c81bd2b2c5d2ec95aa1b86ccb7f70dbdc27f938521cb16f7885cf604c4a4da78b85827dbc282aa009\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"f518d043f1eff4a8b37efbfa11824850a7c0ee27e17b6e1e93cdb34bd84dbaf2\",\"sig\":\"bdeeeacc454792b08752230684009b23ba918e55c98d3826a0994b2640b08dbe8d3914c4111f895e4c225d4e2d5b97d521053012bde25bda99dc10dbfa8be301\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c" + ], + "generators": [ + { + "file": "generator.wfg" + } + ], + "priors": [ + { + "file": "identities/face1.wfi" + } + ], + "outcome": "refuse", + "reason": "frame_mismatch", + "at": [ + "track", + 0 + ] + }, + { + "name": "generation_not_proved", + "about": "the policy requires proof and the generator is not held (§9.1)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "require-proof" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":[\"37e2ec4346647c7572345c0f78fe9e5b3ebcf9decd9ce9cfeace5271454d9739\",\"07053d900fdd34c7526a548e0ae3c0b885a89e9efb373b1052ecb023b53c874b\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"74676a381d3796f67de607e3801d608795c77b2fe2755b9aef4ea4b1745375bf0e758bfc954a5cf395448583422475801725c95659bf0e4d1ee1974893064a01\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce\",\"sig\":\"1e3bbbdeddc863b36402b304cce9ca61e01d3a3a4eb5580a124481421242a28169406e17d02cbf3c45d4eb71532b16bd7b48f7467eef1afca2f67f7e23273608\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c" + ], + "generators": [], + "priors": [ + { + "file": "identities/face1.wfi" + } + ], + "outcome": "refuse", + "reason": "generation_not_proved", + "at": [ + "track", + 0 + ] + }, + { + "name": "claim_missing", + "about": "a segment with no origin claim (§8.4)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":[\"37e2ec4346647c7572345c0f78fe9e5b3ebcf9decd9ce9cfeace5271454d9739\",\"07053d900fdd34c7526a548e0ae3c0b885a89e9efb373b1052ecb023b53c874b\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"74676a381d3796f67de607e3801d608795c77b2fe2755b9aef4ea4b1745375bf0e758bfc954a5cf395448583422475801725c95659bf0e4d1ee1974893064a01\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c" + ], + "generators": [ + { + "file": "generator.wfg" + } + ], + "priors": [ + { + "file": "identities/face1.wfi" + } + ], + "outcome": "refuse", + "reason": "claim_missing", + "at": [ + "track", + 0 + ] + }, + { + "name": "claim_mismatch", + "about": "a segment's claim names another generator as its model (§8.4)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":[\"37e2ec4346647c7572345c0f78fe9e5b3ebcf9decd9ce9cfeace5271454d9739\",\"07053d900fdd34c7526a548e0ae3c0b885a89e9efb373b1052ecb023b53c874b\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"74676a381d3796f67de607e3801d608795c77b2fe2755b9aef4ea4b1745375bf0e758bfc954a5cf395448583422475801725c95659bf0e4d1ee1974893064a01\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"e1900a11223158fe4ba24f20d436ee69e94daaddad480cfca64e13dd9dbbc87b\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce\",\"sig\":\"3a0b94a86e2f7b921ba046b34a366fab00f816cec85f68b779ccbeb252b32e5e8bd0e1168ef32360a1f83f9850b6be6c02d8f42510d0c451fc14d38d859d8406\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c" + ], + "generators": [ + { + "file": "generator.wfg" + } + ], + "priors": [ + { + "file": "identities/face1.wfi" + } + ], + "outcome": "refuse", + "reason": "claim_mismatch", + "at": [ + "track", + 0 + ] + }, + { + "name": "class_understated", + "about": "a segment claimed as a capture: its envelope generates, so synthesis refuses the claim (synthesis §3.3)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":[\"37e2ec4346647c7572345c0f78fe9e5b3ebcf9decd9ce9cfeace5271454d9739\",\"07053d900fdd34c7526a548e0ae3c0b885a89e9efb373b1052ecb023b53c874b\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"74676a381d3796f67de607e3801d608795c77b2fe2755b9aef4ea4b1745375bf0e758bfc954a5cf395448583422475801725c95659bf0e4d1ee1974893064a01\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"captured\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[],\"operation\":\"camera\",\"oversight\":null,\"record\":null,\"sig\":\"fbdebb4a2574af3176047327bd6744455ca60c78cf1337570214e112df1072c714aa26496f021a8930b3c68495188f3c147e3b749c8f2c9c39993a16ed584f05\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"digitalCapture\",\"step\":\"capture\",\"subject\":{\"content_hash\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": null, + "player": [], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c" + ], + "generators": [ + { + "file": "generator.wfg" + } + ], + "priors": [ + { + "file": "identities/face1.wfi" + } + ], + "outcome": "refuse", + "reason": "class_understated", + "at": [ + "synthesis", + [ + "claim", + 0 + ] + ] + }, + { + "name": "present_output_irreproducible", + "about": "a player that presents the warped pictures, not the generated frames, under the segment's claim (synthesis §9.2)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":[\"37e2ec4346647c7572345c0f78fe9e5b3ebcf9decd9ce9cfeace5271454d9739\",\"07053d900fdd34c7526a548e0ae3c0b885a89e9efb373b1052ecb023b53c874b\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"74676a381d3796f67de607e3801d608795c77b2fe2755b9aef4ea4b1745375bf0e758bfc954a5cf395448583422475801725c95659bf0e4d1ee1974893064a01\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce\",\"sig\":\"1e3bbbdeddc863b36402b304cce9ca61e01d3a3a4eb5580a124481421242a28169406e17d02cbf3c45d4eb71532b16bd7b48f7467eef1afca2f67f7e23273608\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": "{\"audio\":null,\"classes\":[\"synthesized\"],\"grace_us\":500000,\"interval_us\":10000000,\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-label-policy\",\"sig\":\"bab8544cee98aee81f08be063f3764438eeb0fc433400d9528969960483c9e17c730a3357261b3d511e28e989e5d8cf5674cca1c889f757c44d73d6be042f503\",\"signer_pubkey\":\"cacc7690016331baf28bf16ac58c1c10a3eb2bb82041b758aff950ec87c13ed6\",\"visual\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\"}", + "player": [ + "{\"at\":0,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"wai.avatar.face\",\"media\":\"video\",\"origin\":\"5165623a9049ceffb80369b4295f44893dde4b7a62948b56b0fe078d9e5930d8\",\"output\":\"0e38799196037acd8e04c9e1d48f629e132efb2758fcb017274800449b960574\",\"subject\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"tier\":\"generation-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"e3957909c5363e3be89259688fb5313457f659ac5f994e3bbed53419402dc46e\",\"sig\":\"5e57720489a43c5666766e8a78f2b1283f0599c1f49755a0d6148924946cbeb89bc3a9383effde75547d63879d88798522ad97eb4187bef2d1565e56ed2d0a0e\",\"signer_pubkey\":\"83157859a973efc15c1171a1f7d44fbd2a482254499d4844ab5becc7ef0f2e0d\"}", + "{\"at\":40000,\"entry\":{\"asset\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\",\"label\":\"visual\",\"trigger\":\"first-exposure\",\"type\":\"shown\"},\"kind\":\"wai-player\",\"prev\":\"1d7029e1bd4ebd1128d19426ad5c7789359542195b8a168f7ab4a6341ece9697\",\"seq\":1,\"session\":\"e3957909c5363e3be89259688fb5313457f659ac5f994e3bbed53419402dc46e\",\"sig\":\"45c3509882481f5ea40dcc3f0a1ef2bfe94d9ba2e2b61d1b975a5039f74a86fa3c2fcffa8f7415f00f0593bff12a2beeca91eea120a55a8eee3939ecddd58c05\",\"signer_pubkey\":\"83157859a973efc15c1171a1f7d44fbd2a482254499d4844ab5becc7ef0f2e0d\"}" + ], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c" + ], + "generators": [ + { + "file": "generator.wfg" + } + ], + "priors": [ + { + "file": "identities/face1.wfi" + } + ], + "outcome": "refuse", + "reason": "present_output_irreproducible", + "at": [ + "synthesis", + [ + "player", + 0 + ] + ] + }, + { + "name": "label_not_shown", + "about": "a player that presents a generated face with no label at first exposure (synthesis §10.4)", + "policy": { + "dual_layer": "when-given", + "assistive": "accept-asserted", + "second_layer": "accept-stated", + "generation": "accept-unrecomputed" + }, + "identities": [ + "{\"identity\":\"42e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-identity\",\"sig\":\"270b91bfe5f93dbe77764af863b9629f1b07620b25a418c793d6136461261dc43efecdf91e37f9a471b859ecb0098f11d12db7d0fcbaa5bdbe8dd6980d20550d\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "tracks": [ + "{\"frames\":[\"37e2ec4346647c7572345c0f78fe9e5b3ebcf9decd9ce9cfeace5271454d9739\",\"07053d900fdd34c7526a548e0ae3c0b885a89e9efb373b1052ecb023b53c874b\"],\"identity\":\"95b3a54742267bf24728f264686a1e2f9a291b16a3f3c01bacdb12668b9c7888\",\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-face-track\",\"object\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"prev\":null,\"seq\":0,\"sig\":\"74676a381d3796f67de607e3801d608795c77b2fe2755b9aef4ea4b1745375bf0e758bfc954a5cf395448583422475801725c95659bf0e4d1ee1974893064a01\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\"}" + ], + "claims": [ + "{\"class\":\"synthesized\",\"evidence\":null,\"inputs\":[],\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-origin\",\"media\":\"video\",\"models\":[{\"digest\":\"2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6\",\"type\":\"trained\"}],\"operation\":\"wai.avatar.face\",\"oversight\":\"prompt_guided\",\"record\":\"09549746edb734c0c5fc9d181889c68d7942e3abd229b4574f4b8757c32720ce\",\"sig\":\"1e3bbbdeddc863b36402b304cce9ca61e01d3a3a4eb5580a124481421242a28169406e17d02cbf3c45d4eb71532b16bd7b48f7467eef1afca2f67f7e23273608\",\"signer_pubkey\":\"cbe678813e8b93a8fc9c71ccbebab6af13da3529dc6e38d3c18ad2c28a4b1440\",\"source\":\"trainedAlgorithmicMedia\",\"step\":\"generate\",\"subject\":{\"content_hash\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"type\":\"object\"}}" + ], + "reviews": [], + "label_policy": "{\"audio\":null,\"classes\":[\"synthesized\"],\"grace_us\":500000,\"interval_us\":10000000,\"issued_at\":\"2026-10-22T09:00:00Z\",\"kind\":\"wai-label-policy\",\"sig\":\"bab8544cee98aee81f08be063f3764438eeb0fc433400d9528969960483c9e17c730a3357261b3d511e28e989e5d8cf5674cca1c889f757c44d73d6be042f503\",\"signer_pubkey\":\"cacc7690016331baf28bf16ac58c1c10a3eb2bb82041b758aff950ec87c13ed6\",\"visual\":\"503af176b05fd725e68b0aa526977d31bcd657d74b15c6103a57ace81384940f\"}", + "player": [ + "{\"at\":0,\"entry\":{\"class\":\"synthesized\",\"decoder\":\"wai.avatar.face\",\"media\":\"video\",\"origin\":\"5165623a9049ceffb80369b4295f44893dde4b7a62948b56b0fe078d9e5930d8\",\"output\":\"73345136e963e8aa699f1cf85ebb80f7848c6c82c424e6ed4c420378efaf83b1\",\"subject\":\"1f2295f93c7d0a1c38a88a0a6475af023552bb101f87baa95e45631043b0e950\",\"tier\":\"generation-equivalence\",\"type\":\"present\",\"variant\":null},\"kind\":\"wai-player\",\"prev\":null,\"seq\":0,\"session\":\"e3957909c5363e3be89259688fb5313457f659ac5f994e3bbed53419402dc46e\",\"sig\":\"e2c528f58d4dea46db4a7085f1883a99ea65341fc68e640affafc94f159417757e0c8a46dd04530b4463bc2f0590dc571592c9304e23e8e80fc49eb10e03a207\",\"signer_pubkey\":\"83157859a973efc15c1171a1f7d44fbd2a482254499d4844ab5becc7ef0f2e0d\"}" + ], + "objects": [ + "574149312f0100007b22776169223a22312e30222c226d65646961223a22696e746572616374697665222c22696e74656e74223a22637265617465222c226d6f64656c5f726571756972656d656e74223a7b226361706162696c697479223a227761692e6176617461722e66616365222c2266616c6c6261636b223a6e756c6c7d2c22636f6e646974696f6e696e67223a7b226b696e64223a2266616365222c227072696f72223a7b226964223a22666163652067656e657261746f72222c22736861323536223a2232333136663063333766323963313231653138363237613833366335306239353966303333326633333230613333386563316464383134636634663664626336222c2264657465726d696e69736d223a22666163652d696e742f31206f766572207761692d6f70732f31227d7d7d9c00000057464b31011004002316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc642e9c1f9dfd3ae9be323a19324ba8361b79b9981e7fbcc7b870484264540460c0000000002001900010000004e00820089007c004e00800089007900500082008b007c006b009c005a00bb008f00b5007200b5007400c3008200df004c00700087006a0031009500c10085007db97e9a76e4df7c" + ], + "generators": [ + { + "file": "generator.wfg" + } + ], + "priors": [ + { + "file": "identities/face1.wfi" + } + ], + "outcome": "refuse", + "reason": "label_not_shown", + "at": [ + "synthesis", + [ + "player", + 0 + ] + ] + } + ] +} diff --git a/wai/live-conformance/avatar_face_verify.py b/wai/live-conformance/avatar_face_verify.py new file mode 100644 index 000000000..56cfaf363 --- /dev/null +++ b/wai/live-conformance/avatar_face_verify.py @@ -0,0 +1,771 @@ +#!/usr/bin/env python3 +"""Independent verifier of the faces-from-keypoints corpus (extensions/avatars.md §12). + +Written from the extension's text. It shares no code with wai-rs, which generates the corpus. +It reads the three files of §7.2 to §7.4 with readers of its own; runs the driver of §7.5 and the +generator's arithmetic of §7.6 in Python's integers, operator by operator as §7.6 states them +(no operator set, no graph); verifies the origin claims, the player's log and the label policy +with the synthesis corpus's second implementation (`synthesis_verify.py`); verifies every +signature with WAI's strict Ed25519 rule (`claim-conformance/_ed25519.py`) and every link hash +with BLAKE3 written from its specification (`claim-conformance/_blake3.py`); reads §10's codes +from the extension; and implements the records of §8 and the steps of §9.2. + +For every vector it requires what the corpus states: each file's reading, each frame's +digests (and, where the corpus gives them, its samples), each case's outcome, reason, place and +statements. Then it mutates copies of the corpus, each with one defect, and requires every copy +to be refused somewhere. + + python3 -I avatar_face_verify.py + +Exit status 0 only if every vector verifies and every mutated copy fails. +""" +import copy +import hashlib +import json +import os +import struct +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +WAI = os.path.normpath(os.path.join(HERE, "..")) +sys.path.insert(0, HERE) +sys.path.insert(0, os.path.join(WAI, "claim-conformance")) +import synthesis_verify as SV # noqa: E402 +import _ed25519 as ED # noqa: E402 +from _blake3 import blake3 # noqa: E402 + +EXTENSION = os.path.join(WAI, "extensions", "avatars.md") +REGISTRY = os.path.join(WAI, "registry", "capabilities.json") +CAPABILITY = "wai.avatar.face" +SIGN_DOMAIN = b"wai:avatar\x01" # §8.1 +LINK_DOMAIN = b"wai:avatar-link\x01" # §8.1 +OBJECT_DOMAIN = b"moq-jwp:object\x01" # jwp-receipts §2.1 +HEADER = 84 # §7.4 + +Refuse, Bad, need, hexb, integer, members = SV.Refuse, SV.Bad, SV.need, SV.hexb, SV.integer, SV.members + + +def sha(b): + return hashlib.sha256(b).digest() + + +class Form(Exception): + """A file that does not read (§7.2 to §7.4), with its code.""" + + def __init__(self, code): + super().__init__(code) + self.code = code + + +# ---------------------------------------------------------------- §7.2 to §7.4 the files + +def u16(b, at): + return b[at] | b[at + 1] << 8 + + +def i16(b, at): + v = u16(b, at) + return v - 65536 if v >= 32768 else v + + +def side_ok(v): + return 4 <= v <= 512 and v % 4 == 0 + + +def have(b, at, n): + if len(b) < at + n: + raise Form("truncated") + + +def shapes(k, c1, c2, c3): + """§7.2: (cin, cout, stride) of the ten convolutions, in the file's order.""" + return [(6 + k, c1, 1), (c1, c2, 2), (c2, c3, 2), (c3, c3, 1), (c3, c3, 1), + (c3, 4 * c2, 1), (2 * c2, c2, 1), (c2, 4 * c1, 1), (2 * c1, c1, 1), (c1, 4, 1)] + + +def read_generator(b): + """§7.2, in its order.""" + have(b, 0, 4) + if b[:4] != b"WFG1": + raise Form("bad_magic") + have(b, 4, 1) + if b[4] != 1: + raise Form("version") + have(b, 5, 17) + k, width, height, c1, c2, c3, gauss = b[5], u16(b, 6), u16(b, 8), u16(b, 10), u16(b, 12), u16(b, 14), u16(b, 16) + bg = struct.unpack_from("= 1 and bg >= 1): + raise Form("field_range") + at, convs = 22, [] + for cin, cout, stride in shapes(k, c1, c2, c3): + nw = cout * cin * 9 + have(b, at, 2 * nw + 4 * cout) + w = list(struct.unpack_from("<%dh" % nw, b, at)) + at += 2 * nw + bias = list(struct.unpack_from("<%di" % cout, b, at)) + at += 4 * cout + convs.append({"cin": cin, "cout": cout, "stride": stride, "w": w, "b": bias}) + if at != len(b): + raise Form("trailing") + return {"k": k, "width": width, "height": height, "c1": c1, "c2": c2, "c3": c3, "gauss": gauss, "bg": bg, "convs": convs} + + +def read_identity(b): + """§7.3, in §7.2's order.""" + have(b, 0, 4) + if b[:4] != b"WFI1": + raise Form("bad_magic") + have(b, 4, 1) + if b[4] != 1: + raise Form("version") + have(b, 5, 5) + k, width, height = b[5], u16(b, 6), u16(b, 8) + if not (1 <= k <= 32 and side_ok(width) and side_ok(height)): + raise Form("field_range") + n = 4 * k + width * height * 3 + have(b, 10, n) + kp = [(i16(b, 10 + 4 * j), i16(b, 12 + 4 * j)) for j in range(k)] + if len(b) != 10 + n: + raise Form("trailing") + return {"k": k, "width": width, "height": height, "keypoints": kp, "rgb": bytes(b[10 + 4 * k:])} + + +def read_segment(b): + """§7.4, in its order.""" + have(b, 0, 4) + if b[:4] != b"WFK1": + raise Form("bad_magic") + have(b, 4, 1) + if b[4] != 1: + raise Form("version") + have(b, 5, HEADER - 5) + k, step, order = b[5], b[6], b[7] + first = struct.unpack_from("= 1 and den >= 1 and reserved == 0): + raise Form("field_range") + have(b, HEADER, 4 * k) + cur = [i16(b, HEADER + 2 * j) for j in range(2 * k)] + + def check(c): + if not -32768 <= c * step <= 32767: + raise Form("field_range") + for c in cur: + check(c) + frames = [[(cur[2 * j] * step, cur[2 * j + 1] * step) for j in range(k)]] + data, pos = b[HEADER + 4 * k:], [0] + + def bit(): + if pos[0] // 8 >= len(data): + raise Form("truncated") + v = (data[pos[0] // 8] >> (7 - pos[0] % 8)) & 1 + pos[0] += 1 + return v + + def bits(m): + v = 0 + for _ in range(m): + v = v << 1 | bit() + return v + + def code(): + z = 0 + while bit() == 0: + z += 1 + if z > 31: + raise Form("field_range") + m = (1 << z) | bits(z) + return (m - 1) << order | bits(order) + + for _ in range(1, n): + for i in range(2 * k): + u = code() + cur[i] += u >> 1 if u % 2 == 0 else -((u + 1) >> 1) + check(cur[i]) + frames.append([(cur[2 * j] * step, cur[2 * j + 1] * step) for j in range(k)]) + if HEADER + 4 * k + (pos[0] + 7) // 8 != len(b): + raise Form("trailing") + while pos[0] % 8: + if bit(): + raise Form("noncanonical") + return {"k": k, "step": step, "order": order, "generator": bytes(b[8:40]), "identity": bytes(b[40:72]), "first": first, + "rate": [num, den], "keypoints": frames} + + +# ---------------------------------------------------------------- §7.5 the driver + +def exp_neg_q16(x): + """integer-operators §2.""" + u = -x + n = u // 45426 + r = x + 45426 * n + t = r + 88670 + q = (t * t) >> 16 + y = ((23495 * q) >> 16) + 22544 + return 0 if n >= 63 else y >> n + + +def rdiv(n, d): + """sgn(n)·⌊(2|n| + d) / (2d)⌋, d ≥ 1.""" + a = (2 * abs(n) + d) // (2 * d) + return -a if n < 0 else a + + +def warp(ident, drv, gauss, bg): + """§7.5: the warped picture (RGB8 bytes) and the maps ([K][h][w] integers, flat).""" + w, h, k, ref, src = ident["width"], ident["height"], ident["k"], ident["rgb"], ident["keypoints"] + out = bytearray(w * h * 3) + maps = [0] * (k * w * h) + for y in range(h): + py = 16 * y + 8 + for x in range(w): + px = 16 * x + 8 + den, nx, ny = bg, 0, 0 + for j in range(k): + dx, dy = px - drv[j][0], py - drv[j][1] + wj = exp_neg_q16(-(((dx * dx + dy * dy) * gauss + 128) >> 8)) + den += wj + nx += wj * (src[j][0] - drv[j][0]) + ny += wj * (src[j][1] - drv[j][1]) + maps[(j * h + y) * w + x] = wj >> 8 + ux, uy = 16 * x + rdiv(nx, den), 16 * y + rdiv(ny, den) + ix, iy = ux // 16, uy // 16 + fx, fy = ux - 16 * ix, uy - 16 * iy + x0, x1 = min(max(ix, 0), w - 1), min(max(ix + 1, 0), w - 1) + y0, y1 = min(max(iy, 0), h - 1), min(max(iy + 1, 0), h - 1) + for c in range(3): + v = ((16 - fx) * (16 - fy) * ref[(y0 * w + x0) * 3 + c] + fx * (16 - fy) * ref[(y0 * w + x1) * 3 + c] + + (16 - fx) * fy * ref[(y1 * w + x0) * 3 + c] + fx * fy * ref[(y1 * w + x1) * 3 + c] + 128) >> 8 + out[(y * w + x) * 3 + c] = v + return bytes(out), maps + + +# ---------------------------------------------------------------- §7.6 the generator's arithmetic + +def sigmoid_entry(v): + """§7.6: T[v + 32768].""" + if v == -32768: + return 0 + if v < 0: + return 32768 - sigmoid_entry(-v) + d = 65536 + min(exp_neg_q16(-16 * v), 65536) + return ((1 << 31) + d // 2) // d + + +TABLE = {} + + +def table(v): + if v not in TABLE: + TABLE[v] = sigmoid_entry(v) + return TABLE[v] + + +def conv(planes, h, w, layer): + """A 3 × 3 convolution, zero padding 1, of planes (each a flat list of h·w integers), each + output R_half_up((sum + bias), 12).""" + cin, cout, stride, wt, bias = layer["cin"], layer["cout"], layer["stride"], layer["w"], layer["b"] + wp = w + 2 + n = h * wp + padded = [] + for p in planes: + q = [0] * ((h + 2) * wp + 2) + for y in range(h): + q[(y + 1) * wp + 1:(y + 1) * wp + 1 + w] = p[y * w:(y + 1) * w] + padded.append(q) + ho, wo = (h - 1) // stride + 1, (w - 1) // stride + 1 + out = [] + if stride == 1: + # Each tap reads the padded plane shifted by its offset; the sums run over the + # padded width, whose two extra columns are dropped at the end. + for o in range(cout): + acc = [bias[o]] * n + for ci in range(cin): + src = padded[ci] + base = (o * cin + ci) * 9 + for t in range(9): + v = wt[base + t] + if v: + off = (t // 3) * wp + t % 3 + acc = [a + v * s for a, s in zip(acc, src[off:off + n])] + out.append([(acc[y * wp + x] + 2048) >> 12 for y in range(ho) for x in range(wo)]) + return out, ho, wo + # Stride 2: each tap's samples gathered once per input channel. + taps = [[[src[(stride * y + t // 3) * wp + stride * x + t % 3] for y in range(ho) for x in range(wo)] for t in range(9)] for src in padded] + for o in range(cout): + acc = [bias[o]] * (ho * wo) + for ci in range(cin): + base = (o * cin + ci) * 9 + for t in range(9): + v = wt[base + t] + if v: + acc = [a + v * s for a, s in zip(acc, taps[ci][t])] + out.append([(a + 2048) >> 12 for a in acc]) + return out, ho, wo + + +def relu(planes): + return [[min(max(v, 0), 32767) for v in p] for p in planes] + + +def shuffle(planes, h, w): + """pixel_shuffle(2): channel 4c + 2i + j to row offset i and column offset j of channel c.""" + out = [] + for c in range(len(planes) // 4): + p = [0] * (4 * h * w) + for i in range(2): + for j in range(2): + src = planes[4 * c + 2 * i + j] + for y in range(h): + p[(2 * y + i) * 2 * w + j:(2 * y + i) * 2 * w + 2 * w:2] = src[y * w:(y + 1) * w] + out.append(p) + return out + + +def generate_frame(gen, ident, drv): + """§7.7: one frame, RGB8 bytes, and the warped picture and maps it was made from.""" + w, h, k = gen["width"], gen["height"], gen["k"] + warped, maps = warp(ident, drv, gen["gauss"], gen["bg"]) + n = w * h + t3 = [[(warped[i * 3 + c] - 128) * 256 for i in range(n)] for c in range(3)] + t4 = [[(ident["rgb"][i * 3 + c] - 128) * 256 for i in range(n)] for c in range(3)] + t5 = [[v * 128 for v in maps[j * n:(j + 1) * n]] for j in range(k)] + cv = gen["convs"] + e1, h1, w1 = conv(t3 + t4 + t5, h, w, cv[0]) + e1 = relu(e1) + e2, h2, w2 = conv(e1, h1, w1, cv[1]) + e2 = relu(e2) + e3, h3, w3 = conv(e2, h2, w2, cv[2]) + e3 = relu(e3) + m1, _, _ = conv(e3, h3, w3, cv[3]) + m1 = relu(m1) + m2, _, _ = conv(m1, h3, w3, cv[4]) + r = relu([[a + b for a, b in zip(p, q)] for p, q in zip(m2, e3)]) + u2, _, _ = conv(r, h3, w3, cv[5]) + u2 = relu(shuffle(u2, h3, w3)) + d2, _, _ = conv(u2 + e2, h2, w2, cv[6]) + d2 = relu(d2) + u1, _, _ = conv(d2, h2, w2, cv[7]) + u1 = relu(shuffle(u1, h2, w2)) + d1, _, _ = conv(u1 + e1, h1, w1, cv[8]) + d1 = relu(d1) + o, _, _ = conv(d1, h1, w1, cv[9]) + out = bytearray(3 * n) + for i in range(n): + m = table(min(max(o[3][i], -32767), 32767)) + for c in range(3): + g = min(max(o[c][i], -32768), 32767) + a = (t3[c][i] * m + 16384) >> 15 + b = (g * (32768 - m) + 16384) >> 15 + out[i * 3 + c] = min(max(((a + b + 128) >> 8) + 128, 0), 255) + return bytes(out), warped, maps + + +FRAMES = {} + + +def frame_of(gen_bytes, ident_bytes, drv): + """A frame, kept by the generator's and the identity's digests and the keypoints.""" + key = (sha(gen_bytes), sha(ident_bytes), tuple(drv)) + if key not in FRAMES: + FRAMES[key] = generate_frame(read_generator(gen_bytes), read_identity(ident_bytes), drv) + return FRAMES[key] + + +def maps_bytes(maps): + return b"".join(struct.pack("", file=sys.stderr) + sys.exit(2) + sys.exit(main(sys.argv[1])) diff --git a/wai/tools/wai_avatar_face_train.py b/wai/tools/wai_avatar_face_train.py new file mode 100644 index 000000000..5211741c3 --- /dev/null +++ b/wai/tools/wai_avatar_face_train.py @@ -0,0 +1,978 @@ +#!/usr/bin/env python3 +"""Train the integer face generator of `extensions/avatars.md` (§7, the lowering `face-int/1`), +measure it, and write the inputs of the corpus `live-conformance/avatar-face/`. + + python tools/wai_avatar_face_train.py --out

[--steps 30000] [--qat-steps 8000] + +Needs torch and numpy. Everything it trains on is generated here: `render` draws a face from a +seeded parameter set (head shape, skin, hair, eyes, brows, nose, mouth, background) at a pose +and an expression (head translation, scale, roll, yaw and pitch, eyes open or shut, gaze, brows, +mouth open and its width), at four times the output size, and box-filters it down. Each picture +comes with the sixteen keypoints the parameters put on it, exactly. No picture, recording or +footage from outside this repository is read. The generated set carries this repository's +licence. + +It writes, under : + +- `generator.wfg`: the generator (§7.2), trained in floating point, then fine-tuned with the + integer arithmetic emulated in the loop (weights on the 2^-12 grid, activations on the + 2^-15 grid, the sigmoid table), and written by rounding each float32 weight times 2^12 and + each bias times 2^27 half to even: exact from the float's bits; +- `identities/.wfi`: identity priors (§7.3) of faces held out of training, and + `tracks/.json`: keypoint tracks (§7.4) of held-out motion, which the corpus reads; +- `report.json`: the seeds, the steps, the losses, and the measurement of §7.9: for the + held-out set, PSNR and SSIM of the integer generator against the floating-point network and + against the rendered truth, the keypoint error of a separately trained float keypoint + detector over each, and the bitrate of the keypoint tracks under §7.4's coding. + +Every seed is fixed (`--seed`), but float training is not claimed to be bit-reproducible: its +sums depend on the device and the thread count of the machine that runs it. What is pinned is +the generator's bytes, by SHA-256. The integer generation over those bytes is exact (§7.6), +whatever machine trained them. `int_forward` below is the arithmetic of §7.5 to §7.7 in numpy +int64: the integers this tool reports are those, not the float network's. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import math +import os +import struct +import time +from pathlib import Path + +import numpy as np + +# --------------------------------------------------------------------------------------- +# The arithmetic of §7.5 (the driver's warp and keypoint maps) in numpy int64. +# --------------------------------------------------------------------------------------- + +K = 16 +FW = 12 # weights are integers at 2^12 +FA = 15 # activations are integers at 2^15 +FB = FW + FA # biases are integers at 2^27 +ACT_HI = 32767 + + +def exp_neg_q16(x: np.ndarray) -> np.ndarray: + """integer-operators §2: exp_neg_q16 of non-positive Q16 values, elementwise.""" + x = x.astype(np.int64) + u = -x + n = u // 45426 + g = 45426 * n + r = x + g + t = r + 88670 + t2 = t * t + q = t2 >> 16 + c = 23495 * q + y = (c >> 16) + 22544 + out = np.where(n >= 63, 0, y >> np.minimum(n, 62)) + return out.astype(np.int64) + + +def rdiv(n: np.ndarray, d: np.ndarray) -> np.ndarray: + """sgn(n)·⌊(2|n| + d) / (2d)⌋ for d ≥ 1: half away from zero (integer-operators E12).""" + a = np.abs(n) + return np.sign(n) * ((2 * a + d) // (2 * d)) + + +def warp_and_maps(ref: np.ndarray, src: np.ndarray, drv: np.ndarray, gauss: int, bg: int): + """§7.5: the warped reference [h, w, 3] u8 and the keypoint maps [K, h, w] in [0, 256]. + + `ref` is the identity's picture [h, w, 3] u8, `src` its keypoints and `drv` the driving + keypoints, each [K, 2] integers in sixteenths of a pixel (x, y).""" + h, w, _ = ref.shape + ys, xs = np.mgrid[0:h, 0:w] + px = (16 * xs + 8).astype(np.int64)[None] + py = (16 * ys + 8).astype(np.int64)[None] + dx = px - drv[:, 0].astype(np.int64)[:, None, None] + dy = py - drv[:, 1].astype(np.int64)[:, None, None] + d2 = dx * dx + dy * dy + e = (d2 * gauss + 128) >> 8 + wk = exp_neg_q16(-e) # [K, h, w], each in [0, 65553] + den = bg + wk.sum(axis=0) + mx = (src[:, 0] - drv[:, 0]).astype(np.int64)[:, None, None] + my = (src[:, 1] - drv[:, 1]).astype(np.int64)[:, None, None] + ddx = rdiv((wk * mx).sum(axis=0), den) + ddy = rdiv((wk * my).sum(axis=0), den) + ux = 16 * xs + ddx + uy = 16 * ys + ddy + ix = ux // 16 + iy = uy // 16 + fx = ux - 16 * ix + fy = uy - 16 * iy + x0 = np.clip(ix, 0, w - 1) + x1 = np.clip(ix + 1, 0, w - 1) + y0 = np.clip(iy, 0, h - 1) + y1 = np.clip(iy + 1, 0, h - 1) + r = ref.astype(np.int64) + p00, p01, p10, p11 = r[y0, x0], r[y0, x1], r[y1, x0], r[y1, x1] + fx3, fy3 = fx[..., None], fy[..., None] + v = ((16 - fx3) * (16 - fy3) * p00 + fx3 * (16 - fy3) * p01 + (16 - fx3) * fy3 * p10 + fx3 * fy3 * p11 + 128) >> 8 + return v.astype(np.uint8), (wk >> 8).astype(np.int64) + + +def sigmoid_table() -> np.ndarray: + """§7.6: the `lut16` table of the mask, T[v + 32768] for v in −32768..32767.""" + v = np.arange(0, 32769, dtype=np.int64) + d = 65536 + np.minimum(exp_neg_q16(-16 * v), 65536) + s = ((1 << 31) + d // 2) // d # S(v) for v ≥ 0 + t = np.zeros(65536, dtype=np.int64) + t[32768:] = s[:32768] # v = 0 .. 32767 + t[1:32768] = 32768 - s[32767:0:-1] # v = −32767 .. −1 + t[0] = 0 # v = −32768, never read + return t + + +# --------------------------------------------------------------------------------------- +# The generator file (§7.2) and its integer forward pass (§7.6, §7.7) in numpy int64. +# --------------------------------------------------------------------------------------- + + +def layer_shapes(c1: int, c2: int, c3: int, k: int = K): + """(cin, cout, stride) of the ten convolutions, in the file's order (§7.2).""" + return [ + (6 + k, c1, 1), (c1, c2, 2), (c2, c3, 2), (c3, c3, 1), (c3, c3, 1), + (c3, 4 * c2, 1), (2 * c2, c2, 1), (c2, 4 * c1, 1), (2 * c1, c1, 1), (c1, 4, 1), + ] + + +def write_wfg(hdr: dict, layers) -> bytes: + o = bytearray(b"WFG1") + o += struct.pack(" np.ndarray: + return p if s == 0 else (p + (1 << (s - 1))) >> s + + +def conv_int(x: np.ndarray, w: np.ndarray, b: np.ndarray, stride: int) -> np.ndarray: + """conv 3×3, zero padding 1, then rq: pre bias, shift 12, half up (§7.6).""" + c, h, wd = x.shape + xp = np.zeros((c, h + 2, wd + 2), np.int64) + xp[:, 1:-1, 1:-1] = x + ho, wo = (h - 1) // stride + 1, (wd - 1) // stride + 1 + acc = np.zeros((w.shape[0], ho, wo), np.int64) + for ky in range(3): + for kx in range(3): + patch = xp[:, ky:ky + stride * (ho - 1) + 1:stride, kx:kx + stride * (wo - 1) + 1:stride] + acc += np.einsum("oc,chw->ohw", w[:, :, ky, kx], patch) + return half_up(acc + b[:, None, None], FW) + + +def shuffle2(x: np.ndarray) -> np.ndarray: + c4, h, w = x.shape + c = c4 // 4 + return x.reshape(c, 2, 2, h, w).transpose(0, 3, 1, 4, 2).reshape(c, 2 * h, 2 * w) + + +TABLE = sigmoid_table() + + +def int_forward(layers, warped: np.ndarray, ref: np.ndarray, heat: np.ndarray) -> np.ndarray: + """§7.6 and §7.7 over one frame: the generated picture [h, w, 3] u8.""" + relu = lambda v: np.clip(v, 0, ACT_HI) # noqa: E731 + t3 = (warped.transpose(2, 0, 1).astype(np.int64) - 128) * 256 + t4 = (ref.transpose(2, 0, 1).astype(np.int64) - 128) * 256 + t5 = heat.astype(np.int64) * 128 + x = np.concatenate([t3, t4, t5]) + (w1, b1, s1), (w2, b2, s2), (w3, b3, s3), (w4, b4, _), (w5, b5, _), (w6, b6, _), (w7, b7, _), (w8, b8, _), (w9, b9, _), (w10, b10, _) = layers + e1 = relu(conv_int(x, w1, b1, s1)) + e2 = relu(conv_int(e1, w2, b2, s2)) + e3 = relu(conv_int(e2, w3, b3, s3)) + m1 = relu(conv_int(e3, w4, b4, 1)) + r = relu(conv_int(m1, w5, b5, 1) + e3) + u2 = relu(shuffle2(conv_int(r, w6, b6, 1))) + d2 = relu(conv_int(np.concatenate([u2, e2]), w7, b7, 1)) + u1 = relu(shuffle2(conv_int(d2, w8, b8, 1))) + d1 = relu(conv_int(np.concatenate([u1, e1]), w9, b9, 1)) + o = conv_int(d1, w10, b10, 1) + g = np.clip(o[:3], -32768, 32767) + z = np.clip(o[3:4], -32767, 32767) + m = TABLE[z + 32768] + a = half_up(t3 * m, 15) + om = 32768 - m + bb = half_up(g * om, 15) + s = a + bb + out = np.clip(half_up(s, 8) + 128, 0, 255) + return out.transpose(1, 2, 0).astype(np.uint8) + + +# --------------------------------------------------------------------------------------- +# The keypoint track coding (§7.4): what the bitrate measurement counts. +# --------------------------------------------------------------------------------------- + + +def zigzag(v: int) -> int: + return 2 * v if v >= 0 else -2 * v - 1 + + +def eg_bits(u: int, k: int) -> int: + """The length of Exp-Golomb order k of u.""" + m = (u >> k) + 1 + return 2 * m.bit_length() - 1 + k + + +def encode_track(frames: np.ndarray, step: int, gen: bytes, ident: bytes, first: int, rate=(25, 1)) -> bytes: + """A `WFK1` payload of the frames [n, K, 2] (sixteenths of a pixel), the coordinates + rounded to multiples of `step` sixteenths, with the order of the deltas' code chosen to + make the payload shortest.""" + q = np.floor(frames / step + 0.5).astype(np.int64) + n = q.shape[0] + deltas = [zigzag(int(v)) for v in (q[1:] - q[:-1]).reshape(-1)] + best = min(range(16), key=lambda k: sum(eg_bits(u, k) for u in deltas)) + out = bytearray(b"WFK1") + out += struct.pack("> best) + 1 + nb = m.bit_length() + bits += [0] * (nb - 1) + bits += [int(c) for c in format(m, "b")] + if best: + bits += [int(c) for c in format(u & ((1 << best) - 1), "0%db" % best)] + while len(bits) % 8: + bits.append(0) + for i in range(0, len(bits), 8): + out.append(int("".join(map(str, bits[i:i + 8])), 2)) + return bytes(out) + + +# --------------------------------------------------------------------------------------- +# The generated faces. +# --------------------------------------------------------------------------------------- + +KEYPOINTS = [ + "left eye", "right eye", "left upper lid", "right upper lid", "left pupil", "right pupil", + "nose tip", "mouth left", "mouth right", "upper lip", "lower lip", "chin", + "left brow", "right brow", "left cheek", "right cheek", +] + + +def identity_params(rng: np.random.Generator) -> dict: + skin = np.array([rng.uniform(0.35, 0.95), 0, 0]) + skin[1] = skin[0] * rng.uniform(0.62, 0.82) + skin[2] = skin[0] * rng.uniform(0.45, 0.70) + hair = rng.uniform(0.02, 0.6) * np.array([1.0, rng.uniform(0.6, 0.95), rng.uniform(0.3, 0.8)]) + if rng.random() < 0.12: + hair = rng.uniform(0.6, 0.85) * np.ones(3) + return dict( + bg0=rng.uniform(0.1, 0.9, 3), bg1=rng.uniform(0.1, 0.9, 3), bgdir=rng.uniform(0, 2 * math.pi), + bgfreq=rng.uniform(2, 9), bgamp=rng.uniform(0, 0.08), + skin=skin, hair=hair, + a=rng.uniform(0.25, 0.31), b=rng.uniform(0.32, 0.38), + hairline=rng.uniform(-0.62, -0.38), hairvol=rng.uniform(0.0, 0.10), sidehair=rng.uniform(0, 1), + eye_x=rng.uniform(0.34, 0.44), eye_y=rng.uniform(0.0, 0.14), eye_w=rng.uniform(0.40, 0.52), + eye_h=rng.uniform(0.020, 0.030), iris=rng.uniform(0.1, 0.6, 3) * np.array([1, 1, 1.3]), + iris_r=rng.uniform(0.85, 1.15), brow_t=rng.uniform(0.008, 0.016), brow_y=rng.uniform(0.16, 0.24), + nose_l=rng.uniform(0.20, 0.30), nose_w=rng.uniform(0.05, 0.08), + mouth_y=rng.uniform(0.46, 0.58), mouth_w=rng.uniform(0.55, 0.78), lip=np.clip(skin * np.array([rng.uniform(0.75, 1.0), 0.55, 0.55]), 0, 1), + glasses=rng.random() < 0.2, gl_col=rng.uniform(0.0, 0.5, 3), + neck=rng.uniform(0.35, 0.55), shirt=rng.uniform(0.05, 0.95, 3), + ) + + +def pose_params(rng: np.random.Generator, neutral: bool = False) -> dict: + s = 0.35 if neutral else 1.0 + return dict( + tx=rng.uniform(-0.06, 0.06) * s, ty=rng.uniform(-0.05, 0.05) * s, scale=1 + rng.uniform(-0.08, 0.08) * s, + roll=rng.uniform(-0.25, 0.25) * s, yaw=rng.uniform(-0.45, 0.45) * s, pitch=rng.uniform(-0.25, 0.25) * s, + open=0.0 if neutral and rng.random() < 0.7 else float(np.clip(rng.uniform(-0.3, 1.0), 0, 1)), + smile=rng.uniform(-0.2, 0.45) * s, + blink_l=0.0 if neutral else float(np.clip(rng.uniform(-1.5, 1.0), 0, 1)), + blink_r=None if rng.random() < 0.85 else float(np.clip(rng.uniform(-1.0, 1.0), 0, 1)), + gx=rng.uniform(-1, 1) * s, gy=rng.uniform(-0.7, 0.7) * s, brow=rng.uniform(-1, 1) * s, + ) + + +def rot(roll, yaw, pitch): + cr, sr, cy, sy, cp, sp = math.cos(roll), math.sin(roll), math.cos(yaw), math.sin(yaw), math.cos(pitch), math.sin(pitch) + rz = np.array([[cr, -sr, 0], [sr, cr, 0], [0, 0, 1]]) + ry = np.array([[cy, 0, sy], [0, 1, 0], [-sy, 0, cy]]) + rx = np.array([[1, 0, 0], [0, cp, -sp], [0, sp, cp]]) + return rz @ ry @ rx + + +def render(idp: dict, pp: dict, size: int = 64, ss: int = 4): + """The face of `idp` at the pose and expression `pp`: [size, size, 3] u8 and its + keypoints [K, 2] in pixels (x, y) of the output.""" + n = size * ss + yy, xx = np.mgrid[0:n, 0:n].astype(np.float64) + u = (xx + 0.5) / n - 0.5 + v = (yy + 0.5) / n - 0.5 + img = np.zeros((n, n, 3)) + # Background: a gradient and a soft grating. + d = math.cos(idp["bgdir"]) * u + math.sin(idp["bgdir"]) * v + tt = np.clip(d + 0.5, 0, 1)[..., None] + img[:] = idp["bg0"] * (1 - tt) + idp["bg1"] * tt + img += idp["bgamp"] * np.sin(2 * math.pi * idp["bgfreq"] * (u + 0.7 * v))[..., None] + + R = rot(pp["roll"], pp["yaw"], pp["pitch"]) + sc = pp["scale"] + cx, cy = pp["tx"], pp["ty"] - 0.02 + A, B, C = idp["a"], idp["b"], idp["a"] * 0.95 + + def proj(p): + q = R @ np.asarray(p, dtype=np.float64) + return np.array([cx + sc * q[0], cy + sc * q[1]]), q[2] + + def surf(x, y): + z2 = 1 - (x / A) ** 2 - (y / B) ** 2 + return C * math.sqrt(max(z2, 0.0)) + + def ell(c0, ax, ay, ang=0.0): + ca, sa = math.cos(ang), math.sin(ang) + du, dv = u - c0[0], v - c0[1] + ru = (ca * du + sa * dv) / max(ax, 1e-6) + rv = (-sa * du + ca * dv) / max(ay, 1e-6) + return ru * ru + rv * rv + + def paint(mask, col, soft=1.5 / n): + nonlocal img + m = np.clip(mask, 0, 1)[..., None] + img = img * (1 - m) + np.asarray(col) * m + + def inside(q, edge): + # q < 1 is inside; a soft edge of about `edge` in the normalised measure. + return np.clip((1 - q) / edge + 0.5, 0, 1) + + roll = pp["roll"] + # Neck and shirt. + nk, _ = proj([0, B * 0.85, -C * 0.2]) + paint(inside(ell(nk + np.array([0, 0.12]), idp["neck"] * A * sc, 0.25 * sc, roll * 0.3), 0.08), idp["skin"] * 0.82) + paint(np.clip((v - (nk[1] + 0.22 * sc)) / 0.01, 0, 1) * inside(ell([nk[0], 0.62], 0.5, 0.3), 0.1), idp["shirt"]) + # Hair volume behind the head. + hc, _ = proj([0, -B * 0.25, -C * 0.3]) + paint(inside(ell(hc, (A + idp["hairvol"] + 0.02 * idp["sidehair"]) * sc, (B * 0.95 + idp["hairvol"]) * sc, roll), 0.06), idp["hair"]) + # The head: the silhouette of the rotated ellipsoid, approximated by its projected centre + # and axes, and shaded by a light the head turns under. + hcen, _ = proj([0, 0, 0]) + q = ell(hcen, A * sc, B * sc, roll) + head = inside(q, 0.05) + du, dv = u - hcen[0], v - hcen[1] + ca, sa = math.cos(roll), math.sin(roll) + lu = (ca * du + sa * dv) / (A * sc) + lv = (-sa * du + ca * dv) / (B * sc) + nz = np.sqrt(np.clip(1 - lu * lu - lv * lv, 0, 1)) + light = np.array([-0.45, -0.5, 0.74]) + lam = np.clip(lu * light[0] * math.cos(pp["yaw"]) + lv * light[1] + nz * light[2] + 0.25 * math.sin(pp["yaw"]), 0, 1) + face_col = idp["skin"][None, None] * (0.55 + 0.5 * lam[..., None]) + m = head[..., None] + img = img * (1 - m) + face_col * m + # Hair over the top of the head, above a hairline that turns with it. + hl = idp["hairline"] + 0.08 * pp["pitch"] + hairmask = head * np.clip((hl - lv) / 0.06 + 0.5, 0, 1) + side = idp["sidehair"] * np.clip((np.abs(lu) - 0.82) / 0.05, 0, 1) * np.clip((0.2 - lv) / 0.1, 0, 1) + paint(np.maximum(hairmask, head * side), idp["hair"] * (0.8 + 0.3 * lam[..., None])) + + kp = np.zeros((K, 2)) + + def visible_w(xside): + # Foreshortening of a feature at x on the surface: |n·view| for a turned head. + return max(0.25, math.cos(pp["yaw"] + math.asin(max(-0.95, min(0.95, xside / A))))) + + # Eyes. + ey = -idp["eye_y"] * B + for side, (ki, li, pi_) in ((-1, (0, 2, 4)), (1, (1, 3, 5))): + ex = side * idp["eye_x"] * A + p3 = [ex, ey, surf(ex, ey) - 0.01] + c2, _ = proj(p3) + fw = visible_w(ex) + blink = pp["blink_l"] if (side < 0 or pp["blink_r"] is None) else pp["blink_r"] + ew = idp["eye_w"] * A * sc * fw + eh = idp["eye_h"] * sc * (1 - 0.92 * blink) + socket = ell(c2, ew * 0.75, idp["eye_h"] * sc * 1.1, roll) + paint(inside(socket, 0.3) * 0.25, idp["skin"] * 0.6) + eyem = inside(ell(c2, ew * 0.5, eh, roll), 0.12) + paint(eyem, [0.95, 0.94, 0.92]) + g = np.array([pp["gx"] * ew * 0.22, pp["gy"] * eh * 0.35]) + g = np.array([ca * g[0] - sa * g[1], sa * g[0] + ca * g[1]]) + ic = c2 + g + ir = idp["iris_r"] * idp["eye_h"] * sc + paint(inside(ell(ic, ir * fw, ir, roll), 0.15) * eyem, idp["iris"]) + paint(inside(ell(ic, ir * 0.45 * fw, ir * 0.45, roll), 0.2) * eyem, [0.03, 0.03, 0.04]) + lid = c2 + np.array([sa * eh, -ca * eh]) + paint(inside(ell(c2, ew * 0.52, eh * 1.06, roll), 0.12) * (1 - inside(ell(c2, ew * 0.48, eh * 0.9, roll), 0.12)), idp["skin"] * 0.35) + kp[ki] = c2 + kp[li] = lid + kp[pi_] = ic + # Brows. + bx = ex + by = ey - idp["brow_y"] * B - 0.012 * pp["brow"] + b2, _ = proj([bx, by, surf(bx, by)]) + paint(inside(ell(b2, ew * 0.62, idp["brow_t"] * sc, roll + side * 0.08 * (pp["brow"] - 0.3)), 0.25), idp["hair"] * 0.7) + kp[12 if side < 0 else 13] = b2 + # Glasses. + if idp["glasses"]: + for ki in (0, 1): + r0 = idp["eye_w"] * A * sc * 0.62 + ring = ell(kp[ki], r0, r0 * 0.8, roll) + paint(np.clip(1 - np.abs(ring - 1) / 0.12, 0, 1), idp["gl_col"]) + # Nose: shading along its ridge and two nostrils, with the tip standing out of the face. + ny = idp["nose_l"] * B + tip, _ = proj([0, ny, surf(0, ny) + 0.05]) + root, _ = proj([0, ey, surf(0, ey)]) + paint(inside(ell((tip + root) / 2, idp["nose_w"] * 0.35 * sc, np.linalg.norm(tip - root) / 2 + 0.01, roll), 0.4) * 0.25, idp["skin"] * 0.7) + for side in (-1, 1): + nn, _ = proj([side * idp["nose_w"] * 0.45, ny + 0.008, surf(0, ny) + 0.03]) + paint(inside(ell(nn, 0.010 * sc, 0.006 * sc, roll), 0.4), idp["skin"] * 0.3) + kp[6] = tip + # Mouth: lips, the opening and teeth. + my = idp["mouth_y"] * B + mw = idp["mouth_w"] * A * (1 + 0.35 * pp["smile"]) + lc, _ = proj([-mw / 2, my - 0.012 * pp["smile"], surf(-mw / 2, my)]) + rc, _ = proj([mw / 2, my - 0.012 * pp["smile"], surf(mw / 2, my)]) + mc, _ = proj([0, my, surf(0, my) + 0.01]) + opn = pp["open"] * 0.045 + up, _ = proj([0, my - 0.008 - opn * 0.2, surf(0, my) + 0.012]) + lo, _ = proj([0, my + 0.009 + opn, surf(0, my) + 0.01]) + mid = (up + lo) / 2 + half_w = np.linalg.norm(rc - lc) / 2 + ang = math.atan2(rc[1] - lc[1], rc[0] - lc[0]) + hh = np.linalg.norm(lo - up) / 2 + paint(inside(ell(mid, half_w, hh + 0.005, ang), 0.15), idp["lip"]) + if pp["open"] > 0.05: + inner = inside(ell(mid, half_w * 0.85, max(hh - 0.006, 0.002), ang), 0.2) + paint(inner, [0.25, 0.06, 0.07]) + teeth = inner * np.clip(((up[1] + 0.005 * sc + 0.01 * pp["open"]) - v) / 0.004 + 0.5, 0, 1) + paint(teeth, [0.93, 0.92, 0.88]) + kp[7], kp[8], kp[9], kp[10] = lc, rc, up, lo + # Chin and cheeks. + chin, _ = proj([0, B * 0.98, surf(0, B * 0.98) + 0.01]) + kp[11] = chin + for ki, side in ((14, -1), (15, 1)): + cpt, _ = proj([side * A * 0.97, 0.15 * B, surf(side * A * 0.97, 0.15 * B)]) + kp[ki] = cpt + img = np.clip(img, 0, 1) + small = img.reshape(size, ss, size, ss, 3).mean(axis=(1, 3)) + out = np.clip(np.floor(small * 255 + 0.5), 0, 255).astype(np.uint8) + kpx = (kp + 0.5) * size # u, v in [-0.5, 0.5) -> pixels + return out, kpx + + +def kp_q4(kp: np.ndarray) -> np.ndarray: + """Keypoints in pixels to sixteenths of a pixel, rounded half to even.""" + return np.round(kp * 16).astype(np.int64) + + +# --------------------------------------------------------------------------------------- +# Training (torch). +# --------------------------------------------------------------------------------------- + + +def make_sample(seed: int, size: int, gauss: int, bg: int): + rng = np.random.default_rng(seed) + idp = identity_params(rng) + ref, rk = render(idp, pose_params(rng, neutral=True), size) + tgt, tk = render(idp, pose_params(rng), size) + rq, tq = kp_q4(rk), kp_q4(tk) + warped, heat = warp_and_maps(ref, rq, tq, gauss, bg) + return ref, warped, heat, tgt, rq, tq + + +def sequence(idp_seed: int, n: int, size: int, fps: int = 25): + """A held-out motion: smooth head motion, blinks, speech-like mouth, glances.""" + rng = np.random.default_rng(idp_seed) + idp = identity_params(rng) + t = np.arange(n) / fps + + def smooth(amp, fmin=0.15, fmax=0.6): + f = rng.uniform(fmin, fmax, 3) + ph = rng.uniform(0, 2 * math.pi, 3) + return amp * sum(np.sin(2 * math.pi * f[i] * t + ph[i]) for i in range(3)) / 3 + + blink = np.zeros(n) + for b0 in rng.uniform(0, n / fps, max(1, int(n / fps / 3))): + blink = np.maximum(blink, np.clip(1 - np.abs(t - b0) / 0.12, 0, 1)) + speech = np.clip(0.5 + 0.5 * np.sin(2 * math.pi * rng.uniform(3, 5) * t + rng.uniform(0, 6)) * (0.6 + 0.4 * np.sin(2 * math.pi * 0.7 * t)), 0, 1) + poses = [] + tx, ty, sc, ro, yw, pi_ = smooth(0.05), smooth(0.04), 1 + smooth(0.06), smooth(0.18), smooth(0.35), smooth(0.18) + sm, gx, gy, br = smooth(0.25) + 0.1, smooth(0.8, 0.2, 1.0), smooth(0.5, 0.2, 1.0), smooth(0.7, 0.2, 0.8) + for i in range(n): + poses.append(dict(tx=tx[i], ty=ty[i], scale=sc[i], roll=ro[i], yaw=yw[i], pitch=pi_[i], open=float(speech[i]) * 0.9, + smile=sm[i], blink_l=float(blink[i]), blink_r=None, gx=gx[i], gy=gy[i], brow=br[i])) + rng2 = np.random.default_rng(idp_seed + 1) + ref, rk = render(idp, pose_params(rng2, neutral=True), size) + frames, kps = zip(*(render(idp, p, size) for p in poses)) + return ref, rk, np.stack(frames), np.stack(kps) + + +try: + import torch as _torch + from torch.utils.data import IterableDataset as _Iterable +except ImportError: # the measurement-free parts of this tool run without torch + _torch, _Iterable = None, object + + +POSES = 9 # renders per training identity: a near-neutral one and eight others + + +def _render_identity(args): + seed, size = args + rng = np.random.default_rng(seed) + idp = identity_params(rng) + imgs, kps = [], [] + for j in range(POSES): + img, kp = render(idp, pose_params(rng, neutral=(j == 0)), size) + imgs.append(img) + kps.append(kp) + return np.stack(imgs), np.stack(kps) + + +def render_pool(path: Path, n_id: int, size: int, workers: int, base: int): + """Render `n_id` training identities, POSES each, into `imgs.npy` and `kps.npy` (memory + mapped, so the data loader's workers share them).""" + import multiprocessing as mp + + imgs = np.lib.format.open_memmap(path / "imgs.npy", mode="w+", dtype=np.uint8, shape=(n_id, POSES, size, size, 3)) + kps = np.lib.format.open_memmap(path / "kps.npy", mode="w+", dtype=np.float64, shape=(n_id, POSES, K, 2)) + t0 = time.time() + with mp.get_context("spawn").Pool(workers) as pool: + for i, (im, kp) in enumerate(pool.imap(_render_identity, [(base + i, size) for i in range(n_id)], chunksize=16)): + imgs[i] = im + kps[i] = kp + if (i + 1) % 2000 == 0: + print(f"rendered {i + 1}/{n_id} identities ({time.time() - t0:.0f} s)", flush=True) + imgs.flush() + kps.flush() + del imgs, kps + + +class FaceStream(_Iterable): + """Training samples, made in the data loader's workers from the rendered pool: a + reference face, its warp to another pose of the same face, the keypoint maps, and the + face rendered at that pose.""" + + def __init__(self, pool: Path, base, gauss, bg): + self.pool, self.base, self.gauss, self.bg = str(pool), base, gauss, bg + + def __iter__(self): + info = _torch.utils.data.get_worker_info() + wid = info.id if info else 0 + imgs = np.load(self.pool + "/imgs.npy", mmap_mode="r") + kps = np.load(self.pool + "/kps.npy", mmap_mode="r") + rng = np.random.default_rng(self.base + 1_000_003 * wid) + f = lambda a: _torch.from_numpy(np.ascontiguousarray(a.transpose(2, 0, 1)).astype(np.float32)) # noqa: E731 + while True: + i = int(rng.integers(0, imgs.shape[0])) + r = 0 if rng.random() < 0.6 else int(rng.integers(1, POSES)) + t = int(rng.integers(0, POSES - 1)) + t = t + 1 if t >= r else t + ref, tgt = np.asarray(imgs[i, r]), np.asarray(imgs[i, t]) + warped, heat = warp_and_maps(ref, kp_q4(np.asarray(kps[i, r])), kp_q4(np.asarray(kps[i, t])), self.gauss, self.bg) + yield f(warped), f(ref), _torch.from_numpy(heat.astype(np.float32)), f(tgt) + + +def train(args, out: Path, report: dict): + import torch + import torch.nn as nn + import torch.nn.functional as F + + dev = torch.device("mps" if torch.backends.mps.is_available() else "cpu") + torch.manual_seed(args.seed) + size, gauss, bg = args.size, args.gauss, args.bg + c1, c2, c3 = args.c1, args.c2, args.c3 + shapes = layer_shapes(c1, c2, c3) + + class Q(torch.autograd.Function): + """Round half up onto a grid, gradient straight through.""" + + @staticmethod + def forward(ctx, x, scale): + return torch.floor(x * scale + 0.5) / scale + + @staticmethod + def backward(ctx, g): + return g, None + + class QE(torch.autograd.Function): + """Round half to even onto a grid (the export's rule), gradient straight through.""" + + @staticmethod + def forward(ctx, x, scale): + return torch.round(x * scale) / scale + + @staticmethod + def backward(ctx, g): + return g, None + + class Net(nn.Module): + def __init__(self): + super().__init__() + self.convs = nn.ModuleList([nn.Conv2d(ci, co, 3, stride=s, padding=1) for ci, co, s in shapes]) + self.qat = False + + def conv(self, i, x): + c = self.convs[i] + w, b = c.weight, c.bias + if self.qat: + w = QE.apply(torch.clamp(w, -32768 / 4096, 32767 / 4096), 4096.0) + b = QE.apply(b, float(1 << FB)) + y = F.conv2d(x, w, b, stride=c.stride, padding=1) + if self.qat: + y = Q.apply(y, 32768.0) + return y + + def forward(self, warped, ref, heat): + act = lambda v: torch.clamp(v, 0, ACT_HI / 32768) # noqa: E731 + t3 = (warped - 128) / 128 + t4 = (ref - 128) / 128 + t5 = heat / 256 + x = torch.cat([t3, t4, t5], 1) + e1 = act(self.conv(0, x)) + e2 = act(self.conv(1, e1)) + e3 = act(self.conv(2, e2)) + m1 = act(self.conv(3, e3)) + r = act(self.conv(4, m1) + e3) + u2 = act(F.pixel_shuffle(self.conv(5, r), 2)) + d2 = act(self.conv(6, torch.cat([u2, e2], 1))) + u1 = act(F.pixel_shuffle(self.conv(7, d2), 2)) + d1 = act(self.conv(8, torch.cat([u1, e1], 1))) + o = self.conv(9, d1) + g = torch.clamp(o[:, :3], -1, 32767 / 32768) + z = o[:, 3:4] + m = torch.sigmoid(8 * z) + if self.qat: + m = Q.apply(m, 32768.0) + s = m * t3 + (1 - m) * g + return s * 128 + 128, g * 128 + 128, 8 * z + + pool = out / "pool" + pool.mkdir(exist_ok=True) + if not (pool / "imgs.npy").exists() or np.load(pool / "imgs.npy", mmap_mode="r").shape[0] != args.identities: + render_pool(pool, args.identities, size, args.workers, args.seed * 104729) + if args.measure_only: + # The float network and the generator of an earlier run of this tool, from --out. + fnet = Net().to(dev) + fnet.load_state_dict(torch.load(out / "float.pt", map_location=dev)) + fnet.eval() + blob = (out / "generator.wfg").read_bytes() + report["generator"] = {"sha256": hashlib.sha256(blob).hexdigest(), "bytes": len(blob)} + prev = json.loads((out / "report.json").read_text()) if (out / "report.json").exists() else {} + for k in ("losses", "train_seconds"): + if k in prev: + report[k] = prev[k] + return fnet, blob + net = Net().to(dev) + with torch.no_grad(): + # Start with an even mask, and smaller weights. + net.convs[9].bias[3] = 0.0 + for c in net.convs: + c.weight.mul_(0.7) + + from torch.utils.data import DataLoader + + def batches(base): + dl = DataLoader(FaceStream(pool, base, gauss, bg), batch_size=args.batch, num_workers=args.workers, persistent_workers=False) + return iter(dl) + + def loss_fn(y, t): + l1 = (y - t).abs().mean() + gy = lambda a: a[..., 1:, :] - a[..., :-1, :] # noqa: E731 + gx = lambda a: a[..., :, 1:] - a[..., :, :-1] # noqa: E731 + return l1 + 1.0 * ((gy(y) - gy(t)).abs().mean() + (gx(y) - gx(t)).abs().mean()) + + losses = [] + phases = [("float", args.steps, args.lr, False), ("qat", args.qat_steps, args.lr * 0.2, True)] + it = batches(args.seed * 7919) + t0 = time.time() + for name, steps, lr, qat in phases: + net.qat = qat + opt = torch.optim.Adam(net.parameters(), lr=lr) + sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, max(steps, 1)) + run = 0.0 + for step in range(steps): + w_, r_, h_, t_ = (a.to(dev) for a in next(it)) + y, g_, z_ = net(w_, r_, h_) + # The blend's own loss, the generated picture's (so the generator learns a whole + # face whatever the mask chooses), and a penalty on mask logits past ±4 (so the + # mask never saturates where its gradient vanishes). + loss = loss_fn(y, t_) + 0.25 * (g_ - t_).abs().mean() + 0.1 * torch.relu(z_.abs() - 4).mean() + opt.zero_grad() + loss.backward() + opt.step() + sched.step() + run += loss.item() + if (step + 1) % 500 == 0: + losses.append([name, step + 1, run / 500]) + print(f"{name} {step + 1}/{steps} loss {run / 500:.3f} ({time.time() - t0:.0f} s)", flush=True) + run = 0.0 + if name == "float": + float_state = {k: v.detach().clone() for k, v in net.state_dict().items()} + torch.save(float_state, out / "float.pt") + report["losses"] = losses + report["train_seconds"] = round(time.time() - t0) + + # Export: each float32 weight times 2^12 and each bias times 2^27, rounded half to even. + layers = [] + for c in net.convs: + w = c.weight.detach().cpu().numpy().astype(np.float32).astype(np.float64) + b = c.bias.detach().cpu().numpy().astype(np.float32).astype(np.float64) + wi = np.clip(np.round(w * 4096), -32768, 32767).astype(np.int64) + bi = np.clip(np.round(b * (1 << FB)), -(1 << 31), (1 << 31) - 1).astype(np.int64) + layers.append((wi, bi)) + hdr = dict(k=K, width=size, height=size, c1=c1, c2=c2, c3=c3, gauss=gauss, bg=bg) + blob = write_wfg(hdr, layers) + (out / "generator.wfg").write_bytes(blob) + report["generator"] = {"sha256": hashlib.sha256(blob).hexdigest(), "bytes": len(blob)} + + # The float network, for the measurement: the float phase's weights, run in float32. + fnet = Net().to(dev) + fnet.load_state_dict(float_state) + fnet.qat = False + fnet.eval() + return fnet, blob + + +def train_detector(args, dev, pool: Path): + """A float keypoint detector, trained on the rendered training faces and their keypoints, + for the measurement's keypoint error: it reads a picture and gives its sixteen keypoints.""" + import torch + import torch.nn as nn + + torch.manual_seed(args.seed + 1) + size = args.size + + class Det(nn.Module): + """Keypoint maps at half the picture's size, each read by a spatial softmax as the + expected position: sub-pixel by construction.""" + + def __init__(self): + super().__init__() + + def block(a, b, s): + return [nn.Conv2d(a, b, 3, stride=s, padding=1), nn.BatchNorm2d(b), nn.ReLU()] + self.down = nn.Sequential(*block(3, 32, 1), *block(32, 64, 2), *block(64, 64, 1), *block(64, 128, 2), *block(128, 128, 1), *block(128, 128, 1)) + self.up = nn.Sequential(nn.Upsample(scale_factor=2, mode="bilinear", align_corners=False), *block(128, 64, 1), nn.Conv2d(64, K, 3, padding=1)) + half = size // 2 + g = (torch.arange(half, dtype=torch.float32) + 0.5) * 2 + self.register_buffer("gx", g.view(1, 1, 1, half)) + self.register_buffer("gy", g.view(1, 1, half, 1)) + + def forward(self, x): + m = self.up(self.down(x / 255 - 0.5)) + n, k, h, w = m.shape + p = torch.softmax(m.reshape(n, k, -1), -1).reshape(n, k, h, w) + return torch.stack([(p * self.gx).sum((2, 3)), (p * self.gy).sum((2, 3))], -1) + + det = Det().to(dev) + opt = torch.optim.Adam(det.parameters(), lr=1e-3) + det.train() + sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, args.det_steps) + rng = np.random.default_rng(args.seed + 99) + all_imgs = np.load(pool / "imgs.npy", mmap_mode="r") + all_kps = np.load(pool / "kps.npy", mmap_mode="r") + n = min(args.det_pool, all_imgs.shape[0]) + imgs = torch.from_numpy(np.ascontiguousarray(np.asarray(all_imgs[:n]).reshape(-1, size, size, 3).transpose(0, 3, 1, 2)).astype(np.float32)).to(dev) + kps = torch.from_numpy(np.asarray(all_kps[:n]).reshape(-1, K, 2).astype(np.float32)).to(dev) + for step in range(args.det_steps): + idx = torch.from_numpy(rng.integers(0, imgs.shape[0], args.batch)).to(dev) + loss = (det(imgs[idx]) - kps[idx]).abs().mean() + opt.zero_grad() + loss.backward() + opt.step() + sched.step() + if (step + 1) % 1000 == 0: + print(f"detector {step + 1}/{args.det_steps} loss {loss.item():.3f} px", flush=True) + det.eval() + return det + + +def psnr(a: np.ndarray, b: np.ndarray) -> float: + mse = np.mean((a.astype(np.float64) - b.astype(np.float64)) ** 2) + return float("inf") if mse == 0 else 10 * math.log10(255 * 255 / mse) + + +def ssim(a: np.ndarray, b: np.ndarray) -> float: + """SSIM (11×11 Gaussian window, σ 1.5, K1 0.01, K2 0.03) on each channel, averaged.""" + g = np.exp(-((np.arange(11) - 5) ** 2) / (2 * 1.5 ** 2)) + g /= g.sum() + + def blur(x): + x = np.apply_along_axis(lambda r: np.convolve(r, g, mode="valid"), 0, x) + return np.apply_along_axis(lambda r: np.convolve(r, g, mode="valid"), 1, x) + + c1, c2 = (0.01 * 255) ** 2, (0.03 * 255) ** 2 + vals = [] + for ch in range(3): + x, y = a[..., ch].astype(np.float64), b[..., ch].astype(np.float64) + mx, my = blur(x), blur(y) + sxx = blur(x * x) - mx * mx + syy = blur(y * y) - my * my + sxy = blur(x * y) - mx * my + s = ((2 * mx * my + c1) * (2 * sxy + c2)) / ((mx * mx + my * my + c1) * (sxx + syy + c2)) + vals.append(s.mean()) + return float(np.mean(vals)) + + +def measure(args, out: Path, report: dict, fnet, blob: bytes): + import torch + + dev = next(fnet.parameters()).device + det = train_detector(args, dev, out / "pool") + hdr, layers = read_wfg(blob) + ilayers = [(w, b, s) for (w, b, s) in layers] + size, gauss, bg = args.size, args.gauss, args.bg + rows = {"int_float": [], "int_truth": [], "float_truth": [], "warp_truth": []} + ss = {"int_float": [], "int_truth": [], "float_truth": [], "warp_truth": []} + kerr = {"truth": [], "int": [], "float": []} + mouth = {"truth": [], "int": [], "float": []} + bitrate = {"truth_step4": [], "truth_step1": [], "detected_step4": [], "detected_step1": []} + gen_sha = hashlib.sha256(blob).digest() + nseq, nfr = args.measure_identities, args.measure_frames + t_int = 0.0 + for s in range(nseq): + ref, rk, frames, kps = sequence(50_000_000 + 7 * s, nfr, size) + rq = kp_q4(rk) + ident = hashlib.sha256(b"identity" + struct.pack(" Date: Thu, 22 Oct 2026 10:57:20 -0400 Subject: [PATCH 026/242] =?UTF-8?q?avatars=20=C2=A76=20to=20=C2=A713:=20fa?= =?UTF-8?q?ces=20from=20keypoints,=20its=20profile,=20records,=20verificat?= =?UTF-8?q?ion,=20measurement=20and=20mappings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The extension text of wai.avatar.face, its SPEC §3.1, §5 and §7 entries, the determinism-tiers fabric and evidence rows, and the changelog. Co-Authored-By: Claude Opus 5.5 --- wai/CHANGELOG.md | 58 +++ wai/SPEC.md | 15 +- wai/extensions/avatars.md | 699 ++++++++++++++++++++++++++++ wai/extensions/determinism-tiers.md | 4 + 4 files changed, 773 insertions(+), 3 deletions(-) diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index 39e1210ff..bb39868c1 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -6,6 +6,64 @@ wire format may change and such changes are recorded here. ## v0.4.0 — current +### `wai.avatar.face`: a talking head from a signed keypoint track, the same bytes on every sink + +- **The capability** ([avatars](extensions/avatars.md) §6 to §13) registers `wai.avatar.face`, a + payload capability of class `GenerativeDeterministic`. A participant commits once to an + **identity prior**, `WFI1`, a reference picture and its keypoints named by SHA-256, and sends + **keypoint segments**, `WFK1`: the first frame's coordinates in full, the rest as Exp-Golomb + coded deltas, in multiples of a stated step of a sixteenth of a pixel. Every sink generates + the same frames from the segment, the prior and the **generator**, `WFG1`, the capability's + parameter set of one file, pinned by SHA-256: **face-equivalence**, a case of + generation-equivalence. +- **An integer generator.** The driver warps the reference picture to the driving keypoints with + weights from integer-operators §2's exponential, a displacement rounded half away from zero and + a bilinear sample in sixteenths of a pixel, and draws a map of each keypoint (§7.5). The + network is ten 3 × 3 convolutions in a small encoder and decoder, lowered by `face-int/1` to + one `WHG1` graph of `wai-ops/1-i32` operators (§7.6): weights at 2^12, biases at 2^27, Q15 + activations, one rounding per operator, and a mask from a `lut16` logistic table whose every + entry is an integer computation. The frame is the warped picture and the generated one blended + by the mask. The graph meets `acc64`. +- **The same frames on every fabric.** The registered generator's frames, and a seeded + generator's, are the same bytes on the CPU, under `wasm32-wasip2` (the corpus regenerated by + its generator built for wasm32 and run under wasmtime), and on a GPU with every graph on the + device (Metal, at `acc64`, every graph compared with the interpreter), and the corpus's second + implementation gives them in Python's integers (§7.8). +- **A generator trained here.** The open keypoint-driven face-reenactment generators were surveyed + with their weights' licences first (§7.9): those whose weights carry a permissive licence need + operators `wai-ops/1` does not define, and the others carry a non-commercial licence or none + for their weights. `tools/wai_avatar_face_train.py` trains a generator on faces it draws itself, + with the integer arithmetic emulated in a second phase, and measures it: PSNR and SSIM of the + integer generator against the floating-point network and against the rendered faces, the + keypoint error of a separately trained detector, and the keypoint track's bitrate (§7.9). +- **Who drove which face.** The participant's key signs the **identity record** + (`wai-face-identity`) and each segment's **track record** (`wai-face-track`, chained by `seq` + and `prev`, optionally stating each frame's SHA-256). That signature is provenance, not a + permission rule: a track record signed by another key, or a segment that names another prior, + is refused (§8.3). +- **Synthesis.** Every segment carries an origin claim of class `synthesized`, step `generate`, + naming the generator and the track record (§8.4); the segment's envelope generates, so a claim + of any other step is `class_understated`. A player's `present` entry names the frames' digest + at `generation-equivalence`, a verifier that recomputes the frames refuses one that presents + other pictures (`present_output_irreproducible`), and a label policy is held to synthesis §10 + (`label_not_shown`) (§8.5). +- **Mappings** (§11): to ISO/IEC 23090-39, read through the openly readable 3GPP TR 26.813 + V20.0.0 §6.3.4 and draft-ietf-avtcore-rtp-avatar-02 §4, its text not being openly readable + (the identity prior as a component of a base avatar, the generator as a scheme of + `proprietaryAnimations`, a frame as a landmark animation unit, the identity record beside + `authenticationFeatures`, and what the unread text leaves open); to the draft's RTP carriage; + and to the report's two-dimensional avatars, Avatar Representation Assertion, feature + verification and evaluation framework. +- **Conformance** ([`live-conformance/avatar-face/`](live-conformance/avatar-face/)): the trained + generator, two held-out faces and their tracks, and the training report, each pinned by + SHA-256; the files of §7.2 to §7.4 read and refused; frames of a seeded and of the registered + generator, by digest and, for the seeded ones, in full; and cases accepted and refused with + every code of §10 and synthesis's `class_understated`, `present_output_irreproducible` and + `label_not_shown`. `wai_avatar_face_vectors gen|verify`, `tests/avatar_face.rs` and + `tests/avatar_face_gpu.rs` (feature `avatar_face`), and the second implementation + `live-conformance/avatar_face_verify.py`. CI: a step of `wai-conformance.yml`'s independent + verifiers, its job `avatar-face-wasip2`, and a step of `wai-ops-gpu.yml`'s `metal` job. + ### `wai.media.upscale`: super-resolution as an exact derivation, residuals against it, and an account of sink-made pictures - **The extension** ([exact-upscale](extensions/exact-upscale.md)) registers the record diff --git a/wai/SPEC.md b/wai/SPEC.md index 18d124311..bd9b8a43f 100644 --- a/wai/SPEC.md +++ b/wai/SPEC.md @@ -254,7 +254,10 @@ revision does not register, is treated as several files. A manifest names the parameter set a payload was coded against with a **pin**. One capability is the exception: the payload of `wai.generate.spectral` names its own parameter set, the model file, by SHA-256, and an envelope that names it carries no pin -(§5, *Payload of `wai.generate.spectral`*). +(§5, *Payload of `wai.generate.spectral`*). The payload of `wai.avatar.face` names, by +SHA-256, the identity prior its frames are generated from, an input of the decode as the +payload is; a pin names its parameter set, the generator, and covers no identity prior +([avatars](extensions/avatars.md) §7.4). ```json "prior": { @@ -676,7 +679,8 @@ set's shape. **One file:** `wai.neural.int_synth` (its model specification, `int_synth.json`); `wai.neural.int_mlicpp` (its `WMB1` bundle, read from a set as `model.wmb`); `wai.audio.int_codec` (its `WAM1` synthesis, read from a set as `model.wam`); `wai.splat.int_codec` (its `WHM1` synthesis, read from a set as -`g_s.whm`); and `wai.neural.encodec32`, `wai.neural.dac`, `wai.neural.mimi`, +`g_s.whm`); `wai.avatar.face` (its generator, `WFG1`, [avatars](extensions/avatars.md) +§7.2); and `wai.neural.encodec32`, `wai.neural.dac`, `wai.neural.mimi`, `wai.neural.wavtokenizer`, `wai.neural.snac` and `wai.neural.video_bmshj2018` (a self-contained ONNX model; one that keeps tensors in external files is several files). **Several files:** every other capability that decodes against a parameter @@ -850,6 +854,7 @@ standard. See the linked extension for each. | `wai.haptic.signal` | WHAP envelope (intensity/sharpness keyframes) | sample-equivalence · [haptics](extensions/haptics.md) | | `wai.feed.state` | WWLD, sim `wai.world.sim.feed0` | replay-equivalence · [state-feeds](extensions/state-feeds.md) | | `wai.avatar.pose` | WWLD, sim `wai.world.sim.skeletal0` | pose-equivalence · [avatars](extensions/avatars.md) | +| `wai.avatar.face` | `WFK1`, a segment of a keypoint track, which names its identity prior (`WFI1`) and its generator (`WFG1`) by SHA-256; the generator is the parameter set, one file | **face-equivalence**, a case of generation-equivalence (§7, `GenerativeDeterministic`): the driver's integer warp and keypoint maps, then the generator's network as one graph of [integer-operators](extensions/integer-operators.md) (`face-int/1`), so every sink generates the same frames; each segment is signed by the participant who drives the face and carries an origin claim of class `synthesized` · [avatars](extensions/avatars.md) §6 to §13 | | `wai.splat.4d` | W4DS container (per-Gaussian keyframed tracks) | frame-state-equivalence · [volumetric](extensions/volumetric.md) | | `wai.splat.int_codec` | `WIS1` — a LEARNED Gaussian-splat ATTRIBUTE codec: a learned transform over the `[14,S,S]` attribute image (pos/scale/rot/color/opacity), integer synthesis (int_hyper machinery, 14 channels) over a factorized-rANS latent (`int_splat.rs`; `tools/wai_int_splat_export.py` writes the model and a JSON form a converter reads) | **splat-equivalence**: the decoded attribute tensor is byte-identical on every machine (a portable cloud hash) — the int_hyper of 3D, where a float splat-attribute decode desyncs. The float rasterizer stays sink-supplied presentation; the *attributes* are the contract. Pure i64, no float/ONNX. Payload: [integer-payloads](extensions/integer-payloads.md) §6; decoded through its lowering, [integer-operators](extensions/integer-operators.md) §13.2 (`whm1/1`) | | `wai.film.linear` | WFLM container (sealed world + camera track + frame schedule) | film-equivalence · [linear-film](extensions/linear-film.md) | @@ -2136,7 +2141,11 @@ registry as `codecs::capability_replicate_class(cap) -> ReplicateClass`: the stream exactly as a float σ does. (`wai.generate.spectral`: a pure-integer spectral synthesis — prompt+seed draw integer-LUT sinusoid components by a `splitmix64` PRNG; the float `sin()` that bakes the LUT runs once at - model-build, never at generation.) Distinct from `Behavioral`, which conforms + model-build, never at generation. `wai.avatar.face`: a talking head generated from a + keypoint track and an identity prior by an integer warp and one integer graph, + [avatars](extensions/avatars.md) §7; its graph decodes through the integer operator + set, and its `ops` member names the lowering `face-int/1` at `acc64`.) Distinct from + `Behavioral`, which conforms by similarity and never reproduces bytes. It generates rather than reconstructs, so it does not back `replicate`. - **`NeuralFloat`** — runs float inference; exact only on the same build. An diff --git a/wai/extensions/avatars.md b/wai/extensions/avatars.md index 10fc16352..c6b6810d4 100644 --- a/wai/extensions/avatars.md +++ b/wai/extensions/avatars.md @@ -7,6 +7,19 @@ > case of replay-equivalence. Reference impl: the `world` feature of > `wai-rs` (sim `wai.world.sim.skeletal0`); corpus: `avatar-conformance/`; > live sink: wai.transaction.science/avatar. +> +> §6 to §13 register `wai.avatar.face`: a **talking head** made from a signed **keypoint +> track** and an **identity prior**, the same bytes on every sink, so that a receipt proves the +> face a viewer saw. A participant commits once to the prior, a reference picture and its +> keypoints named by SHA-256, and drives it with segments of keypoints, both signed by one key: +> that signature records who drove which face. A sink warps the reference picture to the +> driving keypoints and refines the warp with an integer network pinned by SHA-256, one graph +> of the integer operator set; CPU, wasm32 and GPU give the same frames. Each segment carries a +> synthesis origin claim of class `synthesized`, and a player that presents it is held to +> synthesis's label rules. Reference impl: the `avatar_face` module of `wai-rs` (feature +> `avatar_face`); corpus: [`live-conformance/avatar-face/`](../live-conformance/avatar-face/), +> whose second implementation is `live-conformance/avatar_face_verify.py`; the tool that +> trains and measures generators is `tools/wai_avatar_face_train.py`. Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. ## 1. Scope and model @@ -104,6 +117,692 @@ hash + root + checkpoints + exact signed `work` + carried `joules_micro` [energy-measurement](energy-measurement.md) §2.8); `parent_receipt_hash` chains a performance's seals into a timeline. +## 6. Faces from keypoints: `wai.avatar.face` + +A **participant** sends a face as instructions: a stream of **keypoints** (the eyes, the lids, +the pupils, the nose, the mouth, the chin, the brows, the cheeks), a few bytes a frame, rather +than pictures. Every sink makes the pictures, the same bytes on every sink, from three things: + +- the **identity prior** (§7.3): a reference picture of the face and its keypoints, one file + named by its SHA-256, to which the participant commits once in a signed **identity record** + (§8.2); +- the **generator** (§7.2): an integer network in one file, pinned by its SHA-256 as SPEC §3.1 + pins any parameter set; +- the **keypoint track**: segments of the payload `WFK1` (§7.4), each one object, each signed + by the participant in a **track record** (§8.3). + +A sink's **driver** warps the reference picture to each frame's keypoints and draws a map of +each keypoint (§7.5); the generator's network, one `WHG1` graph of `wai-ops/1` +([integer-operators](integer-operators.md)), refines the warp into the frame (§7.6). Every +operation is integer, so a frame is a function of the three files' bytes and nothing else, and +it is the same on a CPU, under wasm32 and on a GPU (§7.8). Since the frames are generated, every +segment carries a [synthesis](synthesis.md) origin claim of class `synthesized` (§8.4), and a +player that presents them is held to synthesis's label rules (§8.5). A receipt over a segment +then proves the face a viewer saw: anyone who holds the three files recomputes it. + +The identity record and the track records are signed by one key. That signature is +**provenance**: it records which participant drove which face. It is not a grant of +permission, and this extension states no rule about permission: the service a distributor +runs is its own matter. Where a lawful basis for processing a face is recorded, the record is a +`lawful-basis` claim of [jwp-receipts](jwp-receipts.md) §2.4.6. + +In scope: the integer profile of the generator and its three files (§7.1 to §7.4); the driver +and the graph (§7.5, §7.6); the frames and the tier they hold (§7.7, §7.8); the measurement of +the registered generator (§7.9); the records and the synthesis claims over the frames (§8); +verification (§9); the mappings of §11. + +Out of scope: how a sender finds the keypoints in a camera's picture (a detector at the sender, +which may be floating point: its output is the keypoints the track carries, and nothing a sink +computes depends on how they were found); how a generator is trained (§7.9 says how the +registered one was); a transport, which WAI defines none of ([moq-streaming-format](moq-streaming-format.md) +states it in its preamble); an identity prior in the form of a latent, which this revision does not register; a +watermark in the frames (synthesis §7), which this revision does not apply; and faces in three +dimensions, which the pose capability of §1 to §5 serves. + +Relationship to §1 to §5: a pose stream drives a mesh that a sink skins, and the skinning is +presentation, outside the state hash; a keypoint track drives a generator whose frames are the +hashed output. Relationship to the core spec: `wai.avatar.face` is a payload capability of SPEC +§5, registry class `GenerativeDeterministic` (SPEC §7): its decode generates, and its +conformance, **face-equivalence** (§7.7), is a case of generation-equivalence +([determinism-tiers](determinism-tiers.md) §2). Its parameter set is one file, the generator; +the identity prior is named by the payload's own digest, as `wai.generate.spectral`'s model is, +and no pin covers it (SPEC §3.1). + +## 7. The face generator + +### 7.1 The integer profile + +Every value of §7.5 and §7.6 is an integer, and no formula wraps or saturates unless it says so. +The generator's network follows one profile: + +- **Weights** are integers at the scale 2^12, each in a signed 16-bit field: a float weight *w* + is the integer round(*w* · 2^12). **Biases** are integers at the accumulator's scale, 2^27, + each in a signed 32-bit field. +- **Activations** are integers at the scale 2^15 (Q15). Every hidden activation lies in + 0..32767: a clamp after each hidden convolution. +- **One rounding per operator.** A convolution forms its sum exactly, adds its bias, and rounds + once, R_half_up of integer-operators §2 with the shift 12, back to Q15. Every other operator + that rounds rounds once, half up. +- **Nonlinearities** are clamps, or a `lut16` table (integer-operators §5.6) whose every entry is + a stated integer computation (§7.6), so that a second implementation computes the table and + takes no float library on trust. + +How the integers were obtained is not part of the decode. *Informative:* the reference tool +rounds each float32 weight times 2^12, and each bias times 2^27, half to even; a power-of-two +scaling of a float32 is exact, so the integers follow from the float's bits alone. + +### 7.2 The generator: `WFG1` + +One file. Every multi-byte field is little-endian. + +| off | size | field | rule | +|---|---|---|---| +| 0 | 4 | magic `WFG1` | | +| 4 | 1 | version | `1` | +| 5 | 1 | `K` | 1 to 32: the keypoints of a face | +| 6 | 2 | `width` | 4 to 512, a multiple of 4 | +| 8 | 2 | `height` | 4 to 512, a multiple of 4 | +| 10 | 2 | `c1` | 1 to 64 | +| 12 | 2 | `c2` | 1 to 64 | +| 14 | 2 | `c3` | 1 to 64 | +| 16 | 2 | `gauss` | at least 1: *g* of §7.5 | +| 18 | 4 | `bg` | at least 1: *B* of §7.5 | +| 22 | … | the ten convolutions | in the order below; nothing follows the last | + +Each convolution is 3 × 3 with zero padding 1 and one group, and is its weights, signed 16-bit +integers in the order `[cout][cin][3][3]`, then its biases, `cout` signed 32-bit integers. Their +input channels, output channels and strides are, in order: + +| # | `cin` | `cout` | stride | +|---|---|---|---| +| 1 | 6 + `K` | `c1` | 1 | +| 2 | `c1` | `c2` | 2 | +| 3 | `c2` | `c3` | 2 | +| 4 | `c3` | `c3` | 1 | +| 5 | `c3` | `c3` | 1 | +| 6 | `c3` | 4 · `c2` | 1 | +| 7 | 2 · `c2` | `c2` | 1 | +| 8 | `c2` | 4 · `c1` | 1 | +| 9 | 2 · `c1` | `c1` | 1 | +| 10 | `c1` | 4 | 1 | + +A reader checks, in this order, and the first that fails names the refusal: the magic +(`bad_magic`); the version, present and 1 (`version`); the header's other fields present +(`truncated`) and within their rules (`field_range`); each convolution's arrays present +(`truncated`); nothing after the last (`trailing`). A file that fails is not a generator. + +The generator is pinned as SPEC §3.1 pins a parameter set of one file: `sha256` is the SHA-256 of +the file, with no `form`. Nothing but the digest names a generator. + +### 7.3 The identity prior: `WFI1` + +One file: a reference picture and where its keypoints are. + +| off | size | field | rule | +|---|---|---|---| +| 0 | 4 | magic `WFI1` | | +| 4 | 1 | version | `1` | +| 5 | 1 | `K` | 1 to 32 | +| 6 | 2 | `width` | 4 to 512, a multiple of 4 | +| 8 | 2 | `height` | 4 to 512, a multiple of 4 | +| 10 | 4 · `K` | the keypoints | each `x` then `y`, signed 16-bit, in sixteenths of a pixel (§7.5) | +| … | `width` · `height` · 3 | the picture | RGB8: rows top to bottom, pixels left to right, R, G, B bytes | + +Nothing follows the picture. A reader checks in §7.2's order: `bad_magic`, `version`, +`truncated` for a header cut short, `field_range`, `truncated` for the keypoints and the picture, +`trailing`. The prior's **digest** is the SHA-256 of the file; nothing else names it. + +### 7.4 The keypoint segment: `WFK1` + +A segment holds the keypoints of one or more consecutive frames of one face. + +| off | size | field | rule | +|---|---|---|---| +| 0 | 4 | magic `WFK1` | | +| 4 | 1 | version | `1` | +| 5 | 1 | `K` | 1 to 32 | +| 6 | 1 | `step` | 1 to 16: a coordinate is a multiple of `step` sixteenths of a pixel | +| 7 | 1 | `order` | 0 to 15: the order of the deltas' code | +| 8 | 32 | `generator` | the SHA-256 of the generator (§7.2) | +| 40 | 32 | `identity` | the SHA-256 of the identity prior (§7.3) | +| 72 | 4 | `first` | the index of the segment's first frame in the track | +| 76 | 2 | `n` | 1 to 1024: its frames | +| 78 | 2 | `rate_num` | at least 1 | +| 80 | 2 | `rate_den` | at least 1: the frame rate is `rate_num` / `rate_den` frames a second | +| 82 | 2 | reserved | 0 | +| 84 | 4 · `K` | frame 0 | its 2 · `K` coordinates, `x` then `y` for each keypoint, each a signed 16-bit **value** *v*; the coordinate is *v* · `step` | +| 84 + 4 · `K` | … | the deltas | frames 1 to `n` − 1 | + +**The deltas.** For each later frame, for each of its 2 · `K` coordinates in frame 0's order, +one code: the value becomes the value before it plus the code's delta. A code is Exp-Golomb of +order `order`, most significant bit first: *z* zero bits, a one bit, then *z* bits, which with +the one form *m* (so *m* lies in 2^*z*..2^(*z*+1) − 1); then `order` bits *r*; the code is +*u* = (*m* − 1) · 2^`order` + *r*. The delta is *u* / 2 for an even *u* and −(*u* + 1) / 2 for an +odd one. The codes are packed with no gap; after the last, zero bits pad to a byte boundary, and +nothing follows. Each coordinate is the value times `step`, in sixteenths of a pixel. + +A reader checks, in this order: the magic (`bad_magic`); the version (`version`); the rest of +the header present (`truncated`) and within its rules (`field_range`); frame 0 present +(`truncated`), each of its coordinates in −32768..32767 (`field_range`); then each code in order: +its bits present (`truncated`), at most 31 zeros before its one (`field_range`), the coordinate +it gives in −32768..32767 (`field_range`); then fewer than eight bits of padding (`trailing`), each +0 (`noncanonical`). + +**Choosing `order`.** Any order reads; an encoder chooses one per segment. *Informative:* the +reference writer chooses the order that makes the segment shortest, the lowest where two tie. + +**The envelope.** A segment is the payload of a `WAI1` envelope (SPEC §2) whose capability is +`wai.avatar.face`, whose `intent` is `create`, and whose `conditioning.prior` pins the generator +the payload names: `sha256` is the payload's `generator` (§9.2 step 3, `generator_mismatch`). The +identity prior is the payload's `identity`, which no pin covers. The track's segments are +objects of one track (a MoQ track, a sequence of envelopes); a segment of one frame is a frame +sent on its own. + +### 7.5 The driver + +For a frame of driving keypoints *D*ₖ = (*Dx*ₖ, *Dy*ₖ), *k* < `K`, over an identity prior whose +keypoints are *S*ₖ and whose picture is *R* of `width` × `height` (*w* × *h*), the driver computes, +for each pixel (*x*, *y*), *x* < *w*, *y* < *h*, in sixteenths of a pixel (a pixel's centre is at +(16*x* + 8, 16*y* + 8)): + +1. **Weights.** For each *k*: *dx* = 16*x* + 8 − *Dx*ₖ, *dy* = 16*y* + 8 − *Dy*ₖ; + *e*ₖ = ⌊((*dx*² + *dy*²) · *g* + 128) / 256⌋ with *g* the generator's `gauss`; and + *w*ₖ = exp_neg_q16(−*e*ₖ) (integer-operators §2), in 0..65553. +2. **Displacement.** *N* = *B* + Σ *w*ₖ with *B* the generator's `bg`; + Δ*x* = rdiv(Σ *w*ₖ · (*Sx*ₖ − *Dx*ₖ), *N*) and Δ*y* likewise, with rdiv(*n*, *d*) = + sgn(*n*) · ⌊(2|*n*| + *d*) / (2*d*)⌋, integer-operators E12's rounding, half away from zero. +3. **The sample.** *U* = 16*x* + Δ*x* and *V* = 16*y* + Δ*y*; *ix* = ⌊*U* / 16⌋, *fx* = *U* − 16 · *ix*, + *iy* = ⌊*V* / 16⌋, *fy* = *V* − 16 · *iy*. With *p*(*Y*, *X*) = *R*[clamp(*Y*, 0, *h* − 1)][clamp(*X*, 0, + *w* − 1)], each channel of the **warped picture** at (*x*, *y*) is + ⌊((16 − *fx*)(16 − *fy*) · *p*(*iy*, *ix*) + *fx*(16 − *fy*) · *p*(*iy*, *ix* + 1) + (16 − *fx*)*fy* · + *p*(*iy* + 1, *ix*) + *fx* · *fy* · *p*(*iy* + 1, *ix* + 1) + 128) / 256⌋. +4. **The maps.** For each *k*, the **keypoint map** *M*ₖ at (*x*, *y*) is ⌊*w*ₖ / 256⌋, in 0..256. + +At rest, where each *D*ₖ is *S*ₖ, the displacement is 0 and the warped picture is the reference +picture. *Width:* every value is below 2^41 in magnitude; 64-bit integers hold them. + +### 7.6 The graph: the lowering `face-int/1` + +The generator's network is one `WHG1` graph (integer-operators §10), which the lowering +`face-int/1` writes from the file by integer-operators §13.0's canonical rules. Its three data +inputs, sizes set per decode: tensor 0, the warped picture `[3, h, w]`, and tensor 1, the +reference picture `[3, h, w]`, each `check [0, 255]`; tensor 2, the maps `[K, h, w]`, +`check [0, 256]`. Then its operators, each output taking the next tensor index, every tensor +`derived` with no exponent, each convolution of §7.2 written as `conv` with its `cout`, kernel +3 × 3, its stride on both sides, zero padding 1 on each side, one group, its weights at two +bytes, and an `rq` of no multiplier, one shift 12, a `pre` bias per channel at four bytes and the +rounding `half_up`; and *relu* written `clamp(0, 32767)`: + +1. `rescale` of tensor 0 with one multiplier 256 (four bytes), shift 0, a `pre` bias of −128 on + each channel (four bytes), `floor`: the warped picture centred, *t*₃; the same of tensor 1: + *t*₄; `rescale` of tensor 2 with one multiplier 128, shift 0, no bias, `floor`: *t*₅; +2. `concat`(*t*₃, *t*₄, *t*₅); convolution 1; *relu*: *e*�; +3. convolution 2; *relu*: *e*₂; convolution 3; *relu*: *e*₃; +4. convolution 4; *relu*; convolution 5; `add` of it and *e*₃; *relu*: *r*; +5. convolution 6 of *r*; `pixel_shuffle(2)`; *relu*; `concat` with *e*₂; convolution 7; *relu*: *d*₂; +6. convolution 8 of *d*₂; `pixel_shuffle(2)`; *relu*; `concat` with *e*�; convolution 9; *relu*: *d*�; +7. convolution 10 of *d*�: *o*, of four channels; +8. `split(0, 3)` of *o*; `clamp(−32768, 32767)`: the generated picture *G*; +9. `split(3, 1)` of *o*; `clamp(−32767, 32767)`; `lut16` with the table *T* below and no + exponents: the mask *m*; +10. `mul`(*t*₃, *m*) with an `rq` of no bias, shift 15, `half_up`; `rescale` of *m* with one + multiplier −1, shift 0, a `post` bias of 32768, `floor`: 32768 − *m*; `mul`(*G*, that) with + shift 15, `half_up`; `add` of the two; +11. `rescale` with no multiplier, shift 8, a `post` bias of 128 on each channel, `half_up`; then + `clamp(0, 255)`, the graph's one output, the frame `[3, h, w]`. + +**The mask's table.** For *v* in 0..32767, *S*(*v*) = ⌊(2^31 + ⌊*D* / 2⌋) / *D*⌋ with *D* = 65536 + +min(exp_neg_q16(−16*v*), 65536): the logistic function of *v* / 4096, at Q15, from integer-operators +§2's exponential, whose value at 0, 65553, is taken no higher than 2^16. *T*[*v* + 32768] = *S*(*v*) +for *v* ≥ 0, 32768 − *S*(−*v*) for −32767 ≤ *v* < 0, and *T*[0] = 0. So *T*[32768] = 16384, the +table is symmetric about it, and every entry lies in 0..32767. + +So the frame is the warped picture blended with the generated one by the mask: *m* · *t*₃ + +(32768 − *m*) · *G*, each product rounded once, brought back to samples. A generator that reads +always meets `acc64` (integer-operators §8): no value it forms passes 2^63. The registered +generator does not meet `acc32` (a 16-bit activation times a 16-bit weight over hundreds of +terms), and every operator of the graph is in `wai-ops/1-i32` (integer-operators §9.1), so a +fabric of that subset runs it at `acc64` (integer-operators §7.3). + +### 7.7 Frames and face-equivalence + +A segment's **frames** are, for each frame of the segment in order, the driver over the frame's +keypoints (§7.5) and then the graph (§7.6); a frame is RGB8 of `width` × `height`, rows top to +bottom. The generator, the identity prior and the segment MUST agree on `K`, and the generator +and the prior on `width` and `height`; where they do not there are no frames (`shape_mismatch`, +§9.2). + +**Face-equivalence.** Given the same generator, identity prior and segment, a conforming sink +MUST compute the same frames, byte for byte, on every machine. A segment's decode in canonical +form, the buffer synthesis's `present` entry and a review name by digest (synthesis §2, §9), +is its frames' RGB8 buffers one after another. The tier a sink's path holds over it is +`generation-equivalence` (synthesis §9.2; [determinism-tiers](determinism-tiers.md) §2), with +the rule of determinism-tiers §2 *What a tier covers*: a step after the generation that alters +the frames leaves the tier. + +### 7.8 Fabrics + +The registered generator's frames were computed on these fabrics, and gave the same bytes: + +- **CPU**: the integer operator set's interpreter, macOS arm64 (the corpus generator and its + tests; §12); +- **wasm32**: the corpus regenerated byte for byte by `wai_avatar_face_vectors verify` built for + `wasm32-wasip2` and run under wasmtime, on macOS arm64; +- **GPU**: every graph on the device through `wai-ops/1-i32` in WGSL ([determinism-tiers](determinism-tiers.md) + §5), at `acc64`, every graph also run on the interpreter and compared, on Metal, the + integrated GPU of an arm64 desktop (`tests/avatar_face_gpu.rs`); +- the corpus's second implementation, `live-conformance/avatar_face_verify.py`, in Python's + integers. + +Nothing is claimed for a fabric not listed. + +### 7.9 Measurement + +The registered generator, the corpus's `generator.wfg` (SHA-256 `2316f0c37f29c121e18627a836c50b959f0332f3320a338ec1dd814cf4f6dbc6`), has +`K` 16: in order, the left and the right eye, the left and the right upper lid, the left and the +right pupil, the tip of the nose, the left and the right corner of the mouth, the upper and the +lower lip, the chin, the left and the right brow, and the left and the right cheek. Its frames +are 64 × 64, its widths `c1`, `c2`, `c3` are 24, 48 and 64, its `gauss` is 1024 (a +Gaussian of about 5.7 pixels) and its `bg` 6554 (0.1 at Q16). + +`tools/wai_avatar_face_train.py` trained it on faces it draws itself, from seeded parameter sets +(a shaded head, hair, eyes with lids and pupils, brows, a nose, a mouth with lips and teeth, a +background), each picture with the keypoints its parameters place on it exactly; no picture from +outside this repository was used. It drew 6000 faces, each at nine poses and expressions, +and trained on pairs of one face: a reference picture warped by §7.5 to another of its pictures' +keypoints, the target that picture. It trained 60000 steps in floating point, then 12000 with the +integer arithmetic emulated in the loop (weights on the 2^−12 grid, activations on the 2^−15 +grid rounded half up, the mask's table), on the mean absolute error of the frame and of its +horizontal and vertical differences, plus a quarter of the generated picture's own error. Its +report is the corpus's `training.json`. + +**Why a generator trained here.** Before anything was downloaded, the open keypoint-driven +face-reenactment generators were surveyed with the licences of their code and their weights: + +| generator family | licence of the weights | used | +|---|---|---| +| implicit three-dimensional keypoints over a feature volume, with a warping module and a decoder | the base networks permissive (MIT); the bundled face-detection models non-commercial | no: its receiving side needs operators `wai-ops/1` does not define, three-dimensional convolution, sampling at positions the data gives, and spatial normalisation | +| a three-dimensional latent volume driven by a motion code | the repository's (Apache-2.0); its archive of dependencies carries other models under their own terms | no: the same operators | +| first-order motion, and thin-plate-spline motion, from keypoints | none stated for the weights (the code is MIT) | no: no licence is granted for the weights | +| a latent-motion animator; a depth-aware generator; a reimplementation of three-dimensional keypoint synthesis | non-commercial (CC BY-NC 4.0) | no | +| an articulated-motion animator | no licence granted | no | +| an audio-driven talking-head toolkit whose renderer is keypoint-driven | none stated for the weights (the code is Apache-2.0) | no | + +So no generator whose weights carry a permissive licence could be lowered to `wai-ops/1` in this +revision, and the registered one was trained here. + +**Quality.** Measured over 12 faces held out of training, each driven for 100 frames at 25 frames a +second (smooth head motion, blinks, a speech-like mouth, glances), each frame generated by the +integer generator (the arithmetic of §7.5 and §7.6, which gives the corpus's frames) and by the +floating-point network it was quantised from (the weights of the floating-point phase, in +float32), against each other and against the face the renderer draws at those keypoints. PSNR is +over RGB8; SSIM uses an 11 × 11 Gaussian window of σ 1.5, per channel, averaged: + +| compared | PSNR mean (dB) | PSNR min (dB) | SSIM mean | SSIM min | +|---|---|---|---|---| +| integer, float | 48.54 | 39.42 | 0.9988 | 0.9953 | +| integer, rendered | 28.24 | 21.27 | 0.9238 | 0.8558 | +| float, rendered | 28.22 | 21.11 | 0.923 | 0.8546 | +| warp alone, rendered | 22.89 | 13.99 | 0.7635 | 0.3499 | + +The last row is §7.5's warp alone, before the network. + +**Keypoint error.** A floating-point keypoint detector, trained on the training faces, read the +sixteen keypoints of each picture; the table gives the root mean square of their distances from +the keypoints that drove the frame, in pixels of the 64 × 64 frame, over all sixteen and over the +mouth's four (its corners and its lips). The first row is the detector's own error: + +| pictures read | all (px) | mouth (px) | +|---|---|---| +| the rendered faces | 0.111 | 0.092 | +| the float network's | 0.569 | 0.547 | +| the integer generator's | 0.575 | 0.542 | + +**Bitrate.** The tracks coded by §7.4 in segments of 25 frames (one second), the order chosen +per segment, counting the payloads' bytes and no envelope's; "rendered" are the keypoints the +renderer placed, "detected" those the detector read from the rendered frames, as a sender reads +them from its camera's pictures; step 4 is a quarter of a pixel and step 1 a sixteenth: + +| keypoints | kbit/s | +|---|---| +| rendered, step 4 | 2.942 | +| rendered, step 1 | 4.386 | +| detected, step 4 | 2.96 | +| detected, step 1 | 4.413 | + +These are figures for one generated face set at 64 × 64. Nothing is claimed for captured faces, +another size or another generator. + +## 8. Records and synthesis + +### 8.1 Encodings and signatures + +Every record of this section is a JSON object under the encodings of synthesis §2: byte strings +in lowercase hexadecimal, unsigned integers of at most 2^53 − 1 in plain decimal, no other number, +no boolean, date-times as [jwp-receipts](jwp-receipts.md) §2.4.3 defines them, no member named +twice, at most 32 levels of nesting, exactly the members a section lists, and a `kind` naming the +record; one that does not is `record_malformed`. Every record carries `signer_pubkey` (32 bytes, +an Ed25519 key of RFC 8032) and `sig` (64 bytes): + +``` +sig = Ed25519(signer_key, "wai:avatar\x01" || JCS(record without "sig")) +link_hash = BLAKE3("wai:avatar-link\x01" || JCS(record)) +``` + +JCS is RFC 8785. The signature is verified strictly ([SPEC](../SPEC.md) §7 *Signatures*): a key +that fails step 1 or 2 is `bad_key`, and a signature that fails a later step `bad_signature`. A +record is named by its link hash. + +### 8.2 The identity record + +A participant commits to an identity prior once, `kind` `"wai-face-identity"`: + +| member | type | meaning | +|---|---|---| +| `kind` | string | `"wai-face-identity"` | +| `identity` | 32 B hex | the SHA-256 of the identity prior (§7.3) | +| `issued_at` | date-time | when it signed it | +| `signer_pubkey`, `sig` | | §8.1: the participant's key | + +### 8.3 The track record + +The participant signs one track record for each segment, `kind` `"wai-face-track"`: + +| member | type | meaning | +|---|---|---| +| `kind` | string | `"wai-face-track"` | +| `object` | 32 B hex | the content hash ([jwp-receipts](jwp-receipts.md) §2.1) of the segment's envelope | +| `identity` | 32 B hex | the link hash of the identity record of the face it drives | +| `seq` | integer | its position among that face's track records, from 0 | +| `prev` | 32 B hex or `null` | the link hash of that face's track record before it; `null` exactly for `seq` 0 | +| `frames` | array or `null` | the SHA-256 of each frame the segment generates (§7.7), in order, 1 to 1024; `null` where the participant states none | +| `issued_at` | date-time | when it signed it | +| `signer_pubkey`, `sig` | | §8.1: the identity record's key | + +A track record MUST be signed by the key that signs the identity record it names +(`driver_key_mismatch`), and the segment's payload MUST name that record's prior +(`identity_mismatch`). So one key, one face: a key cannot drive a face another key committed to, +and a face cannot be driven by a segment that names another prior. A face's track records chain +by `seq` and `prev` (`track_chain_broken`). Where `frames` is given, the participant states the +frames every sink makes, and a verifier that holds the three files checks them +(`frame_mismatch`). + +### 8.4 Origin claims + +Every segment carries a synthesis origin claim (synthesis §3.1), signed by the participant or by +a party that generates its frames, with: + +- `subject` the segment object, `{"type": "object", "content_hash"}`; +- `class` `synthesized` and `step` `generate`; `operation` `wai.avatar.face`; +- `models` naming the generator's SHA-256, of `type` `trained` for a trained generator; +- `record` the link hash of the segment's track record; +- `media` `video`, and `oversight` as the generating party states it (*informative*: the + corpus states `prompt_guided`, the keypoints of a person guiding the generation); +- `source`, as synthesis §5 computes it. + +The segment's envelope generates (synthesis §3.3: its capability's class is +`GenerativeDeterministic`), so a claim of any step but `generate` over it is +`class_understated`, and a claim over a conversion of the frames keeps the class +(synthesis §3.4). A segment with no claim is `claim_missing`; a claim whose class, step, model or +record is not the above is `claim_mismatch` (§9.2 step 5). The claim covers every frame of the +segment: its subject is the object whose decode the frames are (§7.7). A frame a party shows or +holds apart from its segment is named in a claim of its own by the digest of its samples +(synthesis §3.1), of class `synthesized`, with the segment as its input. + +### 8.5 Presentation and labels + +A player that presents a segment writes synthesis's `present` entry (synthesis §10.3): the +segment as `subject`, its claim as `origin`, class `synthesized`, media `video`, tier +`generation-equivalence`, decoder `wai.avatar.face`, and as `output` the digest of the frames +it presented, face-equivalence's canonical buffer (§7.7). A verifier that recomputes the frames +holds that buffer, so a player that presents other pictures under the claim, the warped +pictures for instance, states an output the segment does not decode to +(`present_output_irreproducible`, synthesis §9.2). Under a label policy (synthesis §10.1) whose +classes include `synthesized`, the label is due at first exposure, after each interruption and +at the policy's intervals, and a log that does not show it is `label_not_shown` (synthesis +§10.4). A review of a segment (synthesis §9) names the canonical buffer as its reconstruction at +`generation-equivalence`, and so holds for every viewer whose player generated the frames +exactly. + +## 9. Verification + +### 9.1 Inputs + +A verifier is given, each in a list: identity records; track records; origin claims (synthesis +§3.1); reviews (synthesis §9.1); a label policy or none; a player's log or none (synthesis §10); +the envelopes it holds, each named by its content hash; the generators and the identity priors +it holds, each named by its SHA-256; and a **policy** of synthesis §11.1's members and one more: + +| member | values | default | meaning | +|---|---|---|---| +| `generation` | `accept-unrecomputed`, `require-proof` | `accept-unrecomputed` | `require-proof`: §9.2 step 3.10 applies (`generation_not_proved`) | + +### 9.2 Steps + +The verifier runs these steps and stops at the first refusal, which it reports with the reason +and where: a record by its list and index (`["identity", i]`, `["track", i]`), or synthesis's own +place (`["synthesis", …]`). + +1. **Reading.** Each identity record, then each track record, reads (§8.1 to §8.3) and its + signature verifies: `record_malformed`, `bad_key`, `bad_signature`. +2. **Identity records**, in order: where the verifier holds a prior by `identity`, it reads + (§7.3) (`identity_invalid`). +3. **Track records**, in order, each by these checks in order: + 1. Its identity record is one the verifier holds (`identity_unknown`). + 2. Its key is that record's (`driver_key_mismatch`). + 3. It is that face's first track record with `seq` 0 and `prev` `null`, or follows the face's + track record before it in the list with `seq` one higher and `prev` its link hash + (`track_chain_broken`). + 4. Its object is an envelope the verifier holds (`object_unknown`). + 5. The envelope is a `WAI1` whose capability is `wai.avatar.face` and whose payload reads as a + segment (§7.4) (`payload_invalid`). + 6. The envelope's `conditioning.prior.sha256` is the payload's `generator` + (`generator_mismatch`). + 7. The payload's `identity` is the identity record's (`identity_mismatch`). + 8. Where the verifier holds the generator, by the payload's `generator`, it reads (§7.2) and + loads under `acc64` (`generator_invalid`). + 9. With the generator and the prior held: the generator, the prior and the segment agree + (§7.7) (`shape_mismatch`); the frames are generated, and where `frames` is given, each + frame's SHA-256 is its entry (`frame_mismatch`). + 10. Under `generation` `require-proof`, a segment whose frames were not generated in step 9: + `generation_not_proved`. + + A segment whose frames were generated is **proved**; its canonical buffer (§7.7) is the + object's decoded samples for step 4. Otherwise it is **not recomputed**. +4. **Synthesis.** Synthesis §11.2 over the origin claims, the reviews, the label policy, the + player's log, the envelopes, and the decoded samples of step 3, with the policy's members: a + refusal there is this verification's, at `["synthesis", …]`. +5. **Each segment's claim.** For each track record in order: a claim whose subject is its object + (`claim_missing`); the first such claim is of class `synthesized`, step `generate`, names the + payload's generator among its `models`, and has the track record's link hash as `record` + (`claim_mismatch`). + +### 9.3 What an accepted case states + +- for each identity record, in order: its link hash, its `identity`, and `read` where the + verifier held the prior, `not-held` otherwise; +- for each track record, in order: its link hash, its object, its frames' count, `proved` or + `not-recomputed`, and for a proved segment the SHA-256 of its canonical buffer, `null` + otherwise; +- synthesis's statements: for each origin claim, in order, its link hash and class; each label + due and the `shown` entry that met it; each review presented; the EU icon the label policy + shows, where it shows one (synthesis §11.3). + +## 10. Reasons + +The codes a verification refuses with, each with where it is raised: + +| reason | raised at | +|---|---| +| `record_malformed` | §8.1, §9.2 step 1 | +| `bad_key` | §8.1 | +| `bad_signature` | §8.1 | +| `identity_invalid` | §7.3, §9.2 step 2 | +| `identity_unknown` | §9.2 step 3.1 | +| `driver_key_mismatch` | §8.3, §9.2 step 3.2 | +| `track_chain_broken` | §8.3, §9.2 step 3.3 | +| `object_unknown` | §9.2 step 3.4 | +| `payload_invalid` | §7.4, §9.2 step 3.5 | +| `generator_mismatch` | §7.4, §9.2 step 3.6 | +| `identity_mismatch` | §8.3, §9.2 step 3.7 | +| `generator_invalid` | §7.2, §9.2 step 3.8 | +| `shape_mismatch` | §7.7, §9.2 step 3.9 | +| `frame_mismatch` | §8.3, §9.2 step 3.9 | +| `generation_not_proved` | §9.1, §9.2 step 3.10 | +| `claim_missing` | §8.4, §9.2 step 5 | +| `claim_mismatch` | §8.4, §9.2 step 5 | + +A verifier also raises every code synthesis raises (synthesis §12), at `["synthesis", …]`: +`class_understated`, `present_output_irreproducible` and `label_not_shown` among them. + +The codes a file is refused with as it is read (§7.2 to §7.4), which §9.2 reports as +`payload_invalid`, `identity_invalid` or `generator_invalid`: + +| code | raised at | +|---|---| +| `bad_magic` | §7.2, §7.3, §7.4 | +| `version` | §7.2, §7.3, §7.4 | +| `field_range` | §7.2, §7.3, §7.4 | +| `truncated` | §7.2, §7.3, §7.4 | +| `trailing` | §7.2, §7.3, §7.4 | +| `noncanonical` | §7.4 | + +## 11. Mappings to other documents + +Each mapping below states what WAI's elements are in the other document's terms, read from +the text named, and marks what that text leaves open. None changes this extension. + +### 11.1 ISO/IEC 23090-39, the Avatar Representation Format + +ISO/IEC 23090-39 was at the Draft International Standard stage when this revision was written, +and its text is sold, not openly readable; it was not read. What is mapped here is what two +openly readable documents publish about it: 3GPP TR 26.813 V20.0.0 §6.3.4, which summarises its +data model and its animation stream, and draft-ietf-avtcore-rtp-avatar-02 §4, which summarises its +animation units. Every field width, code value and URN namespace of ISO/IEC 23090-39 is +therefore open here, and the rows mark where the mapping depends on one. + +| WAI | the Avatar Representation Format, as those documents describe it | mapping | left open by the text read | +|---|---|---|---| +| the identity prior (§7.3) | a base avatar: an ARF document in a container (an ISOBMFF or a ZIP container), its components held as data items | the prior is one data item of the container, named by its SHA-256 | how a two-dimensional picture and its keypoints are declared as a component | +| the generator (§7.2) | the preamble's `proprietaryAnimations`: an animation scheme named by a URN, with data items that carry its model or configuration, "for example ML-based reconstruction models" | the generator is such a data item, pinned by its SHA-256; the scheme is `wai.avatar.face` under the lowering `face-int/1` | the URN namespace; how a data item is typed | +| the identity record (§8.2) | the preamble's `authenticationFeatures`: encrypted face and voice feature vectors and a URI to a public key, which bind the avatar to its owner | WAI binds the prior to a key by a signature over the prior's digest, and carries no feature vector; the record's key is the key such a URI would name | the vectors' form; the encryption | +| a frame of a segment (§7.4) | a landmark animation unit, `AAU_LANDMARK`: a landmark set, flags for velocity and confidence, a dimensionality (two or three), a count, and for each landmark its index and coordinates | one frame is one unit: the face's `K` keypoints as a two-dimensional landmark set, indices 0 to `K` − 1, no velocity, no confidence, each coordinate *c* sixteenths of a pixel of the generator's frame written as *c* / 16 pixels. Each such value is a dyadic number below 2^11 in magnitude with at most four fractional bits, which a binary floating-point coordinate of at least 16 significand bits holds exactly. Read back, a coordinate in pixels becomes sixteenths, rounded half to even, and then a multiple of the segment's `step` | the coordinates' type and width; how a landmark set is defined in the container | +| a segment's timing (§7.4) | each unit's 32-bit timestamp in ticks; the configuration unit's timescale, ticks a second, a 32-bit float | with a timescale of `rate_num` ticks a second (an integer below 2^24, which a 32-bit float holds exactly), frame *i* of a segment has the timestamp (`first` + *i*) · `rate_den` | the timestamp's wrap; how a profile string is chosen | +| the frames (§7.7) | none: the animation stream drives a base avatar that a receiver renders by its own means | the frames are not carried; every sink generates them, the same bytes | — | + +So a WAI segment converts to landmark units and back without loss where the unit's coordinate +type holds the values above; which type it is, is the open question. The track record, the +claim and face-equivalence rest on the segment's envelope, whose content hash a track record +names: a party that rebuilds a segment from units must rebuild the same bytes (`step`, `order`, +`first` and the rate are fields a unit does not carry), so WAI's provenance travels with the +segment, not with the units. + +### 11.2 draft-ietf-avtcore-rtp-avatar-02 + +The draft carries animation units in RTP, one unit per packet, a fragment of one, or several +in an aggregation packet, under a two-byte payload header: `D` (dependency), `UT` (the unit type; +4 is a landmark unit), `L` (a level of detail) and `AvID` (an avatar's identifier in the +session), and signals the media type `application/ampg` with the optional parameters +`version`, `frameworks`, `avatar-ids` and `avatar-lods`. WAI defines no transport (the preamble +of [moq-streaming-format](moq-streaming-format.md)); this is how its elements sit in that +carriage: + +| the draft | WAI | +|---|---| +| a single-unit packet of `UT` 4 (§5.4.2) | one frame of a segment as a landmark unit (§11.1) | +| a multi-time aggregation packet, `UT` 14 (§5.4.4) | the frames of one segment, each its unit, with its timestamp offset from the first | +| `D` | 0: each landmark unit holds its frame's keypoints in full; the deltas of §7.4 stay inside a segment's payload, never across units | +| `L` | one level of detail: a generator generates at one size, `width` × `height` | +| `AvID` and `avatar-ids` | the face: the value an `avatar-ids` entry maps an `AvID` to is application dependent (§7.2 of the draft), and a WAI session maps it to the identity record's link hash, which names the prior | +| `frameworks` | a URN for the face's keypoint scheme; WAI registers none in this revision | +| the RTP timestamp, at the clock rate the SDP gives and the configuration unit's timescale | §11.1's timestamp, with `rate_num` ticks a second | +| §11's security considerations, which leave integrity and authenticity to the application | the track records and the claims, which sign the segment object, not the packets; carried with RTP they go beside the units, as the segment envelopes do | + +A receiver that holds only the units has the keypoints and can generate the frames, the same +bytes, given the generator and the prior; it cannot check a track record or a claim, which name +a segment's envelope. + +### 11.3 3GPP TR 26.813 V20.0.0 + +The technical report is openly readable, and was read in V20.0.0. + +| the report | WAI | +|---|---| +| §6.5.3: a two-dimensional avatar's base is a neural network and a base image; a *robust* model animates characters other than those it was trained on, a *personalized* model one character | the generator is a model and the identity prior the base image; the registered generator is a robust model in that sense (§7.9: its measured faces were held out of its training); a personalized model is a generator trained for one face, pinned by its digest the same way | +| §6.5.3: the model is stored in an exchangeable format with metadata (identifier, input and output size, framework and version) | the generator is one file of integers, pinned by SHA-256, and its network is one graph of `wai-ops/1` that every receiver runs to the same bytes (§7.6, §7.8) | +| §6.5.4: inference is driven by the user's video, audio or text, and its output is encoded with a video codec and streamed | the driving input is keypoints the sender extracts from its video; the inference moves to every receiver, and what is streamed is the keypoint track (§7.9 measures its bitrate) | +| §8.2.1: animation samples in a data channel's message envelope, whose message types are defined for blend-shape and joint samples; "for other animation samples, a dedicated message type needs to be defined" | WAI defines no message type; a segment envelope is the unit such a message would carry | +| §8.2.2: RTP carriage by draft-ietf-avtcore-rtp-avatar | §11.2 | +| §8.3.4.2: an Avatar Representation Assertion, a credential that binds an avatar representation, by a hash over the container's graph, to a user's key pair, issued by a trusted issuer | the identity record binds the prior, by its SHA-256, to the participant's key, signed by that key; an issuer's assertion of who holds the key is outside WAI, where jwp-receipts §7's key discovery serves | +| §9.2.2: receivers extract facial features from the user's video and compare them with features stored in the base avatar, to detect impersonation | a receiver checks that the frames are the generation of the committed prior under the participant's key (§9.2); nothing is extracted or compared | +| §9A.1: the evaluation framework's reproducibility rests on fixed content and deterministic rendering conditions | face-equivalence makes the rendered frames themselves reproducible: a test bed's output is a digest every implementation reproduces | +| §9A.3.1 and §9A.3.2.1: PSNR and SSIM against a reference, and the Lip Vertex Error, the RMS error of the mouth's landmarks | §7.9 reports PSNR and SSIM, and the RMS error of a keypoint detector's reading over all keypoints and over the mouth's four | +| §10.2, recommendation 3.b: study compression of animation streams | §7.4's coding, and §7.9's measured bitrate | + +## 12. Conformance + +[`live-conformance/avatar-face/`](../live-conformance/avatar-face/) holds the corpus. Its +inputs, each pinned by SHA-256 in the generator and in `vectors.json`, are written by +`tools/wai_avatar_face_train.py` (§7.9): `generator.wfg`, the registered generator; the identity +priors `identities/face1.wfi` and `identities/face2.wfi`, faces the tool generated and held out of +training; their keypoint tracks `tracks/face1.json` and `tracks/face2.json`, in sixteenths of a +pixel; and `training.json`, the tool's report. `wai_avatar_face_vectors gen` writes the rest from +fixed seeds: `seeded.wfg`, a generator of three keypoints at 16 × 16 drawn from SHA-256 of a +label and trained on nothing; `seeded.wfi`, a picture that is an integer function of a label; +and `vectors.json`, which holds: + +- **forms**: files of §7.2 to §7.4 and how each reads, its fields or its refusal, every code of + §10's second table at least once; +- **frames**: for the seeded generator and identity at three sets of keypoints, and for the + registered generator over each frame of both tracks, the SHA-256 of the warped picture, of the + maps (each value a 16-bit little-endian unsigned integer, in `[K][h][w]` order) and of the + frame, and for the seeded ones the warped picture and the frame in full; +- **cases** of §9.1, each with its outcome, and, accepted, everything §9.3 states; refused, its + reason and its place. Accepted: one participant's two segments, proved, presented with the + label at first exposure and within the interval; the same with nothing held, not recomputed; + two participants each driving her own face; a track record that states no frames. Refused: + each code of §10's first table, and synthesis's `class_understated` (a segment claimed as a + capture), `present_output_irreproducible` (a player that presents the warped pictures) and + `label_not_shown`. + +The segments the cases carry round each keypoint of the tracks to a quarter of a pixel (`step` +4). A conforming implementation gives every vector what the corpus states. +`wai_avatar_face_vectors verify` and the test `tests/avatar_face.rs` check that every file but the +inputs regenerates byte for byte, that `wai-rs` gives every vector what it states, that §10 lists +exactly the reference's codes, and that §7.9's figures are `training.json`'s. +`live-conformance/avatar_face_verify.py` is the second implementation, written from this text: it +shares no code with `wai-rs`; it reads the files and runs §7.5 and §7.6 in Python's integers, +operator by operator; it verifies the claims, the log and the label policy with the synthesis +corpus's second implementation (`synthesis_verify.py`), every signature with WAI's strict rule +(`claim-conformance/_ed25519.py`) and every link hash with BLAKE3 written from its specification +(`claim-conformance/_blake3.py`); it reads §10's codes from this text; and it mutates copies of +the corpus and requires every copy to be refused. + +## 13. Security and privacy considerations + +- A frame is a function of the generator, the prior and the keypoints, and nothing else. A proved + segment shows that the frames are what those files give, not that the person in front of the + sender's camera looked so: the keypoints are the sender's statement of where the features + were, and the generator adds what it learned. +- The identity record and the track records bind a face to a key. They show that the holder of + that key drove the face, not who the holder is: binding a key to a person is + [jwp-receipts](jwp-receipts.md) §7's key discovery, or a credential outside WAI. A party that + obtains an identity prior can commit to it under a key of its own; what the records then show + is that this other key drove the face, which is what a verifier checks against the key it + expects. A verifier that knows the participant's key refuses a track signed by another + (`driver_key_mismatch`). +- An identity prior is a picture of a face. It is fetched by its digest from whoever serves it, + and anyone who holds it can generate the face from any keypoints. A service that does not want + a prior to be held outside a session keeps it in the session's confidentiality + ([confidentiality](confidentiality.md)); the digest in a record names the picture and lets a + party that holds the picture recognise it. +- A keypoint track describes a person's expressions and movements. Where processing them needs a + lawful basis, the service records it as a `lawful-basis` claim (jwp-receipts §2.4.6). +- Every frame is synthesized, and is labelled as such (§8.4, §8.5). A party that strips the claim + and the label from the frames is caught only where a verifier receives them through the + segment, whose envelope generates (synthesis §3.3); this revision applies no watermark to the + frames (synthesis §7). + ## Appendix An avatar is the highest-bandwidth capture reduced to its instructions: diff --git a/wai/extensions/determinism-tiers.md b/wai/extensions/determinism-tiers.md index 42615920c..dd3e09a0a 100644 --- a/wai/extensions/determinism-tiers.md +++ b/wai/extensions/determinism-tiers.md @@ -556,6 +556,9 @@ shows, by fabric: interpreter's outputs, so its `entropy-consistency` claim (§5) holds with its scale path there. Its synthesis graphs use operators outside `wai-ops/1-i32` and ran on the CPU. + The frames of `wai.avatar.face`, whose tier is generation-equivalence + ([avatars](avatars.md) §7.8), gave the CPU's bytes with the generator's graph on Metal, at + `acc64`, and under `wasm32-wasip2` (§6). - **NPU.** No decoder of these capabilities runs on one in this revision, and the tier is not claimed there. A network evaluated in floating point on a GPU or NPU does not meet it (§1). @@ -604,6 +607,7 @@ task list has no Windows task after that day. The Windows results below are hist | `wai.audio.int_codec`, `wai.splat.int_codec` | committed vectors that never skip. Their frozen hashes are the output of an independent Python integer reference, computed when the vectors were exported (`tools/wai_int_audio_export.py`, `tools/wai_int_splat_export.py`); no Python runs in CI. The conformance workflow's fast-kernel steps enable `audio_int` and `splat_int` and, in debug and release builds, decode both vectors against those hashes under the reference kernels and every fast-kernel policy (`tests/int_kernels_vectors.rs`); that passed on the forge's Linux x86_64, Linux arm64 and macOS arm64 runners for the landing chain's head `7cfcecfa` (run 745, 2026-09-30). The same steps also name `tests/int_audio.rs` and `tests/int_splat.rs`, which check each vector against the same hash and against its own metadata (sample count; attribute count, tensor size and grid side); Cargo declares each with its feature as a required feature, so a step that names it without that feature fails instead of running nothing. This row records no forge run of those two | `wai-rs/tests/int_audio.rs`, `int_splat.rs`, `int_kernels_vectors.rs`; `.github/workflows/wai-conformance.yml` | | `wai.neural.int_mlicpp` on its committed vector (synthetic seeded weights; a 64×64 image, 8 latent channels in 4 slices): the decode reproduces the export tool's independent integer reference byte for byte — the RGB, and every scale bucket and symbol of the latent stream in coding order (512 symbols over all 16 buckets of the scale table, 47 of the symbols coded by the rANS escape) — under the reference kernels and under the fast kernels on 1, 2, 3, 5 and all available threads; one integer unit changed in each of four chosen parameters, one on each path that feeds a value (a hyper-decoder weight, an entropy-network weight, a mean-head bias, a synthesis weight), changes the image; a header whose shapes the model cannot produce, or on which a network would form a buffer over the cap, is refused before anything is allocated (`tests/int_mlicpp_preflight.rs`, which counts allocations); damaged payloads are refused or decode, and never panic in an overflow-checked build | measured 2026-09-29 with rustc 1.98.0 on the source of the change that added this row: macOS arm64 native (the wai-rs tests, with overflow checks on and off; the `byte-exact-conformance` golden, release) and wasm32-wasip2 under wasmtime 49.0.1 (the same tests and golden, release; on wasm the fast kernels run single-threaded as portable scalar code, and `usize` is 32-bit). All pinned hashes matched on both. Not measured: x86_64 — the development machine has no x86_64 translation layer installed, and an x86_64-apple-darwin build of the golden compiles but cannot execute there — nor Linux or Windows. The golden runs on every leg of the byte-exact matrix (`wai-conformance.yml`, `wai-byte-equality.yml`) and `tests/int_mlicpp.rs` on every leg of `wai-conformance.yml` in both profiles. Both passed on the forge's Linux x86_64, Linux arm64 and macOS arm64 runners for the landing chain's head `7cfcecfa` (run 745, 2026-09-30); there is no Windows leg. The dispatch through `decode_envelope_strict` (the prior pinning the one-file model bundle, the strict gate) runs in `wai-neural-gates.yml`. The weights are synthetic, so nothing here measures compression quality | `wai-rs/tests/int_mlicpp.rs`, `wai-rs/tests/int_mlicpp_preflight.rs`, `byte-exact-conformance/src/int_mlicpp_golden.rs`, `wai-rs/tests/neural_native_decode.rs`; `tools/wai_int_mlicpp_export.py` | | `wai-ops/1-i32` graphs on GPUs (`wai/wai-ops-gpu`, §5): each device's arithmetic agrees with 128-bit host arithmetic on every probe case; every graph vector of `ops-conformance/` that the subset covers gives the interpreter's outputs or refusal code, at `acc32` and at `acc64`; the integer decoders, with their graphs on the device, give the CPU decode's bytes or refusal codes on every committed vector and refusal fixture, and every graph the device runs gives the interpreter's outputs | measured 2026-10-20 and 2026-10-21 with rustc 1.98.0 and wgpu 30.0.1, release builds, on the source of the change that added this row, on the adapters §5 lists: Metal on macOS 27.0.1 (arm64); headless Chromium 148, and a Chromium-based browser at 155, on that GPU; Vulkan on Linux x86_64 with the 0x10de and 0x8086 adapters (driver 595.84, Mesa 25.2.8) and, on another host, with the 0x1002 adapter (Mesa 26.0.8); and llvmpipe on both Linux hosts. On each, the probe (`tests/arith.rs`) agreed on every word of 1,051,757 cases, and the corpus (`tests/corpus.rs`) ran 813 vectors (1,104 decodes) on the 32-bit kernels and 1,426 (1,921 decodes) on the 64-bit ones, 212 of the decodes refusals, with no difference; 209 graphs use an operator outside the subset and ran on the CPU, and 95 vectors are refused at load. Run on the 32-bit kernels, 330 of the 613 graphs whose certificate does not meet `acc32` gave other results, so the width a graph runs at is the certificate's. Eighteen edits of one kernel expression each (a rounding shift or its addend off by one, a tie rule, a clamp bound moved by one, a dropped carry or cross term, a sign rule, a post bias, an index rule; `tests/mutants.rs`) were each caught by the corpus on every adapter, and an edited product by the probe. `wai-rs/tests/ops_gpu.rs` decoded the committed vectors and refusal fixtures of the six capabilities §5 names, the world renderer's two vectors and `wai.image.jpegai`'s `jpegai/v1` codestream, comparing every graph the device ran with the interpreter as well as each decode's bytes; none differed. The browser leg ran the corpus, not the decoders. `wai-ops-gpu.yml` runs the Metal and browser legs on the `macos` runner, where an absent adapter fails; this row records no forge run of it yet. No other workflow runs a GPU leg of these tests | `wai-ops-gpu/tests/`, `wai-ops-gpu/browser/`, `wai-rs/tests/ops_gpu.rs`; `.github/workflows/wai-ops-gpu.yml` | +| `wai.avatar.face` ([avatars](avatars.md) §7.8), at generation-equivalence: the registered generator's frames and a seeded generator's are the same bytes on the CPU (macOS arm64), under `wasm32-wasip2` and with every graph on a GPU (Metal, at `acc64`, every graph compared with the interpreter), and the corpus's second implementation, in Python's integers, gives the same frames | `wai-rs/tests/avatar_face.rs` and `avatar_face_verify.py` (`wai-conformance.yml`); `wai_avatar_face_vectors verify` built for `wasm32-wasip2` under wasmtime (the job `avatar-face-wasip2`); `wai-rs/tests/avatar_face_gpu.rs` (`wai-ops-gpu.yml`, `metal`). Run on the build host, macOS arm64, on 2026-10-22, for the change that registered it; no forge run is recorded here | `wai/live-conformance/avatar-face/` | So at `entropy-consistency`, WAI's JPEG AI symbol decode is demonstrated across architectures (x86_64 against arm64) once the distribution indices are given; the -- 2.54.0 (Apple Git-157) From be3f8427f5d3e779397bc6822eca42f0f560b172 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 10:57:20 -0400 Subject: [PATCH 027/242] CI: the face generator's corpus, its second implementation, wasm32-wasip2 and Metal legs Co-Authored-By: Claude Opus 5.5 --- .github/workflows/wai-conformance.yml | 85 +++++++++++++++++++++++++++ .github/workflows/wai-ops-gpu.yml | 26 +++++++- 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index 40c5188d9..9b22e4c9a 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -135,6 +135,12 @@ on: # reports, upscale_verify.py reads that table, and the registry's wire identifiers test # reads its sections. - 'wai/extensions/exact-upscale.md' + # tests/avatar_face.rs requires this extension's §10 to list the codes the reference + # reports and its §7.9 to state the training report's figures, avatar_face_verify.py + # reads §10, and the registry's tests read SPEC §5's registration of wai.avatar.face. + - 'wai/extensions/avatars.md' + # The tool that trained the corpus's generator and wrote its inputs and its report. + - 'wai/tools/wai_avatar_face_train.py' # tests/ai_usage.rs requires this extension's §9 to list the codes the reference reports, # ai_usage_verify.py reads that table, and the registry's wire identifiers test reads its # sections. @@ -811,6 +817,65 @@ jobs: wasmtime run --dir ../quantum-conformance::/corpus \ target/wasm32-wasip2/debug/wai_quantum_vectors.wasm verify /corpus + # The face generator under wasm32-wasip2, run by wasmtime (extensions/avatars.md §7.8): + # wai_avatar_face_vectors verify, built for wasm32, regenerates every file of + # live-conformance/avatar-face from its seeds and inputs, every frame through the integer + # operator set's interpreter compiled to wasm32, and requires the committed bytes, which a + # native build wrote: the frames are the same on wasm32 as on the CPU. One leg: wasm32 is + # the same machine on every host. Linux, so it does not queue on the single macOS runner. + avatar-face-wasip2: + needs: relevant + if: needs.relevant.outputs.run == 'true' + name: faces from keypoints under wasm32-wasip2 (wasmtime) + runs-on: linux + timeout-minutes: 60 + steps: + - uses: actions/checkout@v5 + + - name: Install Rust 1.98.0 with the wasm32-wasip2 target + shell: bash + run: | + export PATH="$HOME/.cargo/bin:$PATH" + command -v rustup >/dev/null 2>&1 || \ + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0 + rustup toolchain install 1.98.0 --profile minimal + rustup default 1.98.0 + rustup target add --toolchain 1.98.0 wasm32-wasip2 + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + + # The release binary, pinned by version and SHA-256, rather than a build from source. + - name: Install wasmtime 49.0.2 + shell: bash + run: | + set -euo pipefail + [ "$(uname -m)" = x86_64 ] || { echo "::error::the pinned wasmtime archive is x86_64; this host is $(uname -m)"; exit 1; } + if ! command -v xz >/dev/null 2>&1; then + SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo" + $SUDO apt-get update -qq + $SUDO apt-get install -y -qq xz-utils + fi + v=49.0.2 + f="wasmtime-v$v-x86_64-linux.tar.xz" + curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \ + "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f" + echo "a4d6e9e3a5a60f527cf7793d674c48930c80c2e8977995b8a275cad3254b9322 $RUNNER_TEMP/$f" | sha256sum -c - + tar -xJf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP" + echo "$RUNNER_TEMP/wasmtime-v$v-x86_64-linux" >> "$GITHUB_PATH" + + # One codegen unit for the wai package: see the quantum job's last step for the LLD 22 + # cdylib crash this avoids. + - name: wai_avatar_face_vectors verify under wasmtime + shell: bash + working-directory: wai/wai-rs + run: | + set -euo pipefail + cargo build --locked --release --target wasm32-wasip2 --no-default-features --features avatar_face --bin wai_avatar_face_vectors \ + --config 'profile.release.package.wai.codegen-units=1' + t="${CARGO_TARGET_DIR:-target}" + wasmtime run --dir ../live-conformance/avatar-face::/corpus \ + "$t/wasm32-wasip2/release/wai_avatar_face_vectors.wasm" verify /corpus | tee "$RUNNER_TEMP/face-wasm.log" + [ "$(grep -c '^PASS ' "$RUNNER_TEMP/face-wasm.log")" = 3 ] || { echo "::error::not every check passed under wasm32"; exit 1; } + # The generated v2 vocabulary (wai/vocab/v2.*) is checked as a consumer uses it: # sample manifests, typed and untyped, WAI1 and WAI2, are expanded through the # JSON-LD context exactly as written on the wire and validated against the SHACL @@ -1294,6 +1359,26 @@ jobs: (cd wai-rs && cargo test --no-default-features --features exact_upscale --lib exact_upscale::) (cd wai-rs && cargo test --no-default-features --features exact_upscale --test exact_upscale) python3 -I live-conformance/upscale_verify.py live-conformance/upscale + # wai.avatar.face (extensions/avatars.md §6 to §13): the module's unit tests (the + # generator's wai-ops/1 graph against §7.6's arithmetic written out loop by loop; the warp + # at rest is the reference; the mask's table; the files read and refused in their order), + # and tests/avatar_face.rs (the committed corpus regenerates byte for byte from its fixed + # seeds and its pinned inputs, and no other file is there; wai-rs gives every form, frame + # and case what it states; §10 lists exactly the reference's codes; §7.9's figures are + # training.json's; the registered generator meets acc64 and not acc32); then the corpus's + # second implementation, avatar_face_verify.py, written from the extension's text and + # sharing no code with wai-rs, which runs the driver and the generator's arithmetic in + # Python's integers, verifies the claims, the log and the label policy with + # synthesis_verify.py, and requires every mutated copy of the corpus to fail; standard + # library only, run isolated (-I). + - name: faces from keypoints · wai-rs and avatar_face_verify.py + working-directory: wai + shell: bash + run: | + set -euo pipefail + (cd wai-rs && cargo test --no-default-features --features avatar_face --lib avatar_face::) + (cd wai-rs && cargo test --release --no-default-features --features avatar_face --test avatar_face) + python3 -I live-conformance/avatar_face_verify.py live-conformance/avatar-face # wai.ai.usage (extensions/ai-usage.md): the reference module's unit tests (RFC 9651's # Dictionary parser against the vocabulary's examples and the RFC's edges, statements # read and refused in their steps' order, the precedence rule, HTTP fields compared by diff --git a/.github/workflows/wai-ops-gpu.yml b/.github/workflows/wai-ops-gpu.yml index dfea03d12..10c48e19c 100644 --- a/.github/workflows/wai-ops-gpu.yml +++ b/.github/workflows/wai-ops-gpu.yml @@ -10,7 +10,8 @@ name: wai-ops-gpu integer operators on GPUs # at both widths, against the committed results and the interpreter; the shader # mutants, every one of which the corpus must catch; every committed vector and # refusal fixture of every capability that runs a wai-ops/1 graph, the jpegai/v1 -# inputs included, against the decode on the CPU; and the corpus again in a +# inputs included, against the decode on the CPU; the exact-upscale derivations and +# the face generator's frames, against the CPU's; and the corpus again in a # headless browser's WebGPU, the kernels compiled by the browser's own WGSL # compiler. WAI_OPS_GPU_EXPECT declares the one adapter the host must offer, and # WAI_OPS_GPU_REQUIRE makes an absent adapter a failure, never a skip; each step @@ -48,6 +49,11 @@ on: - 'wai/wai-rs/src/exact_upscale/**' - 'wai/wai-rs/tests/exact_upscale_gpu.rs' - 'wai/live-conformance/upscale/priors/**' + # The face generator's graph on the device, over the corpus's frames. + - 'wai/wai-rs/src/avatar_face.rs' + - 'wai/wai-rs/src/avatar_face/**' + - 'wai/wai-rs/tests/avatar_face_gpu.rs' + - 'wai/live-conformance/avatar-face/**' - 'wai/wai-rs/tests/vectors/**' - 'wai/int-payload-conformance/**' - 'wai/neural-conformance/**' @@ -136,6 +142,7 @@ jobs: set -euo pipefail cargo test --locked --no-default-features --features ops_gpu,video_int,audio_int,splat_int,world_int --test ops_gpu --no-run cargo test --locked --no-default-features --features ops_gpu,exact_upscale --test exact_upscale_gpu --no-run + cargo test --locked --no-default-features --features ops_gpu,avatar_face --test avatar_face_gpu --no-run - name: The bench tool builds working-directory: wai/tools/ops-gpu-bench run: cargo build --locked --release @@ -212,6 +219,23 @@ jobs: grep -q '^test result: ok\. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out' "$log" || { echo "::error::the test did not run and pass"; exit 1; } grep -qE "^exact-upscale gpu: key=gpu-metal-0 .* cases=[1-9][0-9]* .* cpu=0 .* differ=0 errors=0 equal$" "$rep" \ || { echo "::error::no equal run on the GPU"; exit 1; } + # The face generator (extensions/avatars.md §7.8): every frame the avatar-face corpus + # names, from the seeded and the registered generator, is the CPU's frame and the corpus's + # digest with the generator's graph on the device, every graph run on the device and + # compared with the interpreter. + - name: The face generator's frames with its graph on the GPU + shell: bash + working-directory: wai/wai-rs + run: | + set -euo pipefail + log="$RUNNER_TEMP/avatar-face-gpu.log" + rep="$RUNNER_TEMP/avatar-face-gpu.report" + : > "$rep" + WAI_OPS_GPU_REPORT="$rep" nice -n 10 cargo test --locked --release --no-default-features --features ops_gpu,avatar_face \ + --test avatar_face_gpu -- --nocapture 2>&1 | tee "$log" + grep -q '^test result: ok\. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out' "$log" || { echo "::error::the test did not run and pass"; exit 1; } + grep -qE "^face-gpu: key=gpu-metal-0 frames=[1-9][0-9]* same=true graphs_on_gpu=[1-9][0-9]* .* on_cpu=0 .* differed=0 errors=\[\] " "$rep" \ + || { echo "::error::no equal run on the GPU"; exit 1; } - name: wasm-bindgen CLI 0.2.127, and the wasm32-unknown-unknown target shell: bash run: | -- 2.54.0 (Apple Git-157) From da3c09c5a1aa56b4603ca377f32b73b9feb0ec87 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 10:58:47 -0400 Subject: [PATCH 028/242] format-probe-conformance: README with the corpus's provenance, licences and what was measured Co-Authored-By: Claude Opus 5.5 --- wai/format-probe-conformance/README.md | 70 ++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 wai/format-probe-conformance/README.md diff --git a/wai/format-probe-conformance/README.md b/wai/format-probe-conformance/README.md new file mode 100644 index 000000000..12c64131f --- /dev/null +++ b/wai/format-probe-conformance/README.md @@ -0,0 +1,70 @@ +# format-probe-conformance — header probes + +The corpus of [`extensions/format-probes.md`](../extensions/format-probes.md) §13 and of +[`extensions/projection.md`](../extensions/projection.md) §7. + +**Feature equivalence.** A conforming probe of a capability, given a vector's file, reaches the +outcome `vectors.json` states for it: the parameters (`{"accept": {...}}`, the members each +section of format-probes names) or the refusal (`{"refuse": code}`, a code of its §12). A +conforming reader of the projection descriptor gives each binding of `bindings` its outcome +(projection.md §5). Each fact of `facts` holds of its file's probe: what its encoder was asked for. + +- `ext/` — vectors written by encoders, from synthetic pictures (`tools/yuv.py`): + - `vvc/` H.266 Main 10 at 64×64 and at 100×58 (a conformance window); + - `mvhevc/` an MV-HEVC bitstream of two views, and `hevc/` its base layer; + - `jxs/` JPEG XS codestreams: 4:2:2 at 10 bits, R, G and B under the reversible colour + transform, and a lossless 4:4:4 one; `jxs-video/` three frames in `wai.video.jxs`'s framing; + - `avs3/` an AVS3 video stream of three pictures. +- `gen/` — what `wai_format_probe gen` writes: LCEVC streams, MHAS streams, AVS3 audio frame + headers, IEEE 1857.11 picture headers and projection descriptors from fixed values, and the + negative controls, each a change to one vector, every code of §12 given. +- `vectors.json` — every vector's capability, file, SHA-256, origin and outcome; the bindings; + the facts; the probes and codes of §2.4 and §12; and the fetched LCEVC streams with what their + encoder was asked for. +- `provenance.json` — every external vector's encoder, input and command, its decodes (the + output's size and SHA-256 under each decoder) and, for JPEG XS, whether the decoders agree. +- `fetch.sh` — fetches five LCEVC streams by digest (format-probes §13.3). + +**Provenance.** `ext/` is written by `tools/make_external.sh` on the build host, from synthetic +pictures `tools/yuv.py` writes (a ramp, a checker and a moving bar per plane; the second view +of the stereo pair is the first shifted four columns), with these encoders and decoders, each +at the revision the script pins and refuses to run without: + +| key | role | licence | +|---|---|---| +| `vvc_enc`, `vvc_dec` | an open H.266 encoder and decoder | BSD-3-Clause-Clear | +| `jxs` | an open JPEG XS encoder and decoder | BSD-2-Clause-Patent | +| `jxs2` | a second, independent JPEG XS decoder (a crate, without default features) | MIT | +| `mvhevc` | the MV-HEVC reference encoder and decoder of the joint video team | BSD-3-Clause | +| `avs3_enc` | an open AVS3 video encoder | BSD-4-Clause | +| `avs3_dec` | an open AVS3 video decoder | BSD-3-Clause | + +The script names each repository and revision. None of their code or data is in this corpus: +only the bitstreams they wrote from the synthetic pictures. `gen/` is written by +`wai_format_probe gen` from fixed values and from `ext/`; no permissively licensed encoder +writes LCEVC, MPEG-H 3D Audio or AVS3 audio, so those vectors are the generator's, laid out as +format-probes §6, §7 and §9 read them, and are not decodable streams. The fetched LCEVC streams +are an open LCEVC decoder's unit-test streams (BSD-3-Clause-Clear), written by an LCEVC encoder +from a short test sequence; their licence notice names their owner, so they are fetched by +SHA-256 and not committed. + +**Measured** (`provenance.json`, on the arm64 build host): every H.266, MV-HEVC, H.265 and AVS3 +vector decodes with its decoder, and the base layer of the MV-HEVC vector decodes to the same +bytes alone as within the two-view stream. Every JPEG XS codestream decodes to identical bytes +along the first decoder's C path and its SIMD path. The second decoder gives the same bytes on +five of the six codestreams; it does not implement the MLS.12 profile of the lossless one (it +refuses its `Ppih`), and on a copy whose `Ppih` and `Plev` are 0 its output differs, while the +first decoder's output equals that vector's source picture, byte for byte. No x86 decode was +measured. + +**Checks.** + +- `wai_format_probe verify .` (from `../wai-rs`, feature `format_probe`) and + `wai-rs/tests/format_probe.rs`: every vector's digest and outcome, every binding and fact, + the generated files byte for byte and no other file under `gen/`, and the extension's + tables; with `--fetched `, the five fetched streams, every one required. +- `format_probe_verify.py`, written from the two extensions and sharing no code with wai-rs: + probes every vector, binding, fact and fetched stream itself, and requires every mutated copy + of the corpus to fail: + + bash fetch.sh /tmp/lcevc && python3 -I format_probe_verify.py . --fetched /tmp/lcevc -- 2.54.0 (Apple Git-157) From 069b551c8505a38f9851a1e0bd50787931f2ac79 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 12:32:14 -0400 Subject: [PATCH 029/242] wai-ops-npu: the package energy counter is an OnChipCounter, not HwShunt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The powercap package-0 counter is an on-chip vendor counter, so its class is OnChipCounter (energy-measurement §2), with the whole package as its scope. HwShunt is the legacy class, accepted when read and never emitted (§2.6). Corrected in integer-operators §9.5, the CHANGELOG entry and the bench's documentation. Co-Authored-By: Claude Opus 5.5 --- wai/CHANGELOG.md | 2 +- wai/extensions/integer-operators.md | 2 +- wai/wai-ops-npu/src/bin/bench.rs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index 96431f58b..fb3af4862 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -14,7 +14,7 @@ wire format may change and such changes are recorded here. - the elementwise operators, and `lut16` a table block at a time. The plan, the checks, the range policies, the movement operators and the state stay the interpreter's, so the device adds no refusal. -- **Measured: where the unit is not exact.** The unit's shift-round step is `R_floor`, `R_half_up` and `R_half_even` (§2) under three of its modes, at shifts 0 to 59 only. At 60 to 63 every mode shifts left by `64 − s`. 36 vectors of the subset have such a shift, and the kernel forms those in the core's scalar unit. A 32-bit accumulator lane keeps the low 32 bits of a sum past i32. 110 of the 458 graphs that fail the subset by `acc32` alone gave other results on the device. Integer-operators §9.5 has the table, with time and energy, each energy reading named by class: the package counter is `HwShunt`, the runtime's own estimate is `ModelBased`. No speed or energy claim is made: the device path is bound by its dispatches. +- **Measured: where the unit is not exact.** The unit's shift-round step is `R_floor`, `R_half_up` and `R_half_even` (§2) under three of its modes, at shifts 0 to 59 only. At 60 to 63 every mode shifts left by `64 − s`. 36 vectors of the subset have such a shift, and the kernel forms those in the core's scalar unit. A 32-bit accumulator lane keeps the low 32 bits of a sum past i32. 110 of the 458 graphs that fail the subset by `acc32` alone gave other results on the device. Integer-operators §9.5 has the table, with time and energy, each energy reading named by class: the package counter is `OnChipCounter` (energy-measurement §2; its scope is the whole package), the runtime's own estimate is `ModelBased`. No speed or energy claim is made: the device path is bound by its dispatches. - **Results by neural processor (§9.5):** on PCI 0x1022:0x17f0, every graph vector in the subset gave the committed result and the interpreter's: 733 vectors, 1,005 decodes. Every one of twelve kernel edits was caught by the corpus. No other neural processor was run. - **The Web Neural Network API (§9.5):** a per-operator account from its Candidate Recommendation Draft of 21 October 2026. Its convolution, matrix and quantize operators are floating point, so the subset is exact there only when built from its `"int32"` element-wise operations and reductions; a multiplier needs `"int64"`, which the draft allows but does not require. No browser leg was run. - **`ops-conformance/npu.jsonl` (§14.5):** each graph vector's place in the subset, from its graph's own verdict. The generator writes it and the independent checker writes it again, and both must give the committed bytes. The device test runs exactly the vectors it lists. diff --git a/wai/extensions/integer-operators.md b/wai/extensions/integer-operators.md index eb6690d7f..2936cfd55 100644 --- a/wai/extensions/integer-operators.md +++ b/wai/extensions/integer-operators.md @@ -1048,7 +1048,7 @@ The vector unit's requantisation, with the scalar route for shifts of 60 to 63, | an `int_synth` graph (§13.1), `[8, 32, 32]` to `[3, 64, 64]`, three transposed convolutions | 7.22–7.26 ms | 89.1–89.2 ms (778 dispatches) | - The device path is bound by its dispatches. It uses one core, four work units per dispatch and a host round trip per operator, and the host gathers every tap. No speed claim is made. -- The package's energy counter (powercap `package-0`) is a vendor counter, a reading of class `HwShunt` while it counts. It covers the whole package and everything running on it, including other users' work on that host. Its readings per window were 26.1–29.0 J idle; 90.7–94.6 J with the CPU looping either workload; 74.7–75.4 J with the device looping the vectors; and 76.4–78.1 J with the device looping the synthesis. +- The package's energy counter (powercap `package-0`) is a vendor counter, a reading of class `OnChipCounter` (energy-measurement §2; legacy `HwShunt` is never emitted, §2.6) while it counts. It covers the whole package and everything running on it, including other users' work on that host. Its readings per window were 26.1–29.0 J idle; 90.7–94.6 J with the CPU looping either workload; 74.7–75.4 J with the device looping the vectors; and 76.4–78.1 J with the device looping the synthesis. - The device runtime also reports a power figure that it calls an estimate. Integrated over a window, that is a model, class `ModelBased`. It gave 2.0–2.6 J per device window. In idle windows it gave 0.3–1.2 J, higher right after device work, so it lags. - Per pass, the device path costs more energy than the CPU on these workloads. No energy claim is made for the device. diff --git a/wai/wai-ops-npu/src/bin/bench.rs b/wai/wai-ops-npu/src/bin/bench.rs index d9a70cb21..c989ba6e9 100644 --- a/wai/wai-ops-npu/src/bin/bench.rs +++ b/wai/wai-ops-npu/src/bin/bench.rs @@ -15,7 +15,8 @@ //! workload, and the device looping it. The energy each window reads, by source and //! class: //! - the package's energy counter (`/sys/class/powercap/intel-rapl:0`, `package-0`): a -//! vendor counter read while it counts, `HwShunt` in the family's classes; it covers +//! vendor counter read while it counts, `OnChipCounter` in WAI's classes +//! (energy-measurement §2); it covers //! the whole package and everything running on it, other users' work included; //! - the device's own power figure, sampled from its runtime's report ("Estimated //! Power"), integrated over the window: the runtime calls it an estimate, so -- 2.54.0 (Apple Git-157) From 950ae477d16cc221864e6cac4010f6bf49f6da45 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 12:35:35 -0400 Subject: [PATCH 030/242] avatar-face corpus: the mask's table pinned by its digest and entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A frame rarely reads the table at 0, so the frames alone did not catch a table without §7.6's clamp (the second implementation passed with it). The corpus now states the table's SHA-256 and its entries at nine values, and both implementations check them. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/avatars.md | 2 + wai/live-conformance/avatar-face/README.md | 2 + wai/live-conformance/avatar-face/vectors.json | 41 +++++++++++++++++++ wai/live-conformance/avatar_face_verify.py | 17 ++++++++ wai/wai-rs/src/avatar_face/corpus.rs | 20 +++++++++ 5 files changed, 82 insertions(+) diff --git a/wai/extensions/avatars.md b/wai/extensions/avatars.md index c6b6810d4..f969ef018 100644 --- a/wai/extensions/avatars.md +++ b/wai/extensions/avatars.md @@ -751,6 +751,8 @@ fixed seeds: `seeded.wfg`, a generator of three keypoints at 16 × 16 drawn from label and trained on nothing; `seeded.wfi`, a picture that is an integer function of a label; and `vectors.json`, which holds: +- **the mask's table** of §7.6: its SHA-256, each entry a 16-bit little-endian signed integer, + `T[0]` first, and its entries at some values; - **forms**: files of §7.2 to §7.4 and how each reads, its fields or its refusal, every code of §10's second table at least once; - **frames**: for the seeded generator and identity at three sets of keypoints, and for the diff --git a/wai/live-conformance/avatar-face/README.md b/wai/live-conformance/avatar-face/README.md index cb3e1a6f8..c5cb56e4d 100644 --- a/wai/live-conformance/avatar-face/README.md +++ b/wai/live-conformance/avatar-face/README.md @@ -14,6 +14,8 @@ inputs it reads, and the two files its generator writes beside it. `vectors.json` names every input and written file with its SHA-256, and holds: +- `mask_table`: the SHA-256 of §7.6's table (each entry a 16-bit little-endian signed + integer, `T[0]` first) and, in `at`, its entries at some values `v`, each `[v, T[v + 32768]]`; - `forms`: files of §7.2 to §7.4, each `{"name", "form", "hex", "outcome"}` with, where it reads, `read` (the header's fields; for an identity its keypoints and the SHA-256 of its picture; for a segment its fields and every frame's keypoints in sixteenths of a pixel) and, where it is diff --git a/wai/live-conformance/avatar-face/vectors.json b/wai/live-conformance/avatar-face/vectors.json index 5c773a1e6..65761d872 100644 --- a/wai/live-conformance/avatar-face/vectors.json +++ b/wai/live-conformance/avatar-face/vectors.json @@ -38,6 +38,47 @@ } ], "segment_step": 4, + "mask_table": { + "sha256": "91f890606a5853dbcb5b4701bafc60933f617c5beee518a9401401acbd336ae3", + "at": [ + [ + -32768, + 0 + ], + [ + -32767, + 10 + ], + [ + -4096, + 8818 + ], + [ + -1, + 16384 + ], + [ + 0, + 16384 + ], + [ + 1, + 16384 + ], + [ + 2, + 16386 + ], + [ + 4096, + 23950 + ], + [ + 32767, + 32758 + ] + ] + }, "forms": [ { "name": "generator", diff --git a/wai/live-conformance/avatar_face_verify.py b/wai/live-conformance/avatar_face_verify.py index 56cfaf363..21f971e68 100644 --- a/wai/live-conformance/avatar_face_verify.py +++ b/wai/live-conformance/avatar_face_verify.py @@ -580,6 +580,13 @@ def form_outcome(v): return {"outcome": "read", "read": r} +def table_outcome(): + """§7.6's table: its SHA-256 (each entry a 16-bit little-endian signed integer, T[0] first) + and its entries at the values the corpus names.""" + entries = [sigmoid_entry(v) for v in range(-32768, 32768)] + return {"sha256": sha(b"".join(struct.pack(">) -> Vec { out } +// ---- the mask's table (§7.6) ---- + +/// The values of `v` whose table entries the corpus states. +const TABLE_AT: [i64; 9] = [-32768, -32767, -4096, -1, 0, 1, 2, 4096, 32767]; + +/// The table's digest (each entry a 16-bit little-endian signed integer, `T[0]` first) and +/// the entries at [`TABLE_AT`]. +fn mask_table() -> Value { + let t = sigmoid_table(); + let bytes: Vec = t.iter().flat_map(|v| v.to_le_bytes()).collect(); + json!({ + "sha256": sx(&bytes), + "at": TABLE_AT.iter().map(|&v| json!([v, t[(v + 32768) as usize]])).collect::>(), + }) +} + // ---- JSON ---- fn case_json(v: &Vector) -> Map { @@ -914,6 +930,7 @@ pub fn generate(inputs: &BTreeMap>) -> Vec<(String, Vec)> { "inputs": INPUTS.iter().map(|(n, d)| json!({"file": n, "sha256": d})).collect::>(), "written": written.iter().map(|(n, b)| json!({"file": n, "sha256": sx(b)})).collect::>(), "segment_step": STEP, + "mask_table": mask_table(), "forms": forms, "frames": frame_vectors(&files), "cases": cases, @@ -937,6 +954,9 @@ pub fn verify(files: &BTreeMap>) -> Vec { fails.push(format!("form {name}")); } } + if doc["mask_table"] != mask_table() { + fails.push("the mask's table differs".into()); + } let frames: Vec = frame_vectors(files); if doc["frames"].as_array() != Some(&frames) { fails.push("the frame vectors differ".into()); -- 2.54.0 (Apple Git-157) From 678ff667204e21232adf2ac557d0fdfcc0fea402 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 13:01:29 -0400 Subject: [PATCH 031/242] =?UTF-8?q?wai:=20stream=20termination=20and=20the?= =?UTF-8?q?=20display=20match=20=E2=80=94=20spec,=20wai-rs=20module,=20cor?= =?UTF-8?q?pus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit stream-termination.md registers wai.live.termination: a sink's signed stream log (WSL1) with an open record (autoplay setting, termination policy, display declaration, ladder), a receipt per segment (the rendition's match against the panel, each hop's joules with its class), observations (visibility, occlusion, display power, interaction) and a termination with its reason, media time and totals per hop and class. capability-constraints §4 adds the display declaration, the match and the admissible renditions, and wai:transfer to the register. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/capability-constraints.md | 99 +- wai/extensions/stream-termination.md | 538 +++++ wai/live-conformance/termination/vectors.json | 1844 +++++++++++++++++ wai/wai-rs/Cargo.toml | 20 + .../src/bin/wai_stream_termination_vectors.rs | 55 + wai/wai-rs/src/codecs/registry.rs | 3 + wai/wai-rs/src/lib.rs | 6 + wai/wai-rs/src/stream_termination.rs | 1287 ++++++++++++ wai/wai-rs/src/stream_termination/corpus.rs | 755 +++++++ wai/wai-rs/src/stream_termination/tests.rs | 81 + wai/wai-rs/tests/stream_termination.rs | 96 + 11 files changed, 4778 insertions(+), 6 deletions(-) create mode 100644 wai/extensions/stream-termination.md create mode 100644 wai/live-conformance/termination/vectors.json create mode 100644 wai/wai-rs/src/bin/wai_stream_termination_vectors.rs create mode 100644 wai/wai-rs/src/stream_termination.rs create mode 100644 wai/wai-rs/src/stream_termination/corpus.rs create mode 100644 wai/wai-rs/src/stream_termination/tests.rs create mode 100644 wai/wai-rs/tests/stream_termination.rs diff --git a/wai/extensions/capability-constraints.md b/wai/extensions/capability-constraints.md index 6c4b98518..2422f97bb 100644 --- a/wai/extensions/capability-constraints.md +++ b/wai/extensions/capability-constraints.md @@ -4,7 +4,9 @@ > check** rather than a binary claim, by ingesting the constraint-set grammar and > register pattern the broadcast-IP world already ratified. No transport, no > registry service, no negotiation protocol — it evaluates values WAI already -> carries. +> carries. A sink also declares what its display presents — resolution, refresh +> rate, bit depth, dynamic range and physical size — and no rendition above that is +> sent to it (§4); the receipt of each segment records how its rendition matched. > Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. ## 1. The problem: support is not binary @@ -82,24 +84,109 @@ integer in a stated unit here. | `wai:sample_rate` | integer | audio samples per second, per channel | | `wai:channel_count` | integer | audio channels | | `wai:bitrate` | integer | bits per second | +| `wai:transfer` | integer | the video's transfer characteristics, as a `TransferCharacteristics` code point of the coding-independent code points (ISO/IEC 23001-8, as `draft-pantos-hls-rfc8216bis-22` §4.4.6.2 cites it): 1, 6, 13, 14 and 15 are standard dynamic range, 16 is PQ and 18 is HLG | The families §3 names (neural, quantum, world, haptic) have no entries yet, and stay unconstrained until they do. The form a page evaluates constraint sets in, and their evaluation, are [browser-sink](browser-sink.md) §5; its reference implementation is the `browser_sink` module of `wai-rs`. -## 4. What this is not +## 4. Display capability + +Bits a panel cannot present are spent and never seen: pixels above its resolution, pictures +above its refresh rate, bits per sample above its depth, a dynamic range above its own. A +sink states what its panel presents in a **display declaration**, and a rendition above it is +not sent. The declaration is a statement the sink makes once to whatever chooses its +renditions, and makes again when its panel changes; nothing is exchanged beyond it. + +### 4.1 The declaration + +| member | value | what it is | +|---|---|---| +| `long` | integer, at least 1 | the panel's longer side, in its own pixels | +| `short` | integer, from 1 to `long` | its shorter side | +| `refresh_milli` | integer, at least 1 | the most pictures it presents per 1000 seconds; for a panel whose rate varies, its highest | +| `bit_depth` | integer, 1 to 16 | the bits per colour component it presents | +| `hdr` | boolean | whether it presents the range of the high-dynamic-range transfers, PQ and HLG, above standard dynamic range's | +| `long_mm`, `short_mm` | integer, 0 or more | its physical size along its longer and shorter sides, in millimetres; 0 where the sink does not know it | + +The sides are stated without orientation, so a sink that turns its panel declares nothing +new. A native host reads every member from its platform's description of the display the +stream is presented on. A page reads them through the interfaces it has: + +- `long` and `short`: CSSOM View's `screen.width` and `screen.height`, which are in CSS + pixels, each times `devicePixelRatio`, rounded to the nearest integer; +- `bit_depth`: ⌊`screen.colorDepth` / 3⌋, which is 8 where the user agent does not say + (CSSOM View has `colorDepth` return 24 when it does not know the depth or withholds it); +- `hdr`: true when the media query `(video-dynamic-range: high)` or `(dynamic-range: high)` + matches (Media Queries Level 5 §6.5, §8.2); +- `refresh_milli`: no interface states it, so a page measures it: 1000 times the whole + number of pictures per second nearest to 10� divided by the median interval, in + microseconds, between at least 60 consecutive animation frames; +- `long_mm` and `short_mm`: 0, since a page has no interface for them. + +Texts read for this section: CSSOM View Module, W3C Working Draft 16 September 2025, §2.1 +and §4.3 (`Screen`, `devicePixelRatio`); Media Queries Level 5, W3C Working Draft +19 February 2026, §6.5 and §8.2; `draft-pantos-hls-rfc8216bis-22` §4.4.6.2 (`RESOLUTION`, +`FRAME-RATE`, `VIDEO-RANGE`). + +### 4.2 The match + +A rendition is read through the parameters of §3.1: `w` = `wai:frame_width`, `h` = +`wai:frame_height`, `r` = `wai:frame_rate_milli`, `d` = `wai:bit_depth` and `x` = +`wai:transfer`, with `x` = 1 where the rendition declares none (an HLS variant stream without +`VIDEO-RANGE` is standard dynamic range, rfc8216bis-22 §4.4.6.2). Its **exceedances** against +a declaration are the bits: + +| bit | name | set when | +|---|---|---| +| 0 | `resolution` | max(`w`, `h`) > `long`, or min(`w`, `h`) > `short` | +| 1 | `refresh` | `r` > `refresh_milli` | +| 2 | `bit_depth` | `d` > `bit_depth` | +| 3 | `dynamic_range` | `x` is 16 or 18, and `hdr` is false | + +A rendition is **within** the declaration when its exceedances are 0. Of the renditions a +sender offers a sink (its **ladder**), the **admissible** ones are those within the +declaration; where none is, the least rendition alone is admissible. The **least** is the +first in the order of (`w` × `h`, `r`, `d`, 1 where `x` is 16 or 18 and 0 otherwise), +ascending, and of two equal there, the one the ladder lists first. A panel smaller than every +rendition is still served, by the least of them. + +The **match** of a rendition is its exceedances, with bit 4, `none_within`, set when no +rendition of the ladder is within the declaration. It is what a receipt records +([stream-termination](stream-termination.md) §3.4). + +- A sender that holds a sink's declaration **MUST NOT** send it a rendition that is not + admissible. A sink that chooses its own renditions — an HLS or DASH player's selection, a + MoQ subscriber's choice of track — **MUST NOT** request one that is not admissible. +- A sink **SHOULD** declare its panel to whatever chooses its renditions, before the first + is chosen, by a means this revision does not fix, and **MUST** declare it again when the + panel changes (a window moved to another display, its dynamic range switched). +- The physical size bounds nothing in this revision: how much of a resolution a viewer + resolves depends on how far away the viewer is, which a sink does not know. It is + declared and recorded. +- A receipt of a segment records the match of its rendition against the declaration in + effect when it was received, and a verifier refuses a segment of a rendition that was + not admissible (stream-termination §6.1, `rendition_above_panel`). + +The check is made before dispatch, as §2's is, and like §2's an unmet declaration is not a +decode error: the rendition is not chosen. + +## 5. What this is not - It is **not** a negotiation protocol. There is no pre-flight exchange, no - controller, and no registry service. Capability-plus-budget binding before + controller, and no registry service. The display declaration of §4 is a statement + the sink makes, which a sender evaluates; nothing answers it. Capability-plus-budget binding before execution is already owned by JCP, and duplicating it here would put two mechanisms in the tree for one job. - It is **not** a transport concern. Constraint evaluation reads values the object already declares. -- It is **not** an energy mechanism. A constraint says what a sink can decode, - never what it should spend. +- It is **not** an energy mechanism. A constraint set says what a sink can + decode, and a display declaration what it can present; neither says what it + should spend. What a rendition above the panel would have cost is not claimed + as saved by this text. -## 5. Framing +## 6. Framing The grammar is thin — close to a JSON-Schema subset — and that is fine. The value is not expressiveness; it is that a ratified, register-backed way of saying diff --git a/wai/extensions/stream-termination.md b/wai/extensions/stream-termination.md new file mode 100644 index 000000000..980359f79 --- /dev/null +++ b/wai/extensions/stream-termination.md @@ -0,0 +1,538 @@ +# WAI Extension: Stream Termination + +> Status: Draft. Registers the record `wai.live.termination`: a sink's signed account of one +> stream it received, from its start to the moment it stopped, and why it stopped. The sink +> signs a **stream log**: a chain of records, `WSL1`, each naming the one before it by digest, +> under a key it makes for its playback session. The log opens with the stream, how it started +> (by a user's activation, or by autoplay, with the viewer's autoplay setting recorded beside +> the platform's answer), the sink's termination policy and its **display declaration** +> ([capability-constraints](capability-constraints.md) §4); it then holds a **receipt for each +> segment** the sink received, with the rendition and how it matched the panel and each hop's +> joules with their acquisition class ([energy-measurement](energy-measurement.md) §2); the +> **observations** of whether anyone could be watching (the page's visibility, occlusion where +> the platform reports it, the display's power and the viewer's interaction); and it ends with +> a **termination**: the reason (hidden, occluded, display off, idle, autoplayed with no +> interaction, stopped by the viewer, or ended), the media time, and the joules of each hop up +> to that point, a total per hop and class and never one across classes. A verifier checks the +> chain, that nothing follows the termination, that the reason is one the log's own +> observations support, that every segment's rendition was admissible under the panel, and the +> totals; it reports how much was delivered while nobody could see it. No new transport. The +> reference implementation is the `stream_termination` module of `wai-rs` (feature +> `stream_termination`), which `wai/wai-live-web` builds for a page; the page's own code is +> `joule-code/joule-code-rs/crates/joule-moq-web/web/room/termination.js`; the corpus is +> [`live-conformance/termination/`](../live-conformance/termination/), and its second +> implementation is `live-conformance/stream_termination_verify.py`. +> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. +> +> Texts read for this revision, each pinned to the version named: the HTML Standard, Living +> Standard last updated 20 October 2026, §6.2 (page visibility: a document's visibility state, +> "update the visibility state" and the `visibilitychange` event) and §6.4 (user activation: +> the activation-triggering input events and the `UserActivation` interface); Page Visibility +> Level 2, W3C Discontinued Draft 23 June 2022, which defers to the HTML Standard and was read +> for its note on when a document is hidden; Autoplay Policy Detection, W3C Working Draft +> 4 September 2025, §2.1 (`getAutoplayPolicy()` and its three values); Intersection Observer, +> W3C Working Draft 18 October 2023, §2.3 (`isIntersecting`); and the texts +> capability-constraints §4.1 names. A section number in this text is this extension's unless +> it names another document. + +## 1. Scope + +In scope: the stream log, its records, their signature and their rules (§3); what a sink +observes, when it terminates a stream, the autoplay setting, the segments it receipts and the +hop figures it states (§4); what a sender does with a termination and a display declaration +(§5); verification, the state a log shows at each instant and what an accepted log states +(§6); and the reasons (§7). + +Out of scope: a transport, and how a log reaches a sender or an operator, which a deployment +carries as it carries its other receipts; how a meter measures, which energy-measurement +governs; a figure for the display's own energy, which this revision does not compute; and +attention, which nothing here measures: a stream nobody *can* see is in scope, one nobody +*looks at* is not. + +Relationship to the core spec: `wai.live.termination` is a **record** ([SPEC](../SPEC.md) §5): +a signed record a WAI tool emits and verifies. It names no payload format and is not +dispatchable. + +Relationship to the other extensions: + +- **[capability-constraints](capability-constraints.md) §4.** The display declaration, the + match and the admissible renditions. A segment receipt records the match (§3.4), and §6.1 + step 6 refuses a segment of a rendition that was not admissible. +- **[energy-measurement](energy-measurement.md) §2.** Each hop figure's class, its tag and its + evidence (§2.7), and the unmetered marker, which is never a figure of zero joules. +- **[live-metering](live-metering.md).** A hop's own record of a room session is per session + and per hop; a stream log is per stream at one sink. The two are separate records, and + neither restates the other. +- **[ad-presentation](ad-presentation.md) §3, §10.** The session key is made as that + extension's is, and a sink that measures a fraction in view for an ad claim reads the same + observations this extension records. +- **[delivery-telemetry](delivery-telemetry.md).** Its observations are unsigned and + aggregated; a stream log is signed and is one stream's. + +## 2. Terms + +- **Sink**: the software that receives a stream and presents it: a page, or a native host. +- **Stream**: one sink's reception of one media track: a MoQ subscription to a track, or an + HTTP client's requests for the segments of one media type of a presentation. Its **media** + is video or audio. +- **Segment**: what a sink receipts as one unit of a stream: one HTTP segment, or the objects + of a MoQ track the sink received over a span of media time it chooses (§4.5). +- **Session key**: an Ed25519 key (RFC 8032) the sink makes for one playback session (§4.1). +- **Unseen**: a video stream is unseen while its document is hidden, its presentation area is + occluded or its display is off (§4.2). +- **Hop**: a party whose energy a segment's delivery and presentation cost, by its **role**: + 1 origin, 2 relay, 3 sink decode, 4 display. +- **Unmetered marker**: a zero figure with class tag 0 (energy-measurement §2), never a figure + of zero joules. A hop the sink states nothing about is **absent**, which is not the marker + either: the marker says the hop took place and was not measured, and absence says nothing. +- *u64*, *u32*, *u8*: little-endian unsigned integers of that many bits; the class's evidence + is big-endian, as energy-measurement §2.7 writes it. A digest is SHA-256 (FIPS 180-4), + 32 bytes. Times `t` are microseconds since 1970-01-01T00:00:00Z on the sink's clock; media + times are microseconds of the stream's media timeline. + +## 3. The stream log + +### 3.1 Records + +A **stream log** is a sequence of records, each: + +| field | size | meaning | +|---|---|---| +| `magic` | 4 | `WSL1` (ASCII) | +| `version` | 1 | 1 in this revision | +| `kind` | 1 | 1 open, 2 segment, 3 observation, 4 display, 5 terminate | +| `sink` | 32 | the session key, which signs the record | +| `seq` | 4 | u32: the record's index in the log, from 0 | +| `prev` | 32 | the digest of the record before it; 32 zero bytes for the first | +| `t` | 8 | u64: when the sink made the record | +| `body` | … | the kind's body (§3.2 to §3.7) | +| `sig` | 64 | §3.8 | + +### 3.2 The open record + +The first record of a log, and the only one of kind 1: + +| field | size | meaning | +|---|---|---| +| `stream` | 32 | the stream: for a MoQ track, its track digest ([live-metering](live-metering.md) §2: SHA-256 over the namespace as live-rooms §6.2 renders it, one zero byte, and the name); for an HTTP stream, SHA-256 over the URI of the content description as the sink requested it | +| `media` | 1 | 1 video, 2 audio | +| `start` | 1 | 1: a user activation started it; 2: it started with none (autoplay) | +| `autoplay` | 1 | the viewer's autoplay setting at the sink: 1 on, 2 off (§4.4) | +| `ua_policy` | 1 | the platform's answer for autoplay (§4.4): 0 not asked, 1 `allowed`, 2 `allowed-muted`, 3 `disallowed` | +| `unseen_grace_ms` | 4 | u32: how long a video stream stays unseen before the sink terminates it (§4.3) | +| `idle_ms` | 4 | u32: how long with no interaction before it terminates the stream as idle; 0 for never | +| `autoplay_grace_ms` | 4 | u32: how long an autoplayed stream with no interaction runs before it terminates it; 0 for never | +| `display` | 22 | the display declaration (§3.3) | +| `ladder` | 1 + 14 × *n* | a u8 count *n*, then each rendition the sender offers: `width`, `height` and `frame_rate_milli` (u32 each), `bit_depth` (u8) and `transfer` (u8), the parameters of capability-constraints §3.1 | + +A video stream has a ladder of 1 to 32 renditions and a display declaration; an audio stream +has a ladder of none, and its declaration is 22 zero bytes. + +### 3.3 The display declaration + +capability-constraints §4.1's members, 22 bytes: + +| field | size | meaning | +|---|---|---| +| `long` | 4 | u32 | +| `short` | 4 | u32 | +| `refresh_milli` | 4 | u32 | +| `bit_depth` | 1 | u8 | +| `hdr` | 1 | 0 false, 1 true | +| `long_mm` | 4 | u32; 0 not known | +| `short_mm` | 4 | u32; 0 not known | + +It is the body of a **display record** (kind 4) as well, which a sink signs when its panel +changes: the declaration in effect at a record is the one in the last display record before +it, or the open record's where there is none. + +### 3.4 The segment record + +A **segment receipt** (kind 2): + +| field | size | meaning | +|---|---|---| +| `segment` | 32 | the segment: for an HTTP segment, SHA-256 over its bytes as received; for a MoQ segment, SHA-256 over the content hashes ([jwp-receipts](jwp-receipts.md) §2.1) of its objects in the order received, concatenated | +| `objects` | 4 | u32: the objects it holds, at least 1; 1 for an HTTP segment | +| `bytes` | 8 | u64: their payload bytes as received | +| `media_start` | 8 | u64: the media time of its first sample | +| `media_end` | 8 | u64: the media time its last sample ends at, above `media_start` | +| `rendition` | 1 | for video, its rendition's index in the open record's ladder; for audio, 255 | +| `match` | 1 | for video, the match of that rendition (capability-constraints §4.2) against the declaration in effect: bits 0 to 3 its exceedances, bit 4 `none_within`, every other bit 0; for audio, 0 | +| `hops` | 1 + … | a u8 count, at most 4, then each hop entry (§3.5) in strictly ascending order of `role` | + +### 3.5 Hop entries + +| field | size | meaning | +|---|---|---| +| `role` | 1 | 1 origin, 2 relay, 3 sink decode, 4 display | +| `joules` | 8 | u64: the hop's energy for this segment, in microjoules; 0 is the unmetered marker | +| `class` | 1 + … | its acquisition class: energy-measurement §2.7's tag byte, then that tag's evidence | + +A hop with no entry is absent (§2). + +### 3.6 The observation record + +| field | size | meaning | +|---|---|---| +| `signal` | 1 | 1 visibility, 2 occlusion, 3 display power, 4 interaction | +| `value` | 1 | visibility: 1 visible, 2 hidden; occlusion: 1 not occluded, 2 occluded; display power: 1 on, 2 off; interaction: 1 | + +What each signal is, and where a sink reads it, is §4.2. + +### 3.7 The terminate record + +| field | size | meaning | +|---|---|---| +| `reason` | 1 | 1 `hidden`, 2 `occluded`, 3 `display_off`, 4 `idle`, 5 `autoplay_unattended`, 6 `user`, 7 `ended` (§4.3) | +| `media` | 8 | u64: the media time at which the sink stopped presenting the stream | +| `segments` | 4 | u32: the segment receipts of the log | +| `totals` | 1 + 14 × *m* | a u8 count *m*, then for each hop role and class among the segments' metered hop entries: `role` (u8), `tag` (u8), `figures` (u32, the entries of that role and tag) and `joules` (u64, their sum), in strictly ascending order of (`role`, `tag`) | +| `unmetered` | 1 + 5 × *k* | a u8 count *k*, then for each role with entries that are the unmetered marker: `role` (u8) and `count` (u32), in strictly ascending order of `role` | + +The totals are the joules of each hop up to the termination: one per role and class, never +one across two classes or two roles (live-metering §4.5 gives the reason). A role in neither +list is absent from every segment, and a verifier names it so (§6.3). + +### 3.8 Digest and signature + +The **digest** of a record is SHA-256 over its bytes before `sig`. Its **signature** is +Ed25519 by `sink` over the 15 bytes `wai:stream-log` (ASCII) and 0x01, followed by the bytes +the digest covers, verified strictly, as [SPEC](../SPEC.md) §7 *Signatures* requires: a key +that fails step 1 or 2 is `bad_key`, and a signature that fails a later step is +`bad_signature`. + +### 3.9 Reading + +A reader reads exactly the bytes it is given as one record, as live-events §3.3 reads a +record with `WSL1` for `WEV1`: fewer than 4 bytes, or a magic of `WSL1` with fewer than 5, is +`truncated`; another magic is `malformed`; a version other than 1 is `unknown_version`; a +`kind` other than 1 to 5 is `malformed`; running out of bytes before `sig` ends is +`truncated`, and bytes after it are `malformed`. The class's evidence is read as +energy-measurement §2.7 writes it, and a tag other than 4 and 5 is followed by nothing; for +tag 5 a reference length above 255 is `malformed`. + +It then refuses, in this order: + +1. as `reason_unknown`, a terminate record whose `reason` is not 1 to 7; +2. as `energy_invalid`, a hop entry whose class breaks live-metering §3.6's `energy_invalid` + rules over its `joules`: the tag is 0 exactly when `joules` is 0; the tag is 0, 2, 3, 4 or + 5, never the legacy 1 (energy-measurement §2.6); for tags 4 and 5, `relative_ppm` and + `window_us` are above 0 and `joules` × `relative_ppm` ≥ 288 676 (energy-measurement §2.3), + tag 4's filtering byte is 0, 1 or 2, and tag 5's reference is UTF-8 and not only white + space; +3. as `display_invalid`, an open record of video or a display record whose declaration breaks + capability-constraints §4.1: `short` from 1 to `long`, `refresh_milli` at least 1, + `bit_depth` 1 to 16, `hdr` 0 or 1; and an open record of audio whose declaration is not 22 + zero bytes; +4. as `record_invalid`, a record that breaks any of these: + - open: `media`, `start` and `autoplay` are 1 or 2, `ua_policy` 0 to 3; a video stream's + ladder holds 1 to 32 renditions, each with `width`, `height` and `frame_rate_milli` at + least 1 and `bit_depth` 1 to 16; an audio stream's holds none; + - segment: `objects` at least 1; `media_start` below `media_end`; no bit of `match` above + bit 4; at most 4 hop entries, each `role` 1 to 4, in strictly ascending order; + - observation: `signal` 1 to 4; `value` 1 or 2 for signals 1 to 3, and 1 for signal 4; + - terminate: each total's `role` 1 to 4, `tag` 2 to 5 and `figures` at least 1, in the + order §3.7 gives; each unmetered entry's `role` 1 to 4 and `count` at least 1, in its + order. + +and then checks the signature (§3.8). + +## 4. What a sink records + +### 4.1 The session key + +A sink **MUST** make a fresh Ed25519 key for each playback session, from its platform's +random generator, and **MUST NOT** derive it from an identifier of the viewer, the device or an +account; it signs every log of that session with it. It presents the key's public part to +the senders of its streams with its first request, by a means this revision does not fix. It +**MAY** make a key per stream instead, so that no two of its logs link. + +### 4.2 The attention state + +A sink records an observation when a signal changes, at the time it learns of the change, and +one for each signal whose value at the open record is not the initial value below, with the +open record's `t`. Before any observation, a stream is **visible**, **not occluded**, its +display **on**, and it has seen no interaction. + +| signal | what it is | where a page reads it | where a native host reads it | +|---|---|---|---| +| 1 visibility | whether the document, or the application, is shown at all | the document's visibility state, `document.visibilityState`, on each `visibilitychange` event (HTML §6.2) | the platform's state of its window or application: minimised, in the background, behind the lock screen | +| 2 occlusion | whether the area the video is presented in can be seen, while the document is visible | the element that presents the video does not intersect the viewport: an `IntersectionObserver` on it reports `isIntersecting` false (Intersection Observer §2.3) | the platform's report that the window is fully covered, where it makes one | +| 3 display power | whether the display is on | a page has no interface for it; a display that goes off makes a document hidden on the platforms the HTML Standard's note describes, which the page records as visibility | the platform's notice that the display slept or woke | +| 4 interaction | that the viewer interacted | an activation-triggering input event (HTML §6.4: a trusted `keydown`, `mousedown`, `pointerdown`, `pointerup` or `touchend` of the kinds that section lists) | the platform's input to the application | + +A sink records at most one interaction observation per second: the rules of §4.3 read only the +time of the last one, and a finer record says more about the viewer than they need. An +interaction observation carries nothing of the input. + +The HTML Standard has a document hidden when its window is minimised, its tab is in the +background, or a system element such as a task switcher covers it; Page Visibility Level 2's +note adds the lock screen. Neither requires a user agent to report a window that other +windows cover, and Page Visibility Level 2 allows one to report it visible. A page records +occlusion only for what it can see through Intersection Observer, which is the element's +place against the viewport and not what other content covers (its §1 sets pixel-accurate +visibility outside its goals). A sink records what its platform reports and claims nothing it +does not. + +### 4.3 Termination + +A sink that terminates a stream signs a terminate record, with the reason, and then: + +- **MUST** end its request for the stream: unsubscribe from the MoQ track, or request no + further segment of it; +- **MUST NOT** sign any record of the log after the terminate record. A segment that arrives + after it, already in flight, is discarded and not receipted. A stream terminated as hidden + sends no further segment receipt; +- **MAY** open a new log for the same stream when the condition ends (the document visible + again, an interaction), with a new open record; a log is never resumed. + +The reasons, each with the state the log must show at the terminate record (§6.2): + +| reason | when | +|---|---| +| 1 `hidden` | a video stream whose document has been hidden for at least `unseen_grace_ms` | +| 2 `occluded` | a video stream whose presentation area has been occluded for at least `unseen_grace_ms` | +| 3 `display_off` | a video stream whose display has been off for at least `unseen_grace_ms` | +| 4 `idle` | `idle_ms` is above 0, and the last interaction, or the open record where there was none, is at least `idle_ms` before | +| 5 `autoplay_unattended` | a stream that started by autoplay, with `autoplay_grace_ms` above 0, no interaction since it opened, and open for at least `autoplay_grace_ms` | +| 6 `user` | the viewer stopped it | +| 7 `ended` | anything else: the stream ended, the session closed, the sink moved to another point of the stream or to another stream | + +A sink **SHOULD** terminate a video stream that has been unseen for `unseen_grace_ms`, and a +stream that meets its own idle or autoplay rule, and **MUST** state as the reason one that the +log supports. Hidden, occluded and display-off apply to video alone: a hidden document still +plays audio someone may hear, so a sink that terminates the video of a presentation as hidden +keeps its audio, whose own log goes on. The grace periods and timeouts are the deployment's +choice; a value of the open record is the sink's statement of the policy it applied. + +### 4.4 The autoplay setting + +`autoplay` is the viewer's setting at the sink, which a service offers its viewers (a player's +"autoplay off"). `ua_policy` is the user agent's answer to `navigator.getAutoplayPolicy` for +media elements (Autoplay Policy Detection §2.1), or 0 where the sink did not ask or has no +such interface. A sink **MUST NOT** start a stream by autoplay when its viewer's setting is off +or the platform answered `disallowed`; a log that states such a start is refused +`autoplay_against_setting`. + +### 4.5 Segments and the panel + +A sink signs a segment receipt for each segment it received and verified, as it receives it. +For a MoQ track it chooses the span: the objects of one group, or of a span of media time +**at most** 10 seconds long, ending at a group boundary. It states the rendition the segment +is of, and the match of that rendition against the declaration in effect +(capability-constraints §4.2), computed over the open record's ladder. + +The ladder is what the sender offers: an HLS multivariant playlist's variant streams with +their `RESOLUTION`, `FRAME-RATE` and `VIDEO-RANGE` (SDR as transfer 1, HLG as 18, PQ as 16; +rfc8216bis-22 §4.4.6.2), a DASH MPD's video representations, or the video tracks of a MoQ +catalog with their `width`, `height` and `framerate`. Where the content description does not +state a parameter, the sink states what it reads from the decoder's configuration: the coded +size, the bit depth of the codec string's profile, and transfer 1 where the stream carries no +colour description. + +### 4.6 Hop figures + +A segment receipt states, for each hop the sink holds a figure for, that figure with its +class (energy-measurement §2): + +- **origin**: the figure the segment's object receipts carry in their labelled leaves + (jwp-receipts §2.2), composed over the segment's objects as jwp-receipts §2.4.1 composes a + group total; the unmetered marker where every object's figure is the marker; absent where a + figure is unlabelled, since an unlabelled figure has no class; +- **relay**: a figure a relay states for the segment, where its deployment gives the sink + one; this revision defines no such statement, so a relay hop is usually absent; +- **sink decode**: the sink's own figure for decoding the segment, measured as + energy-measurement §3 and §4 have it, or the unmetered marker where the sink decoded it with + no meter; +- **display**: a figure the sink's platform states for presenting the segment, or the + unmetered marker. This revision computes none. + +A hop the sink knows took place but did not measure is the unmetered marker; it **MUST NOT** be +written as a figure of zero joules, and a reader **MUST NOT** total it as one. + +## 5. What a sender does + +- A sender that holds a sink's display declaration **MUST NOT** send it a rendition that is + not admissible (capability-constraints §4.2). +- A sender that receives a terminate record, or the end of the request that goes with it + (§4.3), **MUST** stop sending the stream to that sink; it **MAY** keep sending the same + presentation's other streams, its audio among them. +- A sender **MAY** verify a log it is given (§6) and keep it with its own records of the + session. A log is the sink's statement: its observations and figures rest on the sink's key + (§9). + +## 6. Verification + +### 6.1 Steps + +A verifier is given the records of one log, in order. For each record, in order, it runs +these steps and stops at the first refusal, which it reports with the reason and the record's +index: + +1. **Reading** (§3.9): `truncated`, `malformed`, `unknown_version`, `reason_unknown`, + `energy_invalid`, `display_invalid`, `record_invalid`, `bad_key`, `bad_signature`. +2. **After the termination**: a record after a terminate record is `after_termination`. +3. **Order**: the first record is an open record and no other is; each `seq` is the record's + index; each `prev` is the digest of the record before it, and 32 zero bytes for the first; + each `t` is at least the `t` before it. Otherwise `order_invalid`; a log of no record is + refused `order_invalid` at index 0. Each record's `sink` is the first's: otherwise + `sink_changed`. +4. **The open record**: `start` is 2 while `autoplay` is 2 or `ua_policy` is 3: + `autoplay_against_setting`. +5. **A display record** in the log of an audio stream: `display_invalid`. +6. **A segment receipt**: + 1. its `media_start` is at least the `media_end` of the segment receipt before it, where + there is one: otherwise `media_order_invalid`; + 2. for video, its `rendition` is below the ladder's count, and for audio it is 255: + otherwise `rendition_unknown`; + 3. for video, its `match` is the match capability-constraints §4.2 gives its rendition + against the declaration in effect over the ladder, and for audio it is 0: otherwise + `match_mismatch`; + 4. for video, its rendition is admissible: otherwise `rendition_above_panel`. +7. **The terminate record**: + 1. where a segment receipt precedes it, its `media` lies from the last one's `media_start` + to its `media_end`, both included: otherwise `media_order_invalid`; + 2. its reason is `hidden`, `occluded` or `display_off` in the log of an audio stream: + `reason_not_applicable`; + 3. the state of §6.2 at its `t` does not support its reason by the table of §4.3: + `reason_unsupported`; + 4. its `segments`, `totals` and `unmetered` are not those the segment receipts before it + give (§3.7): `totals_mismatch`. + +A log that passes is **accepted**. One with no terminate record is accepted as **open**: the +stream is still running, or its sink stopped without saying why. + +### 6.2 The state at an instant + +The state of a log at a record is read from the observation records before it. For each of +visibility, occlusion and display power it is the value of the last observation of that +signal, or the initial value of §4.2, and its **since** is the `t` of the earliest observation +of the run of observations of that signal, ending with the last, that all state the same +value, or the open record's `t` where there is none. The **last interaction** is the `t` of +the last interaction observation, or none. + +A video stream is **unseen** at a record when visibility is hidden, occlusion occluded or +display power off there. Reading the observation records in order from the initial state, its +**unseen since** is the `t` of the observation after which it became unseen, from a state in +which it was not, and has stayed unseen at every observation since. + +At a terminate record at time `T`, with the open record's `t` written `T0`: + +- `hidden` is supported when visibility is hidden and `T` − since ≥ `unseen_grace_ms` × 1000; +- `occluded` when occlusion is occluded, and `display_off` when display power is off, each + with the same bound; +- `idle` when `idle_ms` is above 0 and `T` − (the last interaction, or `T0` where there is + none) ≥ `idle_ms` × 1000; +- `autoplay_unattended` when `start` is 2, `autoplay_grace_ms` is above 0, there is no + interaction observation, and `T` − `T0` ≥ `autoplay_grace_ms` × 1000; +- `user` and `ended` always. + +### 6.3 The outcome + +- **refuse**: the reason and the record's index (§6.1). +- **accept**: the number of records and of segment receipts; whether the log is open or + terminated, with the reason and `media` of its terminate record; the **totals** and the + **unmetered** counts, by role and class, recomputed from the segment receipts; the roles + **absent** from every segment receipt; for video, the segment receipts whose rendition was + within the declaration and those served under `none_within`; and, for video, what was + delivered while the stream was unseen: + - **unseen time**: the microseconds from the open record's `t` to the last record's during + which it was unseen: the sum, over each run of observations in which it became unseen, of + the time from the observation that made it unseen to the next observation that made it + seen, or to the last record's `t`; + - **unwatched segments**: the segment receipts at which the stream was unseen, with `t` − + unseen since ≥ `unseen_grace_ms` × 1000: made after the sink's own grace had run out, with + their totals and unmetered counts by role and class as above. + +A sink that terminates at its grace has no unwatched segments, and one that never terminates +has every segment it received past its grace among them. The figure is the log's own +account: it rests on the sink's observations (§9), and its totals are per class, as every +total here is. + +## 7. Reasons + +The codes this extension adds, each with where it is raised: + +| reason | raised at | +|---|---| +| `reason_unknown` | §3.9 step 1 | +| `display_invalid` | §3.9 step 3, §6.1 step 5 | +| `record_invalid` | §3.9 step 4 | +| `after_termination` | §4.3, §6.1 step 2 | +| `order_invalid` | §6.1 step 3 | +| `sink_changed` | §6.1 step 3 | +| `autoplay_against_setting` | §4.4, §6.1 step 4 | +| `media_order_invalid` | §6.1 steps 6.1 and 7.1 | +| `rendition_unknown` | §6.1 step 6.2 | +| `match_mismatch` | §6.1 step 6.3 | +| `rendition_above_panel` | capability-constraints §4.2, §6.1 step 6.4 | +| `reason_not_applicable` | §4.3, §6.1 step 7.2 | +| `reason_unsupported` | §4.3, §6.2, §6.1 step 7.3 | +| `totals_mismatch` | §3.7, §6.1 step 7.4 | + +A verifier of this extension also raises `truncated`, `malformed` and `unknown_version` for a +record that does not read, `bad_key` and `bad_signature` for its signature (the codes of +live-events), and `energy_invalid` for a hop entry's class (live-metering §9). + +## 8. Conformance + +[`live-conformance/termination/`](../live-conformance/termination/) holds the corpus, in one +file, `vectors.json`, written from fixed seeds by `wai_stream_termination_vectors gen`. It +has **matches**, each a display declaration, a ladder and a rendition with the match and the +admissible set capability-constraints §4.2 gives them: every exceedance alone and together, +both orientations, a panel smaller than every rendition, a tie for the least, and a +high-dynamic-range panel with PQ and HLG renditions. And it has **logs**, each a list of +records with the outcome of §6.3: + +- **Accepted**: a video stream terminated as hidden after its grace, with a segment received + past the grace counted unwatched, its hops metered under two classes and one unmetered; + terminated as occluded and as display off; as idle, after interactions; as autoplayed with + no interaction, its setting on and the platform's answer `allowed-muted`; stopped by the + viewer; an audio stream that goes on while its document is hidden and ends; a log still + open; a panel smaller than every rendition, served the least; a display record that changes + the declaration mid-stream, and the sink moving to a lower rendition with it; and a + high-dynamic-range panel served a PQ rendition. +- **Refused**: each reason of §7 at least once, each log a control of one defect, among them a + segment receipt after a termination as hidden (`after_termination`), a termination as hidden + before the grace and one while visible (`reason_unsupported`), a rendition above the panel + while one within it was offered (`rendition_above_panel`), and a total summed across two + classes (`totals_mismatch`); and the codes of reading and of the signature. + +A conforming verifier gives every match its match and every log its outcome, reason, index +and facts. `wai_stream_termination_vectors verify` and the test `tests/stream_termination.rs` +check that the file regenerates byte for byte, that `wai-rs` gives every case its stated +outcome, that every reason of §7 refuses a log, and that §7 lists exactly the reference's +codes. `live-conformance/stream_termination_verify.py` is the second implementation, written +from this text and capability-constraints §4: it shares no code with `wai-rs`, verifies +signatures with WAI's strict rule written from RFC 8032 (`claim-conformance/_ed25519.py`), +gives every case the same outcome, and requires every mutated copy of the corpus to fail. The +page runs the corpus through its wasm build, and its own records are verified on the host, in +`joule-moq-web`'s end-to-end scenario `termination`: a page whose tab goes to the background +ends the video it receives with a terminate record, keeps the audio, and opens a new log when +it is in front again. + +## 9. Security and privacy considerations + +- **A log is the sink's statement.** Its signature and its chain make it attributable and + ordered, and keep a record from being removed, reordered or added after the termination + without the key. They do not make an observation true: a sink can state that a document was + hidden when it was not. A verifier checks that the reason agrees with the observations the + log holds, which binds the sink's statements to one another, not to the screen. +- **What a log reveals.** When a viewer's tab was in the background, when the viewer + interacted (at most once a second, nothing of the input), the panel's size and depth, the + autoplay setting, and the stream. A service that collects logs learns that much about each + playback session it collects from. The session key is fresh and derived from nothing that + identifies the viewer (§4.1), so logs of two sessions do not link through it; a sink that + makes a key per stream keeps two streams of one session apart as well. Where an operator + needs to record a legal basis for collecting logs, jwp-receipts §2.4.6's `lawful-basis` + claim is the place. +- **A display declaration is a fingerprint surface.** Its members are among what a page + already exposes (CSSOM View, Media Queries), and its refresh rate is measured, not read. + Physical size is not available to a page and is 0 there. +- **Energy figures.** A per-segment figure measured at the sink is a side channel on what the + sink did, as browser-sink §13 says of a page's figures; a page receives one only from a meter + its host attached. +- **Not claimed.** A termination saves the energy of the segments it stopped, which this + record does not state: what was not sent was not measured. The unwatched totals are what + was delivered and not seen, by the sink's account, per class. diff --git a/wai/live-conformance/termination/vectors.json b/wai/live-conformance/termination/vectors.json new file mode 100644 index 000000000..3c78f9ef6 --- /dev/null +++ b/wai/live-conformance/termination/vectors.json @@ -0,0 +1,1844 @@ +{ + "corpus": "wai.live.termination", + "spec": "extensions/stream-termination.md, extensions/capability-constraints.md §4", + "generator": "wai_stream_termination_vectors gen", + "note": "Synthetic panels, ladders, timings and joule figures, chosen to exercise the rules; they claim nothing about any device or deployment. Keys are SHA-256 of a label and an index (see the generator); times are microseconds since the Unix epoch.", + "sink_keys": [ + "a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9", + "3949c7e115065e1897874a43e87831d6cf36321a3b81fe18e65e9c2ffe5299ab", + "a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc" + ], + "matches": [ + { + "name": "within", + "about": "1080p30 8-bit SDR on a 1080p60 8-bit panel", + "declaration": { + "long": 1920, + "short": 1080, + "refresh_milli": 60000, + "bit_depth": 8, + "hdr": false, + "long_mm": 1210, + "short_mm": 680 + }, + "ladder": [ + { + "width": 640, + "height": 360, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1920, + "height": 1080, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 3840, + "height": 2160, + "frame_rate_milli": 60000, + "bit_depth": 10, + "transfer": 16 + } + ], + "rendition": 2, + "exceedances": 0, + "match": 0, + "admissible": [ + 0, + 1, + 2 + ], + "least": 0 + }, + { + "name": "resolution", + "about": "1080p on a 720p panel", + "declaration": { + "long": 1280, + "short": 720, + "refresh_milli": 60000, + "bit_depth": 8, + "hdr": false, + "long_mm": 0, + "short_mm": 0 + }, + "ladder": [ + { + "width": 640, + "height": 360, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1920, + "height": 1080, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 3840, + "height": 2160, + "frame_rate_milli": 60000, + "bit_depth": 10, + "transfer": 16 + } + ], + "rendition": 2, + "exceedances": 1, + "match": 1, + "admissible": [ + 0, + 1 + ], + "least": 0 + }, + { + "name": "portrait_panel", + "about": "1920 × 1080 on a panel declared as 1080 × 1920: sides without orientation", + "declaration": { + "long": 1920, + "short": 1080, + "refresh_milli": 60000, + "bit_depth": 8, + "hdr": false, + "long_mm": 0, + "short_mm": 0 + }, + "ladder": [ + { + "width": 640, + "height": 360, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1920, + "height": 1080, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 3840, + "height": 2160, + "frame_rate_milli": 60000, + "bit_depth": 10, + "transfer": 16 + } + ], + "rendition": 2, + "exceedances": 0, + "match": 0, + "admissible": [ + 0, + 1, + 2 + ], + "least": 0 + }, + { + "name": "portrait_rendition", + "about": "a 1080 × 1920 rendition on a 1920 × 1080 panel", + "declaration": { + "long": 1920, + "short": 1080, + "refresh_milli": 60000, + "bit_depth": 8, + "hdr": false, + "long_mm": 1210, + "short_mm": 680 + }, + "ladder": [ + { + "width": 1080, + "height": 1920, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + } + ], + "rendition": 0, + "exceedances": 0, + "match": 0, + "admissible": [ + 0 + ], + "least": 0 + }, + { + "name": "refresh", + "about": "60 pictures a second on a 30 Hz panel", + "declaration": { + "long": 1920, + "short": 1080, + "refresh_milli": 30000, + "bit_depth": 8, + "hdr": false, + "long_mm": 0, + "short_mm": 0 + }, + "ladder": [ + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1280, + "height": 720, + "frame_rate_milli": 60000, + "bit_depth": 8, + "transfer": 1 + } + ], + "rendition": 1, + "exceedances": 2, + "match": 2, + "admissible": [ + 0 + ], + "least": 0 + }, + { + "name": "refresh_measured", + "about": "59.94 pictures a second on a panel measured at 60 Hz", + "declaration": { + "long": 1920, + "short": 1080, + "refresh_milli": 60000, + "bit_depth": 8, + "hdr": false, + "long_mm": 1210, + "short_mm": 680 + }, + "ladder": [ + { + "width": 1920, + "height": 1080, + "frame_rate_milli": 59940, + "bit_depth": 8, + "transfer": 1 + } + ], + "rendition": 0, + "exceedances": 0, + "match": 0, + "admissible": [ + 0 + ], + "least": 0 + }, + { + "name": "bit_depth", + "about": "a 10-bit SDR rendition on an 8-bit panel", + "declaration": { + "long": 1920, + "short": 1080, + "refresh_milli": 60000, + "bit_depth": 8, + "hdr": false, + "long_mm": 1210, + "short_mm": 680 + }, + "ladder": [ + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 10, + "transfer": 1 + } + ], + "rendition": 1, + "exceedances": 4, + "match": 4, + "admissible": [ + 0 + ], + "least": 0 + }, + { + "name": "hlg_on_sdr", + "about": "a 10-bit HLG rendition on a 10-bit SDR panel", + "declaration": { + "long": 1920, + "short": 1080, + "refresh_milli": 60000, + "bit_depth": 10, + "hdr": false, + "long_mm": 0, + "short_mm": 0 + }, + "ladder": [ + { + "width": 1920, + "height": 1080, + "frame_rate_milli": 30000, + "bit_depth": 10, + "transfer": 1 + }, + { + "width": 1920, + "height": 1080, + "frame_rate_milli": 30000, + "bit_depth": 10, + "transfer": 18 + } + ], + "rendition": 1, + "exceedances": 8, + "match": 8, + "admissible": [ + 0 + ], + "least": 0 + }, + { + "name": "pq_on_hdr", + "about": "2160p60 10-bit PQ on a 2160p120 10-bit HDR panel", + "declaration": { + "long": 3840, + "short": 2160, + "refresh_milli": 120000, + "bit_depth": 10, + "hdr": true, + "long_mm": 1440, + "short_mm": 810 + }, + "ladder": [ + { + "width": 640, + "height": 360, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1920, + "height": 1080, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 3840, + "height": 2160, + "frame_rate_milli": 60000, + "bit_depth": 10, + "transfer": 16 + } + ], + "rendition": 3, + "exceedances": 0, + "match": 0, + "admissible": [ + 0, + 1, + 2, + 3 + ], + "least": 0 + }, + { + "name": "every_exceedance", + "about": "2160p60 10-bit PQ on a 720p30 8-bit SDR panel", + "declaration": { + "long": 1280, + "short": 720, + "refresh_milli": 30000, + "bit_depth": 8, + "hdr": false, + "long_mm": 0, + "short_mm": 0 + }, + "ladder": [ + { + "width": 640, + "height": 360, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1920, + "height": 1080, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 3840, + "height": 2160, + "frame_rate_milli": 60000, + "bit_depth": 10, + "transfer": 16 + } + ], + "rendition": 3, + "exceedances": 15, + "match": 15, + "admissible": [ + 0, + 1 + ], + "least": 0 + }, + { + "name": "none_within_least", + "about": "a 640 × 360 panel offered 480p and 720p: the least, 480p, alone admissible", + "declaration": { + "long": 640, + "short": 360, + "refresh_milli": 60000, + "bit_depth": 8, + "hdr": false, + "long_mm": 0, + "short_mm": 0 + }, + "ladder": [ + { + "width": 854, + "height": 480, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + } + ], + "rendition": 0, + "exceedances": 1, + "match": 17, + "admissible": [ + 0 + ], + "least": 0 + }, + { + "name": "none_within_other", + "about": "the same panel, the 720p rendition", + "declaration": { + "long": 640, + "short": 360, + "refresh_milli": 60000, + "bit_depth": 8, + "hdr": false, + "long_mm": 0, + "short_mm": 0 + }, + "ladder": [ + { + "width": 854, + "height": 480, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + } + ], + "rendition": 1, + "exceedances": 1, + "match": 17, + "admissible": [ + 0 + ], + "least": 0 + }, + { + "name": "least_by_rate", + "about": "none within; two of one size, the least the one of lower frame rate", + "declaration": { + "long": 640, + "short": 360, + "refresh_milli": 25000, + "bit_depth": 8, + "hdr": false, + "long_mm": 0, + "short_mm": 0 + }, + "ladder": [ + { + "width": 1280, + "height": 720, + "frame_rate_milli": 60000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + } + ], + "rendition": 1, + "exceedances": 3, + "match": 19, + "admissible": [ + 1 + ], + "least": 1 + }, + { + "name": "least_tie", + "about": "none within; two identical renditions, the least the one listed first", + "declaration": { + "long": 640, + "short": 360, + "refresh_milli": 60000, + "bit_depth": 8, + "hdr": false, + "long_mm": 0, + "short_mm": 0 + }, + "ladder": [ + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + }, + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 8, + "transfer": 1 + } + ], + "rendition": 1, + "exceedances": 1, + "match": 17, + "admissible": [ + 0 + ], + "least": 0 + }, + { + "name": "least_by_range", + "about": "none within; one size, rate and depth, the least the SDR one", + "declaration": { + "long": 640, + "short": 360, + "refresh_milli": 60000, + "bit_depth": 10, + "hdr": false, + "long_mm": 0, + "short_mm": 0 + }, + "ladder": [ + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 10, + "transfer": 16 + }, + { + "width": 1280, + "height": 720, + "frame_rate_milli": 30000, + "bit_depth": 10, + "transfer": 1 + } + ], + "rendition": 0, + "exceedances": 9, + "match": 25, + "admissible": [ + 1 + ], + "least": 1 + } + ], + "logs": [ + { + "name": "hidden", + "about": "a watched video stream whose tab goes to the background at 5 s: a segment within the 10 s grace, one past it (unwatched), and the termination as hidden at 15.8 s; its origin unmetered, its decode on a counter, its display a stated estimate, its relay absent", + "records": 7, + "outcome": "accept", + "segments": 4, + "media": "video", + "terminated": "hidden", + "media_end": 7000000, + "totals": [ + { + "role": "sink_decode", + "class": "OnChipCounter", + "figures": 4, + "joules": 726000 + }, + { + "role": "display", + "class": "Estimator", + "figures": 4, + "joules": 5600000 + } + ], + "unmetered": [ + { + "role": "origin", + "count": 4 + } + ], + "absent": [ + "relay" + ], + "panel": { + "within": 4, + "none_within": 0 + }, + "unseen_us": 10800000, + "unwatched": { + "segments": 1, + "totals": [ + { + "role": "sink_decode", + "class": "OnChipCounter", + "figures": 1, + "joules": 183000 + }, + { + "role": "display", + "class": "Estimator", + "figures": 1, + "joules": 1400000 + } + ], + "unmetered": [ + { + "role": "origin", + "count": 1 + } + ] + } + }, + { + "name": "occluded", + "about": "the presenting element scrolled out of the viewport at 3 s; terminated as occluded at 8 s, its 5 s grace run out", + "records": 5, + "outcome": "accept", + "segments": 2, + "media": "video", + "terminated": "occluded", + "media_end": 3000000, + "totals": [ + { + "role": "sink_decode", + "class": "OnChipCounter", + "figures": 2, + "joules": 361000 + }, + { + "role": "display", + "class": "Estimator", + "figures": 2, + "joules": 2800000 + } + ], + "unmetered": [ + { + "role": "origin", + "count": 2 + } + ], + "absent": [ + "relay" + ], + "panel": { + "within": 2, + "none_within": 0 + }, + "unseen_us": 5000000, + "unwatched": { + "segments": 0, + "totals": [], + "unmetered": [] + } + }, + { + "name": "display_off", + "about": "the display sleeps at 3 s and the document goes hidden with it; terminated as display off at 5 s, after its 2 s grace", + "records": 5, + "outcome": "accept", + "segments": 1, + "media": "video", + "terminated": "display_off", + "media_end": 1000000, + "totals": [ + { + "role": "sink_decode", + "class": "OnChipCounter", + "figures": 1, + "joules": 180000 + }, + { + "role": "display", + "class": "Estimator", + "figures": 1, + "joules": 1400000 + } + ], + "unmetered": [ + { + "role": "origin", + "count": 1 + } + ], + "absent": [ + "relay" + ], + "panel": { + "within": 1, + "none_within": 0 + }, + "unseen_us": 2000000, + "unwatched": { + "segments": 0, + "totals": [], + "unmetered": [] + } + }, + { + "name": "idle", + "about": "interactions at 1 s and 4 s, then none: terminated as idle at 34 s, its 30 s timeout run out; the segments are 10 s spans of a MoQ track", + "records": 7, + "outcome": "accept", + "segments": 3, + "media": "video", + "terminated": "idle", + "media_end": 30000000, + "totals": [ + { + "role": "sink_decode", + "class": "OnChipCounter", + "figures": 3, + "joules": 543000 + }, + { + "role": "display", + "class": "Estimator", + "figures": 3, + "joules": 4200000 + } + ], + "unmetered": [ + { + "role": "origin", + "count": 3 + } + ], + "absent": [ + "relay" + ], + "panel": { + "within": 3, + "none_within": 0 + }, + "unseen_us": 0, + "unwatched": { + "segments": 0, + "totals": [], + "unmetered": [] + } + }, + { + "name": "autoplay_unattended", + "about": "started by autoplay, the viewer's setting on and the platform's answer allowed-muted; no interaction in 20 s, terminated as autoplayed with no interaction", + "records": 12, + "outcome": "accept", + "segments": 10, + "media": "video", + "terminated": "autoplay_unattended", + "media_end": 20000000, + "totals": [ + { + "role": "sink_decode", + "class": "OnChipCounter", + "figures": 10, + "joules": 1845000 + }, + { + "role": "display", + "class": "Estimator", + "figures": 10, + "joules": 14000000 + } + ], + "unmetered": [ + { + "role": "origin", + "count": 10 + } + ], + "absent": [ + "relay" + ], + "panel": { + "within": 10, + "none_within": 0 + }, + "unseen_us": 0, + "unwatched": { + "segments": 0, + "totals": [], + "unmetered": [] + } + }, + { + "name": "user_stop", + "about": "stopped by the viewer: the sink decode's figures under two classes, a total for each and none across them; the origin once modelled and twice unmetered; relay and display absent", + "records": 5, + "outcome": "accept", + "segments": 3, + "media": "video", + "terminated": "user", + "media_end": 5500000, + "totals": [ + { + "role": "origin", + "class": "ModelBased", + "figures": 1, + "joules": 900000 + }, + { + "role": "sink_decode", + "class": "OnChipCounter", + "figures": 2, + "joules": 390000 + }, + { + "role": "sink_decode", + "class": "CalibratedInstrument", + "figures": 1, + "joules": 210000 + } + ], + "unmetered": [ + { + "role": "origin", + "count": 2 + } + ], + "absent": [ + "relay", + "display" + ], + "panel": { + "within": 3, + "none_within": 0 + }, + "unseen_us": 0, + "unwatched": { + "segments": 0, + "totals": [], + "unmetered": [] + } + }, + { + "name": "audio_hidden", + "about": "an audio stream goes on while its document is hidden, which hidden does not end, and ends with the presentation", + "records": 9, + "outcome": "accept", + "segments": 6, + "media": "audio", + "terminated": "ended", + "media_end": 12000000, + "totals": [], + "unmetered": [ + { + "role": "origin", + "count": 6 + }, + { + "role": "sink_decode", + "count": 6 + } + ], + "absent": [ + "relay", + "display" + ], + "panel": null, + "unseen_us": null, + "unwatched": null + }, + { + "name": "open", + "about": "a log with no terminate record: accepted as open", + "records": 3, + "outcome": "accept", + "segments": 2, + "media": "video", + "terminated": null, + "media_end": null, + "totals": [ + { + "role": "sink_decode", + "class": "OnChipCounter", + "figures": 2, + "joules": 361000 + }, + { + "role": "display", + "class": "Estimator", + "figures": 2, + "joules": 2800000 + } + ], + "unmetered": [ + { + "role": "origin", + "count": 2 + } + ], + "absent": [ + "relay" + ], + "panel": { + "within": 2, + "none_within": 0 + }, + "unseen_us": 0, + "unwatched": { + "segments": 0, + "totals": [], + "unmetered": [] + } + }, + { + "name": "none_within", + "about": "a 640 × 360 panel declared in portrait, and a ladder whose least rendition is 854 × 480: served the least, its match recording the resolution and none within", + "records": 4, + "outcome": "accept", + "segments": 2, + "media": "video", + "terminated": "ended", + "media_end": 4000000, + "totals": [ + { + "role": "sink_decode", + "class": "OnChipCounter", + "figures": 2, + "joules": 361000 + }, + { + "role": "display", + "class": "Estimator", + "figures": 2, + "joules": 2800000 + } + ], + "unmetered": [ + { + "role": "origin", + "count": 2 + } + ], + "absent": [ + "relay" + ], + "panel": { + "within": 0, + "none_within": 2 + }, + "unseen_us": 0, + "unwatched": { + "segments": 0, + "totals": [], + "unmetered": [] + } + }, + { + "name": "display_change", + "about": "the window moves to a 720p panel, declared in portrait, at 3 s: the sink moves to the 720p rendition, within the new declaration", + "records": 5, + "outcome": "accept", + "segments": 2, + "media": "video", + "terminated": "ended", + "media_end": 4000000, + "totals": [ + { + "role": "sink_decode", + "class": "OnChipCounter", + "figures": 2, + "joules": 361000 + }, + { + "role": "display", + "class": "Estimator", + "figures": 2, + "joules": 2800000 + } + ], + "unmetered": [ + { + "role": "origin", + "count": 2 + } + ], + "absent": [ + "relay" + ], + "panel": { + "within": 2, + "none_within": 0 + }, + "unseen_us": 0, + "unwatched": { + "segments": 0, + "totals": [], + "unmetered": [] + } + }, + { + "name": "hdr", + "about": "a 2160p120 10-bit high-dynamic-range panel served the 2160p60 10-bit PQ rendition, within it", + "records": 4, + "outcome": "accept", + "segments": 2, + "media": "video", + "terminated": "user", + "media_end": 3000000, + "totals": [ + { + "role": "sink_decode", + "class": "OnChipCounter", + "figures": 2, + "joules": 361000 + }, + { + "role": "display", + "class": "Estimator", + "figures": 2, + "joules": 2800000 + } + ], + "unmetered": [ + { + "role": "origin", + "count": 2 + } + ], + "absent": [ + "relay" + ], + "panel": { + "within": 2, + "none_within": 0 + }, + "unseen_us": 0, + "unwatched": { + "segments": 0, + "totals": [], + "unmetered": [] + } + }, + { + "name": "segment_after_termination", + "about": "a stream terminated as hidden, then a segment receipt chained after the termination", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600c737687ecf1b6b6e92856186b8c1f73e7bcd89412ec3ac0a3fbda262aeb233d201010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a102468ffd40c97d470b759d51a213f547ad2a447a78577f2ab514c20ee64964d7ec5328636341f3cdf9a2cdf6a713c68e1e447a987e6151b4b831bd1935d128f04", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000a31c88885a3bd19ebb0513cd35ee77e938956e0b8edb9777d68abf0eb4765da580c48ad8e95d0600428cfebca42389bf701dea0ef5e89f034b66b0693b745853dd5cfea61db13a7c3c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c150000000000033a68d8502cfc0904924f87a3f32afa45f4b7ac9e5d24ae42e2b86345a3a0c9096fe0950c2a5692d76c3532e0b2b68044adce88343eb79587a8307b8074eab80c", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003015c6d00a25b9aebe8e15d20c4ff38033b53f84376c7d544954e180ef197d7d0049a9d8e95d0600bebcb35f67c70a129a06228a867588f9e2bb0734a50d96ec5044d19411bf9bf43c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c1500000000000346490a3799ebaabe408dde87db68aa1906884d255555222b5df057d5ef5c521d69ad1d7ef2faeee56629a43a5b7fb618d2595c6037d4f6c7fed638e634d0d808", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000b639876b27df06d9e78b65ccaec40b00201b0d7a91be8b99d6873e55fbb74e05408bb8d8e95d060001028d08b77a5d3b5829bb222a25233cbfcc55b6f2413719c6136178de7f56bd5fcaf6c10a7abeb041cec32fd8197f828e8db492d943cfaecf81113cb3af138eba06", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c904000000228f6865f6b06ac6fcf19ec721886a81734f611df48726466648ea9b8aa37c9280cdc7d8e95d0600476953298d55c89aa984c40680d4154987eed539087a09f6501a036ae7e5d4e03c00000020a107000000000000093d0000000000808d5b00000000000200030100000000000000000003f0c602000000000004000061a800000000001e84800004c05c15000000000003788ce6583786214379555753435762b29396f6f7ab81a800676e2a9fc6b537159c0d27f5189549cf8ab90bb270bc6e42ff20c5b530d68fc28f90f5cd2a6a5a0f", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c905000000be71c4d9adcf93b0f40866f9dba5cb1f1a6bbc3e3893e1ed773bbbbdbb8a503fe0c258d9e95d0600e5474ad30794a39e04cd648e129e34b66f8b3c2de6cbc7c480c06115b08a16643c00000020a1070000000000808d5b000000000000127a00000000000200030100000000000000000003d8ca02000000000004000061a800000000001e84800004c05c15000000000003a517be00f943ac8f7f2db3d5627b507ba9d3b0669903e17e1501f9cf800b5f0b7618b58c8ec0bcf1454b47722ddb9cf7379442fbbda33ae0bb7d96a8a78f5b0c", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9060000000dae29a975726ed063484c99785dc16e5769c836ca43f25060588aa3e8645ebbc0565dd9e95d060001c0cf6a00000000000400000002030404000000f0130b00000000000403040000000073550000000000010104000000ea84cb1985530a018d77f26913fa989729e7fba57e1cd4ddcaa8f14a4654d11e2f01bcbfa996a22d08b34e0c79c45f69437f1621d53ba8d93ac0ae52e7592503", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c907000000196a062433c9d6b2b422e7b93d3d33d399978c9b1ff3839d94a5c2e3899c5f66604777d9e95d0600b102deb11e34deb3bf23363f2c9500bcfa62df862973bbd770b3f240af8ed20e3c00000020a107000000000000127a000000000080969800000000000200030100000000000000000003c0ce02000000000004000061a800000000001e84800004c05c150000000000030b2fa1737a50628a9f6f8649885a63c3ac3900896645968d800b8ea95c6b5678d718622be93e351dea99c81d70a38497e4c1d66200aafdec5615e505eeac6c08" + ], + "outcome": "refuse", + "reason": "after_termination", + "at": 7 + }, + { + "name": "observation_after_termination", + "about": "any record after a terminate record", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003531c174cd5963e8fb73ed34d6b862b52472bcaef6c6112d8d12558ac20383ae86dcc61ef54dc506ad25dc8b566a963725fb778c179651d7a39060f0ce88fee05", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000741e248f8cd5f22ec26be6de32fef87a4a4058401729ac42a45136ddf1d4125f408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000b39ed85e7bcfe31473c9d1adb6506c52e91b2934b002872b82a6aea839a5358bc4a2bc6d908750d87adfd69e3d398917ac6a1925da052539de2a422849666b02", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c904000000fd4fd71c0ecfd845f69fa2a356b7076922480f61d41f0e48032ea48d2e406e2580cdc7d8e95d060004016069573f96b3dc902f879d96254be69909583e31b478acf385b41953993eabed34e8045160e48f093c1fb635b12705e147e3eebd7c0b4c8c81e26a1c78e8f609" + ], + "outcome": "refuse", + "reason": "after_termination", + "at": 4 + }, + { + "name": "hidden_before_grace", + "about": "terminated as hidden 1 µs before its 10 s grace ran out", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600214c5e8c906df2ee551a1b35fad59208112cc9726db73496d5fe22270ef7fff901010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10597ca0106c93bff8a8875a39d617f36af780834a3661a370b7f707bb87539d079fdecb09014b0587b0d249332a28420ae39ab8148daf45ade65cc600ecc8f502", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000cf908d2d29d75f14f84daefb07a75db10f0a38b8d75190ab14fd058b571c36cd80c48ad8e95d06001cac082ba9aef5d42469e9767a484a985cd18467b22cf3361e8bfc59ad593f323c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c150000000000039ad30a7ac3765303d83b4c974c5be8aadd07ae5108b4903dacd58996d12ae22618570634d767b6159067882dd05e6b40b2757f1411edef0cb410f7fe524ce600", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90200000032d160e360a83381e8e56765f2801e07295728c6797825ff5d6340e36a0d9148408bb8d8e95d06000102066afd5fe3fb684ed5784a59e0e10334a2fae1664a7266feb63fd5f17ce54dcb044327da3ed89685ce7d1ee48c7f01a5d1a057b8bfb12f539d5c772144524705", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000cf83e35bd9e30a87f50ffedf9455c6265fc4464c023ff3026d03648725fd765080cdc7d8e95d0600ff2c96e4de5cc0b9ac813f13893b27f23c9c705d944e642b96e09936e31703f23c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c150000000000030a69c43e5790ed0cb3952f220714729c22f37d47a5a681dd0bce28f726afa07e35cf7344a2726a5bf2c852414f6a6e9c3cc83c174a506ea01b85ab393d774504", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c904000000bff330a8d64b6f6485ff066f922ba90d7cae733c3cd33148e4f3daf85565a3cdbf2151d9e95d060001c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000a6386fc5563fa97e31745756b63b3783fd275ce1ffd0fbd7794c1d5d5112c782c16993c24a7adf3545f597cddd2ea3d023bc8756b9bce092e8e48c8de6a05401" + ], + "outcome": "refuse", + "reason": "reason_unsupported", + "at": 4 + }, + { + "name": "hidden_while_visible", + "about": "hidden at 3 s, visible again at 5 s, and terminated as hidden at 20 s", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060084a79216ed11bf62b6ecd44085af06dc72cd1301104a6fa258733710efaa602e01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a100316bb859fb9264d9a0a643cf4e2658b3267a21eaff45dae841f987ff7c1c340f3f8c95fe85c6d1bc5962602dceea437e0317365f69c9719cb93e47d97315d09", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000038917799e1e9d486d67e20b8ea3c61b181b72f29028f7be8c624b18dbcbc4ccc0069ad8e95d06000102b8eab15aa4fa698905d5d53246a95f8ec581454d09a45a3c3556476024df1f80f7fd5268b2e0530dfbafa8edf806a75c9b8cd5fd4d7bbbf3265dc43e02a36405", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90200000043823143102357f954afcb008e15d946cfa33d3a807773a65a7f9a12f79dba51408bb8d8e95d06000101d487cd12928f7244ca00ac61b524e30db96d3bd1a6a34cf96412c90f452877157a14faa4e07e99e34deefab2b83653e577bbff5cec65a73ed83d146273174a0c", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9030000006f68fad38ba6b13d52d8f024d104e0fa29f345eca47c75c8a5015f4e659a26f680cdc7d8e95d060067ab8818be3c90db975b3309b505bb27db85fce89d74ef4524966d7c30096d373c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c150000000000038b555b549fe1a2387cd8f1e03bbc223a7393f159bf2f79479a766bc9845dafa236c0d62f21faf074ed240f321d0bc6515e23c51c92b2130664c52b87fbb4660c", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90400000089257e0958060917f603394cc76997656053164c6fe4cfb010d67392e664528c006d9dd9e95d06000140420f0000000000010000000203040100000020bf020000000000040301000000c05c150000000000010101000000a472f11ce333a165ec113497b58d769f07283dbc05881abb0abe32cfac1774383b0e3bbb92042b743ddff97d9a72659d9280eae10d5628c5e632fdd65c379c06" + ], + "outcome": "refuse", + "reason": "reason_unsupported", + "at": 4 + }, + { + "name": "occluded_never", + "about": "hidden, but terminated as occluded, which no observation states", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06001663e056a4146992576298cd6d13d3009a1992cfee7871b1318d638f6ea36c5701010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a100e5af3002243f404a0d90fe73eba1c2978b25a3544e8f161ddc5fdaa617a41f588b3a44be41b2ee4c9ee60e7e6df56c82369590edcc62ef2349cb225803e3007", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9010000003c2395f4125acc7818d1227159f390b5961ea1a63b96f3e342d15390086302aec0069ad8e95d06000102cbd0cfe138bdd38d4269268900622afc07b02c6814c6740c62456bd7c8e235068f0471d2544c7c6aa3241568ae504cd14121dca725571418de3a3b17c447ce0d", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90200000078ba3226a66541d318d7115287aac29fd8759ef66b3f4e632bfc56fe39c227c980cdc7d8e95d0600e721c158c709b9b33e27b66deafbdd1146f65aebcbc0cb3350c36c4623f0aa393c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c150000000000038babfa52da73f5b13a3f69c991688b3a8ff5149509ee4bf7e50f29dcf0f519fbfc57ad3f159a97dc40ee9df0f91ca91e81e8ea466feb1ab7adad8d815d6c9301", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9030000006ea4f096c3b87ac0d5278b96c426b89d9da93720398e467d77e759d3136adaf2006d9dd9e95d06000240420f0000000000010000000203040100000020bf020000000000040301000000c05c150000000000010101000000a1168fc9134275351c2723d9cb496299f8889ed0c76a7070e10898a245896ee7e18d225fbd28f557672a1dcf20422dd10d2c720399513698b365968c48a37f09" + ], + "outcome": "refuse", + "reason": "reason_unsupported", + "at": 3 + }, + { + "name": "display_off_never", + "about": "terminated as display off with no observation of the display", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600b882ebd934fa36b4803a1c0ba448458682f00d31dbec497d1681a259438caf9601010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e22f99ec98f2270e37dd643f1c9639f069303039119524e5a9d8f918202db6d9a007e38e4d382879b10d50ec0d98e868deec0fb6059a7e5bd1a207643d444409", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90100000063271bb660ad59deaeb349adf93750a20e719cf60607d739bb631e384c78fcb380c48ad8e95d06003bae41688dcbd96c3addf342aadb1514761da5a093fba3871024c61de65482103c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c150000000000038c660abb45047fac535840f8a356d790d2b9be4ee9ea0a0d42b9c0ceb8f36c249bb0eaa8b1148fd01e868624069e2de77173ccb4cfdc162458e20caf1420bd0d", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000498cbebc8537be60089673e6acc0744222e21b9232ecfd64e7ea61193da471c7800336dae95d06000340420f0000000000010000000203040100000020bf020000000000040301000000c05c150000000000010101000000538a32591e2305ec0428e6bf8674ceb46dbb6a9117dcf6b332a394dd02b971c4e3e83d211d48d85bb2d3021b1cd63edfecdc92610a70b19ba1fa69555b733606" + ], + "outcome": "refuse", + "reason": "reason_unsupported", + "at": 2 + }, + { + "name": "idle_no_policy", + "about": "terminated as idle under an idle timeout of 0, never", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06000f2504b64786491f04cd09a39e7304ff90ac85a7e979c535ed424e0762eb3d4201010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a1028170c0cc1b2ba07fb9c0bedd2c2249f3c318fe201a91614cd1fb44d9951ffe1bf31a840b1b4ff9e6e5e5f66bfd41b34a266b21ec58dff5845a321186066dc09", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9010000001f67ca6f529c5bad760478a6ba7c1d720da4764a779a222ef6d6bafad5341dc580c48ad8e95d060088ff525be5c8b4dfc55ef55a3eaadc9b5d3bba7abdbca9b4a9367a568f3b424e3c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c1500000000000389aee1b39770a1ddc666da26cb2b0ce00bc9e2399891781adcc421afc867bdc37e788e4e088dfaa9bd8d0e52c6d3ef97d6682b62d882f6b2db318259f383450e", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000e400a0d10a653575121e906d657940db4bc52149cfb4936b992feba5cef408ae00862ffce95d06000440420f0000000000010000000203040100000020bf020000000000040301000000c05c1500000000000101010000004388f7db342f64f493a1638783bf34ea2a89be8155e2c71bf53f88226017d016e02b6e98a7d49f11a2da71dd6bd34ac30303082943b848e74ef2c01cb6f1e30e" + ], + "outcome": "refuse", + "reason": "reason_unsupported", + "at": 2 + }, + { + "name": "idle_too_soon", + "about": "terminated as idle 29 s after the last interaction, under a 30 s timeout", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600f685341048543544ac88db766655a6028a64f6db1581b50d325cec905c6ac96d01010101102700003075000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a100ab8342ac1cf842abe4fc813add5401bfaa19378132db53b3447054fc826961e0ba5c8e5eed6e4e507f5b39ef1a3cac4a26ce65fb3cc8dd9a64c33b7fe8e0d09", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9010000008cc61a0363f25dc0afadb405dea497ec8e4cf996782f9b88dfb557677f2b57f3408bb8d8e95d06000401fba1cad724c415d1fed6b09a38288304a685a67f2effbd317c52105280d835edcf32aab7d5c8db7392980c1c4b52b981ac0c0af8af3150d55c133a4d525b4306", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90200000091dc9d7946bf338702fa8e13958c297e7ddad2088a4bf43d8bef9c805e18687080d604d9e95d0600704ace29134b3ec8f11b8217081080d99487b59895a6afd3c759b1071e3114fe2c010000a02526000000000000000000000000008096980000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003a95007ddb4ac20222b10e70ea4a43abc7e46813a0ce128126284f34b0fb2149661b52ff147d2b24001f3f0a985a3a047f0be8efc6890d83f11b92750c8c7a806", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000584db2b61cdfa5ecc1c93372d0a77b2084d2973a36909a7ee681e3ce3b3d49a6800c73dae95d060004404b4c0000000000010000000203040100000020bf020000000000040301000000c05c1500000000000101010000007241a0b96c64e8bca8b8a4ad32786922c90b83f41c479bae5dcc86ea20f1022942906d4859ca587139f805e97586fb5a0b5a1eef3d8db27ddcb29f1fa0276608" + ], + "outcome": "refuse", + "reason": "reason_unsupported", + "at": 3 + }, + { + "name": "autoplay_after_interaction", + "about": "started by autoplay, then an interaction, and terminated as autoplayed with no interaction", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600da14c04132d614c9058875843ed64599afff63fa3ac103040558fd7e6f805f00010201011027000000000000204e0000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a103fc7e4af74c0dae950fa9ffe53f4fdf38657fb016287dc3d7c1f7c21a15bbb389e77e5499b82c66e77eacfb59c3efa50ed4fbc23bf303b8ede559200c853a802", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000a7e81f039f5304f89f388ab83df3cb279278b9c35934df9ef5fd4524fb32f59dc0069ad8e95d06000401c10aa20c79bd106b7d3ad0c6a8f1fea15846403dbf6e3d579b64cd0a8cc3090f9105f57eb55818004496970fad70cdc9779d0bc0b62a02d1b5e0c3bed142a309", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000005eb847c7b6c68bf4acceecf0f26f55e8847a58cf70755edb52c5657032768baf0049a9d8e95d0600cd6d1ca0a39b41961b2386fbd9aacd5fd9426ab1deb1b4b315ce6038217331b93c00000020a1070000000000000000000000000080841e0000000000010003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003e939fbcf2b9e25a2f8c0e04b83fcfc25962886cf5e47173e354927261b1c6bcee59a31856095d9e7db295c7b065817d28734b95033b8b93c29af807b331d1306", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000ee5ea72bf540e0004882873e6b75b5fbda405de7d9e99f03ac2ebad93496091140b8e9d9e95d06000540420f0000000000010000000203040100000020bf020000000000040301000000c05c150000000000010101000000142fceeb0747a5d3160498a129adb6b1e6305ff56521c7397ca3db9cf2d9b60ae5697673fbe037dbd81140ca771140688441a9d47ab2381ddb9b4aa98a2b9a00" + ], + "outcome": "refuse", + "reason": "reason_unsupported", + "at": 3 + }, + { + "name": "autoplay_started_by_user", + "about": "started by a user activation, and terminated as autoplayed with no interaction", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600a10f87695ff6bbfad846d5069b8923e56f0bebd1a0a85ccda079188f896a2cd0010101011027000000000000204e0000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a101d19b4eac634ad5c92effe358c06ef14fe34a5dc1360a6e7eb8e666acf6dade34b1d3bda7c7efc40813a4024e536626f1546041938ef4a50942f786501177202", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90100000025e78343346c0abda2bbe3e99e58c1dce1bb2ea401844509379327d571a4aadc0049a9d8e95d0600528fc6bf2f498e950b25f744dbd9c1c1a6c854983e1cf45fc16558a69f96be9c3c00000020a1070000000000000000000000000080841e0000000000010003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003b478278c4fb034b8bd0355ad7279ffba3e5e6c2d726711f0b2e2784c2930131088f0c0d4b3543581291b50117f302cdc7996fb3af0ce86c24fab9ceaa1f7200a", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000f7b3ac9e4a25d8053a3a32b48f1dee68f0e03e1e0d45c2ec5a634bf17bbe3c0740b8e9d9e95d06000540420f0000000000010000000203040100000020bf020000000000040301000000c05c1500000000000101010000003eec358bcf82ba32bbf9a58d7697ecd8307a0ebd7f6e1faa9ce069181f091cecaba85f937ed933554b88703724405c2922c2ad2f41c2141e2eda78f5976d3904" + ], + "outcome": "refuse", + "reason": "reason_unsupported", + "at": 2 + }, + { + "name": "audio_terminated_hidden", + "about": "an audio stream terminated as hidden", + "records": [ + "57534c310101a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc00000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600a0a3d2ba8b7f15e7ba08a930dc37fb799226683303af346f8654e0b33acbd81f020101011027000000000000000000000000000000000000000000000000000000000000000000307e3d7404872f32954ce1846a6d56c5f0b3076346400e41b359ef5827b6255d6447f9a63785d51e423618d085c1f47a46cabf25134416da2b5099d1e435340b", + "57534c310103a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc010000006db79947700f5b4f36e313cacae0ab578989da0716d6aeee8978ee9af04d0d9640827bd8e95d060001024a0dd8d1a9251417ea21066c40c1211565637b70422f9e445c88734522b358c8ee63bdccfee5382a19df8fbbf9e93cd74803172b3f80d14c5982dbc059fa0e05", + "57534c310102a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc02000000aa3805987a23173e72e22780bb0fed78aafa9e35711ea0ee0e834bd243b82c6a80c48ad8e95d060044021dc5159d06eda7a6bc7c0ed05d386310780a7c0e97159c0957b3f267baa13c00000020a1070000000000000000000000000080841e0000000000ff0001030000000000000000000395e3ec9802608f327031f03ef44039abfd2389842f0b862606b04fe80ffbb94bbf96777e1878ad15e1defa12fcd734bd45fa2bfbeb0e3d04e8ba9f555e3c07", + "57534c310105a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc03000000e6c999f62b72d9cefa47daca39f45aeaacebfbcce58c24e632f4ebb91e7231ed800336dae95d06000140420f0000000000010000000001030100000019e849255a15888a6b48278bff8ad9c39486c090a6dac3794b3bf41cb6fca8a1ab70540d5f04ecf853c3d6e6cc84f2c089c9633c57b4b4508e82c2081ba70607" + ], + "outcome": "refuse", + "reason": "reason_not_applicable", + "at": 3 + }, + { + "name": "above_panel", + "about": "a 720p panel sent the 1080p rendition while the 720p one was offered; its match recorded honestly", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060033e3f81aa0c8553fb55c4cac128cf6e8a37d993df2166069447890f5378431200101010110270000000000000000000000050000d002000060ea00000800000000000000000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10b9de95d92d7a3f4f6c5e99910508c08f7a2d8c7551651b84bd8f074488f06530b33fa289f71c27d7f4dfed0d9627b4f30f95775321c3dea456999e49e41cc600", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90100000032cbdab32b7ece734252b75f397cdf39ce58ff09121a13e9218b37c57457f2e380c48ad8e95d0600f042e3062f5a6004f33f138027348becbc9cc374acde0662f1dd910c9b3645f03c00000020a1070000000000000000000000000080841e0000000000020103010000000000000000000320bf02000000000004000061a800000000001e84800004c05c150000000000033a8f6789b7cf11a4b54ba0e619ddae6652e46eb59adbcdaca21cc191ae0b5429bf67dbbe1f09d59e46172e509509ebd8a8c918f9d2ed09e7822cc02665bb4205" + ], + "outcome": "refuse", + "reason": "rendition_above_panel", + "at": 1 + }, + { + "name": "not_the_least", + "about": "no rendition within the panel, and one sent that is not the least", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06008107e2a5fa152a39d2744f16d0b40210bd4434552e7ee0d618aefb5ba9862a2601010101102700000000000000000000800200006801000060ea0000080000000000000000000256030000e001000030750000080100050000d0020000307500000801ec03ba5491b63d925432c5e182392e4dd0655f1fa5abb3e9d9cabd11265d9303cd206e46f3b2383f4948e6122ecad0ae2fd0cb0aefe56bfb1a9352ae72868b00", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000063105b80ba895a72c18ef7a7d55d4e4c60f9a72d28494aa4c9b518b8dd6c74d80c48ad8e95d0600dfa7cd75f98ca81ad478f7ef3441bf03a2b440d1294713d7ba87e8e433d6938c3c00000020a1070000000000000000000000000080841e0000000000011103010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003dc76dafb9df06ba469c81ca14055c2ff31d9d660e488bb203d0464e06e4eae40df8fe44f317ea95f5d6b47e6c1686b130e177be220886de0ed4017b416e1dc00" + ], + "outcome": "refuse", + "reason": "rendition_above_panel", + "at": 1 + }, + { + "name": "hdr_to_sdr_panel", + "about": "a PQ rendition sent to a panel that presents standard dynamic range, while SDR renditions were offered", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002f1af1139666750ff821e2fd01ccddff551c99722d08a623fa7d2914ecc291ca01010101102700000000000000000000000f00007008000060ea00000a00000000000000000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10b2678a0c2e89142240f6579cf03943314b72073166a88c16c1e0dba4fbae5f6bf739df5d42b70d76ed52547b52a494e15d137b02b1502029576f4b99e8244200", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000ffb92137a07c135a12ed99b6fcfdad2ab74c36816d4a0280ed22b738810ca25880c48ad8e95d06005c9e6f67e14a8cdf840ec54c9874973780c38b2891fa3b0d05e94316bae2e13e3c00000020a1070000000000000000000000000080841e0000000000030803010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d23506df08a253e8f98ac0a8efba86c8b9231d81905ce506d53513903d084ccb6c0cc350e3b43a3efa55d24083ff8209e7cc6f693fb51cc950f4b8d2030e7b02" + ], + "outcome": "refuse", + "reason": "rendition_above_panel", + "at": 1 + }, + { + "name": "match_understated", + "about": "the 1080p rendition on a 720p panel, its match written as within", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600a221c8d6ef815a247ac9c1d32b50c9f492ef4fd8a3f4e4388ea8ff7a9119a6500101010110270000000000000000000000050000d002000060ea00000800000000000000000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10b361f97cf59919c1f19449cebb5228766e0c697089efe3672a270d9b7c79cf227ae3ef3709200450f8a5b952d4d0d8839ce289c5f56c7857a908f72cd9b72f00", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90100000027feefba1cd6bfe0c1e60f4454c5233c7c0b27f707b1f32844fe8788fd1dd47d80c48ad8e95d060046b589c81e3a40292c0901db83660caa27d02e62947c73f4bf1b271a935f70ac3c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003e5c1c3e83e053e2b54a527c8688cc7655366f3c1f9ec9cf842c2432f80721ccd569950adc4cb14c5837db4449b092acc35985f55ec01cb09f9d016585819be03" + ], + "outcome": "refuse", + "reason": "match_mismatch", + "at": 1 + }, + { + "name": "match_after_display_change", + "about": "a segment's match not computed against the declaration in effect after a display record", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600d40f71cec4ce98c6b91dc1327ff4d9b671986038a0cad87df8a0bfd1dfcbd3ed01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a1007f8482125761e86fd5a23f73472c90766968e9e9cbe81eceb1383308e2439a71ea8a50d49bf99a27f5790c74d12d0c5cb922d2180fa7837e29b6153b8a3240c", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9010000000ec4a54c5871f10fd0d12e822a353afd7a90cdad97ce83cb87b2624dcd9a2a8f80c48ad8e95d0600e35c79ad9e640464dc0e2415fc830d381245c7e2d7596495411fa3c10628382e3c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003a8116d767b4431e60ea5064646798a4aaab284f9e2968c0e71cca05e1299ea2a026e11479f4f36a9cef5cf888bf6ccfe9abf7b143ce4f14d83d1cf916a225506", + "57534c310104a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000abc19ecb2b2b481e80829beceabef69211bc74b39e62f2f5152b8e1fb26bd495c0069ad8e95d060000050000d002000060ea000008000000000000000000220168de99d7cd7a69a9a06e785ba7545f295a04eb93491df63a2cdebfe3d9f381f975c0db7c3483ec9526d76bf72e19b20ae331e0375c1cf55832a04cb8f703", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9030000002f7252be474a40d686e2292bc307f549f54cc1862c7700f465477761da73fbfc0049a9d8e95d0600bb4c899f6878eb057a960bf808f14d3c649fb3a6a44a76d51d18b532731026e33c00000020a107000000000080841e000000000000093d0000000000010403010000000000000000000308c302000000000004000061a800000000001e84800004c05c150000000000039d8c75f5f6902013bd7d877ed05d2a7249c12d6bfbd15ece240d6b0d76922e52987330a105a06c714b0cdc40b5de1f58ffc572232d4d959e4644bc752af37b0d" + ], + "outcome": "refuse", + "reason": "match_mismatch", + "at": 3 + }, + { + "name": "rendition_out_of_ladder", + "about": "a segment of rendition 4 of a four-rendition ladder", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06003ec55b7f5025dc54ca47d8c38f6bef6f5bce0264047f9c01927d1d1afa199a6901010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a1086360b61cf2b9d5d7dff0bf7904688038fef7033ecb5fe0b825f5d1b954f1a12c9ae3d46baac3990f74a68131ac6daf9242f6727638ad4f1d2decb2a80ea840d", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000f870306151197f78bec8ba2bf132ade75dc17135f53e8b4575f23d960e8d6e0680c48ad8e95d0600cbfd2fe9bdeb9630473d5e830782d9c82aa14101d4c807d52a67b1cb4ce2946f3c00000020a1070000000000000000000000000080841e0000000000040003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c150000000000037886cc4de2ce52429dd915bac3ba8e4eb0ab98a01489d1cc962d27f76f8ba7eca5231e8505b68c462e6dbbf86bde8141e9cd9c105d7c78e1ac89802db949e600" + ], + "outcome": "refuse", + "reason": "rendition_unknown", + "at": 1 + }, + { + "name": "audio_rendition", + "about": "an audio segment naming rendition 0", + "records": [ + "57534c310101a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc00000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600bd413dfc4173537abbe449bd6b1e1e62b2b8c3bf470a5d9da98a08fcc1cfb2a402010101102700000000000000000000000000000000000000000000000000000000000000000096c32ce54aa1696708c0e56031138e7f75562d4d5848773c9f9a490a723e429d36e04c0581f89071f861f2ac68f0f697197efb7d7aafc3fcd8479b596570c101", + "57534c310102a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc01000000a6748db93d5ec3fe1ac7a922502646270f6307bf0d9b0cb71a7e0272cd42ede880c48ad8e95d0600d4dc6385115c6be98a32f85e9fb32fb5f32b5316d41e05826bbabbbf947378413c00000020a1070000000000000000000000000080841e000000000000000103000000000000000000353d9d5b555a9407b764698e88d10bf168334e016115de7ddc96184ffaad7c8334b2077c563e9334d5dc980574debc86b9d0e1ae4c60e65e6c0b7067d60b2008" + ], + "outcome": "refuse", + "reason": "rendition_unknown", + "at": 1 + }, + { + "name": "audio_match", + "about": "an audio segment with a match", + "records": [ + "57534c310101a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc00000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600edfcc61f2de569f3a1f13f9a35d24958d1331879eae9cb160c655041f8c8f33902010101102700000000000000000000000000000000000000000000000000000000000000000061a1ccc3f360e328b917e9a4c806b45d9198bbc6e04b560c3c9790dddc8f45b51d407e9f1b621a3ee5ca4bb9d670dd414ec9118f43f136813b083104801f1d0c", + "57534c310102a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc010000000cc4dc723bdffb11e07bcb3258b690a9806d51231907070f9474b71b2bf0a10080c48ad8e95d060069c27820c311564815ad21602df6e5d48cadde6c34deac8c005f7c79d1c367723c00000020a1070000000000000000000000000080841e0000000000ff010103000000000000000000f6eb97ef07c629c88df11090d50f8e815e3cc2a4b392a7a34f416ffa2cb6ec2fe1e7074f6a70cb677a0fe115d24048da6d970fd34374971fc7ca1629c82aa309" + ], + "outcome": "refuse", + "reason": "match_mismatch", + "at": 1 + }, + { + "name": "segments_overlap", + "about": "a segment starting 1 µs before the one before it ends", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600a8d81abe0e4cdf55ceddcce865df8dbbd6ed7c0334fbd03683f5ac17dfefe68c01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a1074949a7eae12ab73c509352c57b06cabcc7a4450b575dfecac7ccd716eeb66fca2e155946e98b1c55d367de938d0792133bac25bbaea4652c7d92f61e561a60d", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000a745704b5af53e4397c3b00e594f07a4d3efa4ed7cef9510153578d8395adb4680c48ad8e95d060035f6af469836d7013b12c79cc46a6202755717abd52cf7598f732ccb3558126e3c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003961c976919e139bea413e499452c7ffb9263cd1b8aeef63d834c7b40475123503cb67e1a106f17579dccf70825880657bb84626ee65ea4010add34b55730d501", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000009d29ef02aedb2ffa45a69d1fe33400b5da164c09fb7932c43b5cf0ad8a8c0ce60049a9d8e95d06001a70ad4c0ab5e02d1e53fbcec306b3a8707b410520b1face7bc3d46a68ed80093c00000020a10700000000007f841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c150000000000032bfe72ec20780e32f78af72924f72564b4f5e3dc5118552c3472c0efd82de59b1cea08e44ba047f4e7cfda04e8fa27264c74ab97b049cd9c569601595e539902" + ], + "outcome": "refuse", + "reason": "media_order_invalid", + "at": 2 + }, + { + "name": "terminate_media_outside", + "about": "a termination's media time past the last segment's end", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06006579974348b778cb6e5c6d09b3792f10b85f25653909d4e0ba21bf371caa2a8201010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a1037ee7af8b035d931abe66bdc955495b52603582512cd42994406b376ad9ef20a4df682aab1ba1801482b4a0b177abedd04fdd41aeb9112575e9d4ddce6095202", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000967b232b9643f8d5ca3dffee1b4acf7de1672f2c9929a5c635b830c1ffa52cee80c48ad8e95d0600f236a62b4584a8572df4e8a921fbbe8d7829c70bc768aa90dad51c6c20bfdd113c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003ced5170555eb5f0e055e3832f421f401f925c8f0ae241e09f8882c0dd46023da3f79fdaa4128d04f028434fc490bbd487acf084e4a1df79105b12da6e00da606", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000d92752c32d727c37cd6e5a40ac3e1e3f61299780b3515a30453ed602fd62881f0049a9d8e95d0600a7cfdcc30c965b366b1a2514163c16a84203885a274a71b84f2b1099f065bd5d3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003132acf970b1d39ee4abb22616013729650c7155efb3edf0ca6a5668cc9c2a57c9e45440ba766909eb82f3b9c4223b4d51f6837cc565a52b11c4595dbfa3cb60c", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9030000003edbf9d9d3cc4cbf99666abf7037ebd84622eb3250065ab583de307553aa98f4408bb8d8e95d06000601093d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000081ea5535deafa0301140200de6ae093d2a3ba5844731c029f733e1c689f4aca6d6473069f8855f4c8e48eba57dfc3a8ac9f656008c0b1290e44465023f7710f" + ], + "outcome": "refuse", + "reason": "media_order_invalid", + "at": 3 + }, + { + "name": "autoplay_setting_off", + "about": "started by autoplay while the viewer's autoplay setting is off", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600631e2ba9638f15d7f22a86a6cd9fa61401422f453f9b77fd6ebb38d17d3a47f801020201102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10ffd77fba4699a915ae98c4eefa4fb99a3e4c986710442680f95e3f97ec078640391d82aeb9b5fe9c3df07239007ca3cc300fe7ea13ca8d69f91cf4858f89ef0f" + ], + "outcome": "refuse", + "reason": "autoplay_against_setting", + "at": 0 + }, + { + "name": "autoplay_disallowed", + "about": "started by autoplay while the platform answered disallowed", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600a7c1d7b57c622ee86b97ac413ddada1f9fbfb3812ec181d2f6940a6e517d1c4201020103102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10d22604a790e4fe313409983a8db4da76fc76aa70bc14372aa9e0196819ce7a653009e84fe7414549f9c3fbaccd4c4847c15f0cba11f23d85a69efdfde2315900" + ], + "outcome": "refuse", + "reason": "autoplay_against_setting", + "at": 0 + }, + { + "name": "totals_across_classes", + "about": "the sink decode's counter and instrument figures summed into one total", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600944aa736d48d1826e624d3c8ab9052a93d74a9394fca852e1325317f62027c7101010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a104fd7bb357d53fa94bda5696ee113d7d67e943b19c1f4debd6bb72115f81e91a0bff98f2d915eed2df6dc405ab1712e265c3dbe8959b4bc42c08480322462a807", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000b619b93bb9b6634cc995ab04907af2fc0b8ec33d8ac6b6461ebfe7b386ad7f0880c48ad8e95d0600692d22ac035eade034a5d9c88f5fa4e01876efe473b8d5c7f767bd1d87c0e62b3c00000020a1070000000000000000000000000080841e00000000000200010350340300000000000500000fa000000000001e84800000001563616c3a636f727075732d6d657465722d3030303158147bcbffdf4f5e5b1275982624b4b0b5a645a9c972cfc8eb76f27562bfdb4d86a795aae620f5abd9bc922fe6992a810e6d0097fd5fbb8f45d6b329216d2d04", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000e0de22a78c70699d34188767eec20e9788c98659ead7d529c6a54283412b074e0049a9d8e95d0600ad023c563e8fd998bde2375b9689c49b308f486864ae68e51cc2ac4616c10df53c00000020a107000000000080841e000000000000093d00000000000200010330e602000000000004000061a800000000001e84800095d25d9234903bdb5d22a2d01994e6bc276d9fef45f0a066e4ef05fd1a23d555d7e6c1b02013e7cb1de11968ba45df56744accc646257a5fa948c5faa82ae702", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000ebcc1d5f12ff9ac27a269953f010172b94dc3cf43dd0126828fa70917164f8ec408bb8d8e95d060006c0c62d00000000000200000001030402000000801a06000000000000125167c233fd410ec569eb805fc3fce024d2ff48881aea7085806becf7e408534cfd597ad0bf5b18338fe580b48a9c599eb943ea5b308b292b60f33c1b37070c" + ], + "outcome": "refuse", + "reason": "totals_mismatch", + "at": 3 + }, + { + "name": "totals_wrong_sum", + "about": "a total 1 µJ above its figures' sum", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003531c174cd5963e8fb73ed34d6b862b52472bcaef6c6112d8d12558ac20383ae86dcc61ef54dc506ad25dc8b566a963725fb778c179651d7a39060f0ce88fee05", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000741e248f8cd5f22ec26be6de32fef87a4a4058401729ac42a45136ddf1d4125f408bb8d8e95d060006c0c62d00000000000200000002030402000000298205000000000004030200000080b92a00000000000101020000002d56423fece392116b3d2e9c57907bb7b43d8b880217358d56ac7f9fb654af3ef7420aa99cea53903704a670e5ed799a5a57b85b123e2f334efee52b9756b70f" + ], + "outcome": "refuse", + "reason": "totals_mismatch", + "at": 3 + }, + { + "name": "unmetered_count_wrong", + "about": "the origin's unmetered count one short", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003531c174cd5963e8fb73ed34d6b862b52472bcaef6c6112d8d12558ac20383ae86dcc61ef54dc506ad25dc8b566a963725fb778c179651d7a39060f0ce88fee05", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000741e248f8cd5f22ec26be6de32fef87a4a4058401729ac42a45136ddf1d4125f408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a00000000000101010000000c96bd007920f063b41a3e336d1cb239d394f92aa53e253c62bdec97949a4f7c13e8f22341d2f3cdb26249776bd95a7c3efc882891e1a09cfb413371cb90d40e" + ], + "outcome": "refuse", + "reason": "totals_mismatch", + "at": 3 + }, + { + "name": "unmetered_as_absent", + "about": "the unmetered origin left out of the termination, as if it were absent", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003531c174cd5963e8fb73ed34d6b862b52472bcaef6c6112d8d12558ac20383ae86dcc61ef54dc506ad25dc8b566a963725fb778c179651d7a39060f0ce88fee05", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000741e248f8cd5f22ec26be6de32fef87a4a4058401729ac42a45136ddf1d4125f408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a000000000000aff25a7ae4e2c6c4550c5d36abce20b193d14e5e554e59572e09de21ff5b84a1e627a83bd257a84581ea1ace69cbf7c6b9d1f75c7710e7529d2ca149da8ffc06" + ], + "outcome": "refuse", + "reason": "totals_mismatch", + "at": 3 + }, + { + "name": "segments_count_wrong", + "about": "a termination stating three segment receipts of two", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003531c174cd5963e8fb73ed34d6b862b52472bcaef6c6112d8d12558ac20383ae86dcc61ef54dc506ad25dc8b566a963725fb778c179651d7a39060f0ce88fee05", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000741e248f8cd5f22ec26be6de32fef87a4a4058401729ac42a45136ddf1d4125f408bb8d8e95d060006c0c62d00000000000300000002030402000000288205000000000004030200000080b92a00000000000101020000000aee855e7068418fdc21d49f28e094d408b72cdf97ebeda90e764eebf8448a83421f6d7c488e6a7d91811d8eec1c51b41ba71fb0fa6f95cf05b9b38e880fa70b" + ], + "outcome": "refuse", + "reason": "totals_mismatch", + "at": 3 + }, + { + "name": "seq_gap", + "about": "a record whose seq is not its index", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9030000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c150000000000037ca6431565f08bcadf72a23f257095def61c29047c3b71da9733d6f53440e12b5b9bd029214c53b62cbb3abfc9005dc7d4f8a9d556cb0ba23d5c76c0247f210c", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90300000006de63a2a11a6cdf66698151dbff81cd16ba8b6fdd2a2f881da4dd6baf72afbd408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000229cd6f0832a7a638ead3feb47ca7cb528cb8d6e9f2c2c92fdea817810c4ad2870224727df721a206be851e8da257e13ada430f8a1524bee347dd787165fcc08" + ], + "outcome": "refuse", + "reason": "order_invalid", + "at": 2 + }, + { + "name": "prev_wrong", + "about": "a record whose prev is not the digest of the one before it", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90200000007070707070707070707070707070707070707070707070707070707070707070049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c150000000000037cee8939f71d4a34fe832ddca9d0fa6c6a1ac045b0b42c50c55772f69d511b6b6456609c21dcab42bb07b1f9869517c39e4705c5ebe5b25f523e8c056ee9d80f", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90300000045ce9220672102d469262ce8fd17c56b8ba4fe53a0d681d84b322c1e48ba2a8b408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000cf69493aa9d53cfe08c26fd2d23a999856dc95f2c6b71269c8ad573ffb949986ff762e6caf5a52ea28cf8ac08dbadde61947e490c0c3950f797de2f21d85d705" + ], + "outcome": "refuse", + "reason": "order_invalid", + "at": 2 + }, + { + "name": "time_backwards", + "about": "a record made 1 µs before the one before it", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d737fc48ad8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003cdcf3648fe2e1fb3075935ff6246652daa2c7ea18e547adaa64210cb02f69ce40d8db63a7e0cccf3020b5144643d3a26d266ba79d5f482e22df696bfd04f0408", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000b5c9b82064ccc7df5d659eaa495527d2755e96fbeee1bc616e5307f9b48f2ffd408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000644e6e430d9dec78dc895607bf0fe438c1b3ba68152008c3c7995f0b1cba0b645ba1427b17c324c03fd81735904e3add7988cc9dcabf1d9147ff8baf4f14ee0d" + ], + "outcome": "refuse", + "reason": "order_invalid", + "at": 2 + }, + { + "name": "second_open", + "about": "an open record after the first", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600a593620b11ac7387dfdfcd6a7d81fabc6430f03560c8059a73bcc4e0090bdc1401010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a104a604e0fcb12f5e5c7361390e97630c443c338477112233868b258839afa7470b0c1718007a030f98e709932e8ed09efedfb5e9fc53bf895404df1428b58350f", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000140af8b728122ba31d7fad9695785be384c693b50acdfcdc3c78a209334db3a90049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003bb8b333d0ba8f69fddc07d43ec6c3442984ea216651da3c240c30bc37c9150fc7198b2334e26afdb3786b291de8f86764ee87b7f0cecd7b66a7e035894ee520b", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9030000002e19e173db32f4914ea85e7c90947ea121a7eb12aecb172ec2a84cdf647d4a95408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a00000000000101020000003a886c5ff03d19bd673cbed5587d8822644620750607eb8158146f582e6467577101080263d9a1b2bc926edc4452c9c2da97aab6985403602666415da5819607" + ], + "outcome": "refuse", + "reason": "order_invalid", + "at": 1 + }, + { + "name": "first_not_open", + "about": "a log whose first record is a segment receipt", + "records": [ + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c1500000000000367bde693109e87e89d34ead9abe45ada3e49460f6dea41840eb817e9d5bb8f3b72b4a45c55ce0052787888f2f138bc972e90a260c1a5b0e471e3464cbc5cb80a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90100000049d0a8c079ce6a8aed890099c44f1b6e23118e56d9423642b1a51a4ead4498240049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c150000000000030fbe3b207300641c399a8fe9b27d92573f0b0612ead1887d945e47eb82b3fc886762b2a728fe70a957b800180126cb91d9a2b0a0e570ae903fc8aaf03d6e0503", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000c69b227f60c2ceea938a8d2ea2fc6a1e4ce099f49556ef26651cd4052d6eaec4408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a000000000001010200000044ac7dc36a0c42f6ffd67838073c1dc80b8359d854523364c722acd86af4971a44d2168e6c325560fbe7470b6e319f57428a39fa0baade7d04d6fd29e93b9400" + ], + "outcome": "refuse", + "reason": "order_invalid", + "at": 0 + }, + { + "name": "empty", + "about": "a log of no record", + "records": [], + "outcome": "refuse", + "reason": "order_invalid", + "at": 0 + }, + { + "name": "sink_changed", + "about": "a record signed by another key, chained correctly", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a", + "57534c3101023949c7e115065e1897874a43e87831d6cf36321a3b81fe18e65e9c2ffe5299ab020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003c7f977e20f36fb3e6504980ba9ed6d0aa976719a60acc9e0687a072951783135fcec12fe3747666814d3bd1efafdc7df7462efe5aad85ab9f5f7bffc94837902", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000cb37250be09e878fa7ce7b900bb6485f7247e5910cbf39c79a69eb8c766908e9408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a00000000000101020000002eaff55ec6a6d840b06f8b8c54884d3e9c149330b393b9e487120cb65c5829a793fca87273277f92da8824b3d2916d42c4a9505b8a243c2d11bc3f69867cf70e" + ], + "outcome": "refuse", + "reason": "sink_changed", + "at": 2 + }, + { + "name": "hwshunt_hop", + "about": "a hop figure of the legacy class HwShunt, tag 1", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf0200000000000104c05c1500000000000380965555fccac340f9c73f3a08cde62d19ab92826fe54c9356beed4387d669e0c3e88571d466f9b5a4cbdcfe914c5f6d00552e81c63f3965a5fdb18fec537b00", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000000ffbb678cdda63621cd67f6b2215eb1e17e0c0af16bb26070573e49c58926d120049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c150000000000035301eb22b82317638b997f57d8c35d0f5b217d1ce9d656a8846897c9dfabdee43476ff9db64049c79f32ac3ef6be497de6322ec650d3d057f9798dc08c66720f", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000b286c2d743e13f19a1d9cd57b63c786be6ed6cc4ec3f5fa18ae59c3931e8a3b1408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000e6ed3c989a04b9c0da35592aa41058dfc4352f9d609f3d2d9bb4e13f9121bb1a3ccfa2c4da0ffdf390de43e2aa7747a2693abea62a0b9c0b3561243e721ac909" + ], + "outcome": "refuse", + "reason": "energy_invalid", + "at": 1 + }, + { + "name": "below_resolution_floor", + "about": "a counter figure of 10 µJ declared at 1000 ppm, below the resolution floor", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e000000000002000301000000000000000000030a0000000000000004000003e800000000001e84800004c05c15000000000003f8048d98971c8eafee024dfc88d31436ba1130d458461280371d4602a3fe6e7861e1a3594e9136f7b847b1a727a71bc795c40de6c8a0a46faa46cbaa492f7b0a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000528a274d0457263761f4bf0aae85ee524b83739803e1a87426423b9f218056900049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003ce39af1cb6b96e43c101c20a527444e93b8ddb5207b9974130f6140c9edd9b4030024a358cdaaf79851d2b1103d20511f035eabf063bdf8f8a320c9d843a800e", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90300000083a94574d71415730485c823131c71e4489f43ed3808b7e523a4a0f09edf80ab408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a00000000000101020000008641de969bab6a574e62bc35eeb976c865d6473fb76a179d94f3b268f1acba0d4806276190fd9b52baf763b89706e3462cc189b3af586cf2d1f56b654307d201" + ], + "outcome": "refuse", + "reason": "energy_invalid", + "at": 1 + }, + { + "name": "marker_with_class", + "about": "a zero figure with a class: an unmetered hop written as a measurement of zero", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000020320bf02000000000004000061a800000000001e84800004c05c15000000000003169565d4d69a9a62658705eee614e62496472796a4912177d212c6694528ef01d647b0cffc2f204036ca84d48afb9acfc82fb3113682de4c55344cba456d4b00", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000cdc73cada51d25f20ca28f8e95d6425e446369abbfdb3d229e00bfb63f1776210049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c150000000000039bd67f5a7c1ffc94d115f2fb78ab1678019588c13ee0268d4c66294c48b461c6610658f2cd0f967ec6cea3db60683a90dbf912952bfb0c8b20ac1fc125790902", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000d42046d5dde73199cb8b2855b133ea7ecd08622ad9fd8be297e558bb595f3d2c408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a00000000000101020000009b2763255f9bc6c7d3558b42a9010173db512e219c8381bc89bc9d5169a49793d0b553160d773ba342159f7a9c51518eae5456f4522f673585c78b25f7600102" + ], + "outcome": "refuse", + "reason": "energy_invalid", + "at": 1 + }, + { + "name": "figure_without_class", + "about": "a non-zero figure with tag 0", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010500000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003030b9fea6df7dfd3a4fade11cca42e6fbfa724292161837066c743f75cc0e54d1fbf65aef7c4b445cad7bd5530583f2f83f70a93dec7c8557220d2ea91f07502", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90200000097f2eeaa4f3de3d0460d91d96ec323cd3c05036949dda26a1f67523cf56463990049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003cefea708f07b27fcc858b5b12d4e21859a714b2234474974555f6bedc43a3d5f2affe6395873d46e9244f3a8d8d2a8b9a478dc4aad9c2222dc8ca783d1867709", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9030000004cfbcf220719a9f0294c1c4c88a1ee732c2c153e123c036776249d3fc8d50447408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a00000000000101020000002e8f148f91e59b0fb1de73affa62b4daf09489f2f77f4bd7899b16058ac7125a2d42094cb9d21c7457d51ed64e3bd70da8873b530a12d0f6507e766fbac7fa0f" + ], + "outcome": "refuse", + "reason": "energy_invalid", + "at": 1 + }, + { + "name": "reason_unknown", + "about": "a termination of reason 8", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003531c174cd5963e8fb73ed34d6b862b52472bcaef6c6112d8d12558ac20383ae86dcc61ef54dc506ad25dc8b566a963725fb778c179651d7a39060f0ce88fee05", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000741e248f8cd5f22ec26be6de32fef87a4a4058401729ac42a45136ddf1d4125f408bb8d8e95d060008c0c62d00000000000200000002030402000000288205000000000004030200000080b92a00000000000101020000009ea94a233b448cda56fb6e69155a65841ef9aa19b0207e07a086cf37a6ff4b6cc20eaf781c974bc9cba735f4e90e4bd66ffd4f3950604f2d8bd3265ff9a0de01" + ], + "outcome": "refuse", + "reason": "reason_unknown", + "at": 3 + }, + { + "name": "short_above_long", + "about": "a declaration whose shorter side is above its longer", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060009f133df6fa12b6c373b069c1d53dc185b8d8257c7e5793ae569a7c705f43ae001010101102700000000000000000000380400008007000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e39257358857ad04b37e8a00d048db74f6f84f3db5e3eb4f15a3897bc31a58cf7a5d0367525428331464d38ab7baf2f8479bcf5a3a41c9341124422eca6ea302" + ], + "outcome": "refuse", + "reason": "display_invalid", + "at": 0 + }, + { + "name": "hdr_byte", + "about": "a declaration whose hdr byte is 2", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060006e936678eb609ea67245580a2e311dd58316ed2006ea9c019f34aa4ba26bdff01010101102700000000000000000000800700003804000060ea00000802ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a1030558c859f634e0f5cc733547c4bfc01ae4b49f8271df4cc086ec8f6c87dc725bb29d87515a476b098b294d9a4f66deb910043cd2500929ff62b91bee37a3f0e" + ], + "outcome": "refuse", + "reason": "display_invalid", + "at": 0 + }, + { + "name": "audio_declaration", + "about": "an audio stream's open record with a display declaration", + "records": [ + "57534c310101a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc00000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600b4caa553d1a85e95f98f70f1b668c041968233732383f015aac8a68be754695302010101102700000000000000000000800700003804000060ea00000800ba040000a802000000506cdeaeac51a1aee802ed12e2477c6dde54806ea223a165eefbd1b209365db3929f1ed64699635079b5d456afe4a1a0bc48ccfa352e772c04e2252515bcf70c" + ], + "outcome": "refuse", + "reason": "display_invalid", + "at": 0 + }, + { + "name": "display_in_audio", + "about": "a display record in the log of an audio stream", + "records": [ + "57534c310101a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc00000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002feb61650b42e2922f12d627c40ff1147fd4ea4826a87cccf2e81c3f3eea91c802010101102700000000000000000000000000000000000000000000000000000000000000000068bd102077718e566a4af19a53677b39d186f5b6c6e21b663280821c98bb48bca7c8ce90278252b4a58307a45477b5f78a4144341ccf3efce13f62e2fdfbc806", + "57534c310104a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc010000007858ba2f7414a34e107c806b2bd26b82f388aa53d4b32334a3b7d9a1f99d791a40827bd8e95d0600800700003804000060ea00000800ba040000a802000031202b35e8ed59d26f6c57117b5ba1a19f91418813a031303ca8a2c76e8389d4414d12bf958807cf3091a1ec47c4c5ac991eccfa00f8cf87bfed8b0b336ec201" + ], + "outcome": "refuse", + "reason": "display_invalid", + "at": 1 + }, + { + "name": "display_zero_refresh", + "about": "a display record declaring a refresh rate of 0", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06006bdc5c9789cee43d76288a36ee7122bb72b022b6d704f6c588724446bc7db25301010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a1013990fb77c0dc4bfb811ad55453c9bf27ea85812617ad78d22ee4f95e33b108f147dac674fe048a2ca98acfa05a391020ad5341aeb8a633ab1267e5f74bb860a", + "57534c310104a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000860104570ce56db3baf311cc63d5a45dbc74f7cf0515ba7df1f1edc8fcdacd2b40827bd8e95d06008007000038040000000000000800ba040000a8020000f651e10afef8cd0b226a07a250979ca87482fa025f54c792875107ea3d24884b7fb42aa7adc28e27b81a11da284c6e125d220f1e325ac77d29fbce026cf9ac02" + ], + "outcome": "refuse", + "reason": "display_invalid", + "at": 1 + }, + { + "name": "empty_ladder", + "about": "a video stream offered no rendition", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600c419246c693d29d0dcadc7ab633eebc69aa08d908560f5d23084d5799c4ee35401010101102700000000000000000000800700003804000060ea00000800ba040000a802000000e18090b9d094e0b4df023d38c4c97f33746b8808dcbb74e98256506b060702b35e7d1a1836b00060f12b2921148318553609c0813cfc23fc43922c92d1787105" + ], + "outcome": "refuse", + "reason": "record_invalid", + "at": 0 + }, + { + "name": "media_three", + "about": "an open record of media 3", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06006d3ad10465b13a780c3e7f2f6e6f43e97e0e15691b0c74a1956742e41f89b38a03010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10b0c71cac2f8ea782c5974a4eaf31d896997177d6133d80d23e0e7a15ede4023cebe589ddac52d41728ae720d1ffcc62259f5c82dba799eeb455bf302b683590f" + ], + "outcome": "refuse", + "reason": "record_invalid", + "at": 0 + }, + { + "name": "signal_five", + "about": "an observation of signal 5", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600f31603017669c2ab667ce93f70fc8203c9203a479e219bb01f20ce69b57b302e01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a105f6744d5d73cd496d9d06890832d717e58a96918ff9dd74e2ffdff243d7352df54adcc62b46879c898d418ffddad48d760067c25e1744fa744b5ad836c6c3e05", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000b4d13112e0739452e9c32fde52304f3828f9cc4616f8f8738b393d2d3d8ef50540827bd8e95d06000501af1ee1be87c7465f1e387b7fbad923467fd9cb4cb6cfaf5eedfd141aa807b6395e3cae2ad7dc0fe2a2b58f8fd89231a3d37c22568f42ac24224b6559f081fa0e" + ], + "outcome": "refuse", + "reason": "record_invalid", + "at": 1 + }, + { + "name": "interaction_value_two", + "about": "an interaction observation of value 2", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06008431b66869d4cb76374d6cb4af79421c6a240062fdf2a0c68aee5dbc43917c7701010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10222c63319303a59ba19eca3d847a94193d857eecdf35065ddf5650f32a2584cee7ce022c3d2b1a0ebb0cc4e2ea4acbfe55183c8f4dcf12ba57a9112330b83b0f", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90100000066c55cd8fe281b5ba2c86c55b303da2e7957ff81e3c956f786924c14effc047040827bd8e95d0600040211cb106492d3e802b6d5f23ab3a057fa389e2065c378fed5e3b71d65cd4f292c3350840bb766a6d454bb311529fcf0b2b6608822711fe4fa6b0953cf8368d90e" + ], + "outcome": "refuse", + "reason": "record_invalid", + "at": 1 + }, + { + "name": "empty_segment", + "about": "a segment whose media ends where it starts", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600136418f34e5339ea9193556823651578051de3d3038b9043c5b4d7dcf7c9c46201010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10898f589a0e180d480dab417467b341300ca04a5d38e9a40fe3df97bc43db521b13959d54452899ae5adfb8e57db21c05bf89434e90657579b0beecb8c0541d0e", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9010000004372ca58511e83dec6a594eb8919968c393ec87a2213f346074b6a05f713821680c48ad8e95d06000c0109ea73f3ba582b08a426579a9bd94409719bdbd0f784a7a37d8a1c6f3fdf3c00000020a107000000000000000000000000000000000000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003fbf15e782f2a10d534059178560ec3f8239aac74ba01ef41d3e1d2a95497c3c0371744c0c6de27534a3ef03a7e8946da0f579e855d33e4326a69be15e89e9e03" + ], + "outcome": "refuse", + "reason": "record_invalid", + "at": 1 + }, + { + "name": "hops_out_of_order", + "about": "a segment's hop entries out of the order of their roles", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06003219cc02240289920d2480b97dd18aa697c52d7e86408d8951be9067ea82313301010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a103d07fbcf84277f3675161e96503f0967775b47f3a006979a0c6bf9b84a1e34ca531d52ba5411361e2d81528ed1fd7e000577ad32e657772fb8b5748ad92c9f0e", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90100000025e4007eb4305ece943938cedb762d6f92a521a5fd0be105b08f08439def554f80c48ad8e95d06007b50c81eaeabdea30f27511214ee0b4ec5e2fff451679291e7b02d53dd9cd6b53c00000020a1070000000000000000000000000080841e00000000000200030320bf02000000000004000061a800000000001e8480000100000000000000000004c05c15000000000003b6e3d63eca20643976bc4896f73c9a0e2b6c44f89c935e9621eba790fd924df8cd44a65cc1de1bb098a4f11224a2cd08db63a5867db492f58d9794f453ee160c" + ], + "outcome": "refuse", + "reason": "record_invalid", + "at": 1 + }, + { + "name": "match_bit_five", + "about": "a match with bit 5 set", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600b23fbfc2e0adf1e46e43beb6495228c6ea8e2ca68682ab2e929b85e10e93fdce01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a109c1dfc3ac29d50b1d93f035bbe9d65d79840d1ba6734d3ad391984b4d35095079d03b5d11b2ac54bafa8748d0fc1670dba6886fecabebad1badf19ff13540504", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000a0e12dd6a5194e0bc9927b2e8fabeeb70a76b22be0d1c52eba2929fc700c917480c48ad8e95d060054c89a0ae602dd134f5f1993029686d2cfdca978efbf1c579c1a48e65ad3782b3c00000020a1070000000000000000000000000080841e0000000000022003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003cc43b89b36c589c7d1900e631bfdfd9125a2d011ca576de3ee7342e9913b1310ec3973c9068fd4ce2ee9b17a5ca0c1ae3f5fe67a8509a18e665f022d704d4e01" + ], + "outcome": "refuse", + "reason": "record_invalid", + "at": 1 + }, + { + "name": "total_of_tag_zero", + "about": "a total of tag 0: a sum over unmetered figures", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003531c174cd5963e8fb73ed34d6b862b52472bcaef6c6112d8d12558ac20383ae86dcc61ef54dc506ad25dc8b566a963725fb778c179651d7a39060f0ce88fee05", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000741e248f8cd5f22ec26be6de32fef87a4a4058401729ac42a45136ddf1d4125f408bb8d8e95d060006c0c62d000000000002000000030100020000000000000000000000030402000000288205000000000004030200000080b92a00000000000101020000001c246ca3c165eaf044f1a7b19b1306b55ffaf2b52f3c886a3591bea9358eb27a708a8d8ea814b7cb8fa482c533ceb008555b32d4119ea6c53a62d35aa112610a" + ], + "outcome": "refuse", + "reason": "record_invalid", + "at": 3 + }, + { + "name": "truncated", + "about": "a segment receipt cut 1 byte short", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a17", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003531c174cd5963e8fb73ed34d6b862b52472bcaef6c6112d8d12558ac20383ae86dcc61ef54dc506ad25dc8b566a963725fb778c179651d7a39060f0ce88fee05", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000741e248f8cd5f22ec26be6de32fef87a4a4058401729ac42a45136ddf1d4125f408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000b39ed85e7bcfe31473c9d1adb6506c52e91b2934b002872b82a6aea839a5358bc4a2bc6d908750d87adfd69e3d398917ac6a1925da052539de2a422849666b02" + ], + "outcome": "refuse", + "reason": "truncated", + "at": 1 + }, + { + "name": "trailing_byte", + "about": "a segment receipt with 1 byte after its signature", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a00", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003531c174cd5963e8fb73ed34d6b862b52472bcaef6c6112d8d12558ac20383ae86dcc61ef54dc506ad25dc8b566a963725fb778c179651d7a39060f0ce88fee05", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000741e248f8cd5f22ec26be6de32fef87a4a4058401729ac42a45136ddf1d4125f408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000b39ed85e7bcfe31473c9d1adb6506c52e91b2934b002872b82a6aea839a5358bc4a2bc6d908750d87adfd69e3d398917ac6a1925da052539de2a422849666b02" + ], + "outcome": "refuse", + "reason": "malformed", + "at": 1 + }, + { + "name": "bad_magic", + "about": "a record whose magic is WSL2", + "records": [ + "57534c320101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003531c174cd5963e8fb73ed34d6b862b52472bcaef6c6112d8d12558ac20383ae86dcc61ef54dc506ad25dc8b566a963725fb778c179651d7a39060f0ce88fee05", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000741e248f8cd5f22ec26be6de32fef87a4a4058401729ac42a45136ddf1d4125f408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000b39ed85e7bcfe31473c9d1adb6506c52e91b2934b002872b82a6aea839a5358bc4a2bc6d908750d87adfd69e3d398917ac6a1925da052539de2a422849666b02" + ], + "outcome": "refuse", + "reason": "malformed", + "at": 0 + }, + { + "name": "kind_six", + "about": "a record of kind 6", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310106a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c1500000000000372ee98fef0c3dc610b0e4e4e01ef7726942f2518a18ae7634e809a2893631fa3b4a2b5c6ca4aaaed14988f6e8ff0388415bdc0ae089d47a9dc66b9879bf5c204", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003531c174cd5963e8fb73ed34d6b862b52472bcaef6c6112d8d12558ac20383ae86dcc61ef54dc506ad25dc8b566a963725fb778c179651d7a39060f0ce88fee05", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000741e248f8cd5f22ec26be6de32fef87a4a4058401729ac42a45136ddf1d4125f408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000b39ed85e7bcfe31473c9d1adb6506c52e91b2934b002872b82a6aea839a5358bc4a2bc6d908750d87adfd69e3d398917ac6a1925da052539de2a422849666b02" + ], + "outcome": "refuse", + "reason": "malformed", + "at": 1 + }, + { + "name": "unknown_version", + "about": "a record of version 2", + "records": [ + "57534c310201a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003531c174cd5963e8fb73ed34d6b862b52472bcaef6c6112d8d12558ac20383ae86dcc61ef54dc506ad25dc8b566a963725fb778c179651d7a39060f0ce88fee05", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000741e248f8cd5f22ec26be6de32fef87a4a4058401729ac42a45136ddf1d4125f408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000b39ed85e7bcfe31473c9d1adb6506c52e91b2934b002872b82a6aea839a5358bc4a2bc6d908750d87adfd69e3d398917ac6a1925da052539de2a422849666b02" + ], + "outcome": "refuse", + "reason": "unknown_version", + "at": 0 + }, + { + "name": "bad_signature", + "about": "a segment receipt with one bit of its signature flipped", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170b", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003531c174cd5963e8fb73ed34d6b862b52472bcaef6c6112d8d12558ac20383ae86dcc61ef54dc506ad25dc8b566a963725fb778c179651d7a39060f0ce88fee05", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000741e248f8cd5f22ec26be6de32fef87a4a4058401729ac42a45136ddf1d4125f408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000b39ed85e7bcfe31473c9d1adb6506c52e91b2934b002872b82a6aea839a5358bc4a2bc6d908750d87adfd69e3d398917ac6a1925da052539de2a422849666b02" + ], + "outcome": "refuse", + "reason": "bad_signature", + "at": 1 + }, + { + "name": "bad_key", + "about": "an open record whose session key is the identity point, of small order", + "records": [ + "57534c310101010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003d8db2a97e9579bc0bfb8c52ab07da29b2a27d3af1c439f163cebf31a3d2532caf41b6a0f18f20aafda3a2f2658cad02a8542fdf0dcd11839be3a2a9b921a170a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9020000003647c6222a066804228e3f5afe2366aba993e5d41121eef1a1174485f81a2d730049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003531c174cd5963e8fb73ed34d6b862b52472bcaef6c6112d8d12558ac20383ae86dcc61ef54dc506ad25dc8b566a963725fb778c179651d7a39060f0ce88fee05", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000741e248f8cd5f22ec26be6de32fef87a4a4058401729ac42a45136ddf1d4125f408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000b39ed85e7bcfe31473c9d1adb6506c52e91b2934b002872b82a6aea839a5358bc4a2bc6d908750d87adfd69e3d398917ac6a1925da052539de2a422849666b02" + ], + "outcome": "refuse", + "reason": "bad_key", + "at": 0 + }, + { + "name": "long_reference", + "about": "a calibration reference of 300 bytes", + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06002130fdc20682451365c2c3dcc85c57b56e04ad5c752f10e9f3c4af5e3568cfda01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10e202b85aa56c372492ba7ddb5cd55adb38b00690a7ad43689de59ebb14661757124b7f0646af60d96908359ac3b7a3be78a7f1656d9321a35113a77acd731b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000aa726dde1255d562aa2e560c7ee65bf875909e293fea58d73c6ed65549b05c3080c48ad8e95d0600821082af8b064e6bc61a2236a05d52d8bedeafeba4c6daac1ed443e22e8191553c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf0200000000000500000fa000000000001e84800000012c61616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616104c05c1500000000000382d845622aa334c06a12d564ed33f1f7c481d9fe06a131a0422912df405eb0ced4ca389db1b74a61e1f0fdf1402d1ef3f1e6b44c42036aec9852d604bada8502", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000b6c6d3681ca77ba3b1b8524fc38a9dfc7ce51448ce26ac416c4241c9ba1df0d20049a9d8e95d06002a593918d3b45ee585be205f2052f8375198815d674908d6cd3dd3bf1ccfe4ea3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003613857e8362017647eeef3587fc22f4175e037350a753d4f2fb997a51cd826f92791a9e223adba076a814ed89924e9bb57c2910c41519098b396622fa59f5203", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000db7ebcf0af22474e2dd53165a67fa957dd3fd2785febe1a2fcd2c93efdf388c9408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000d6755a149cf5ce181cf3715bbea7befc27700ec646482fa94d4920ee3debce9a198839324b743f0f62dd22599d3477f2e2aba6db9a95f9bf5695a781b7189502" + ], + "outcome": "refuse", + "reason": "malformed", + "at": 1 + } + ] +} diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index e5e2eafc1..d619ea98a 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -247,6 +247,16 @@ energy_steering = ["live_metering"] # hash, the tier), with the side table a page carries it in and the state of the frame on screen. # BLAKE3 and serde_json at run time; Ed25519 to sign the corpus's group receipts. Builds for wasm32. browser_sink = ["jwp_hash", "ed25519"] +# Stream termination (extensions/stream-termination.md) and the display match of +# capability-constraints §4: `wai.live.termination` — a sink's signed stream log (WSL1): the open +# record with its autoplay setting, termination policy, display declaration and ladder; a receipt +# per segment with the rendition's match against the panel and each hop's joules with their +# acquisition class; the observations of visibility, occlusion, display power and interaction; and +# the termination with its reason, media time and totals per hop and class. The verifier checks the +# chain, nothing after the termination, the reason against the observations, every rendition +# admissible under the panel, and the totals, and states what was delivered unseen. SHA-256 and +# Ed25519 only; no new dependency. Builds for wasm32. +stream_termination = ["ed25519"] # Live moderation (extensions/live-moderation): `wai.live.moderation` — the room authority's # signed review policy (WMP1: what is held for review or reviewed after delivery, by whom, each # deadline and fallback, and the reason codes with their grounds), the statement (WMS1) that @@ -880,6 +890,11 @@ name = "wai_browser_sink_vectors" path = "src/bin/wai_browser_sink_vectors.rs" required-features = ["browser_sink"] +[[bin]] +name = "wai_stream_termination_vectors" +path = "src/bin/wai_stream_termination_vectors.rs" +required-features = ["stream_termination"] + [[bin]] name = "wai_live_moderation_vectors" path = "src/bin/wai_live_moderation_vectors.rs" @@ -1683,6 +1698,11 @@ name = "browser_sink" path = "tests/browser_sink.rs" required-features = ["browser_sink", "receipt_claims"] +[[test]] +name = "stream_termination" +path = "tests/stream_termination.rs" +required-features = ["stream_termination"] + [[test]] name = "live_moderation" path = "tests/live_moderation.rs" diff --git a/wai/wai-rs/src/bin/wai_stream_termination_vectors.rs b/wai/wai-rs/src/bin/wai_stream_termination_vectors.rs new file mode 100644 index 000000000..e2b6e4435 --- /dev/null +++ b/wai/wai-rs/src/bin/wai_stream_termination_vectors.rs @@ -0,0 +1,55 @@ +//! Conformance corpus for stream termination and the display match (`live-conformance/termination/`, +//! extensions/stream-termination.md §8). +//! +//! wai_stream_termination_vectors gen write vectors.json, from fixed seeds +//! wai_stream_termination_vectors verify vectors.json regenerates byte for byte, +//! and every match and log gets the +//! outcome it states +//! +//! The generator and this verifier are one implementation. The corpus's second +//! implementation is `live-conformance/stream_termination_verify.py`, written from the +//! extension's text and sharing no code with this binary. + +use std::path::Path; +use wai::stream_termination::corpus; + +fn main() { + let a: Vec = std::env::args().collect(); + match (a.get(1).map(String::as_str), a.get(2)) { + (Some("gen"), Some(d)) => { + let p = Path::new(d).join("vectors.json"); + if let Err(e) = std::fs::create_dir_all(d).and_then(|_| std::fs::write(&p, corpus::generate())) { + eprintln!("write {}: {e}", p.display()); + std::process::exit(1); + } + println!("wrote {}", p.display()); + } + (Some("verify"), Some(d)) => std::process::exit(verify(Path::new(d))), + _ => { + eprintln!("usage: wai_stream_termination_vectors gen|verify "); + std::process::exit(2); + } + } +} + +fn verify(dir: &Path) -> i32 { + let p = dir.join("vectors.json"); + let doc = match std::fs::read_to_string(&p) { + Ok(s) => s, + Err(e) => { + eprintln!("read {}: {e}", p.display()); + return 1; + } + }; + let mut fail = 0; + let same = doc == corpus::generate(); + println!("{} vectors.json regenerates byte for byte from its seeds", if same { "PASS" } else { "FAIL" }); + fail += (!same) as i32; + let fails = corpus::verify(&doc); + for f in &fails { + println!("FAIL {f}"); + } + println!("{} every match and log gets the outcome the corpus states", if fails.is_empty() { "PASS" } else { "FAIL" }); + fail += (!fails.is_empty()) as i32; + (fail != 0) as i32 +} diff --git a/wai/wai-rs/src/codecs/registry.rs b/wai/wai-rs/src/codecs/registry.rs index 9ed0a7939..efa89bf97 100644 --- a/wai/wai-rs/src/codecs/registry.rs +++ b/wai/wai-rs/src/codecs/registry.rs @@ -237,6 +237,7 @@ pub const CAP_BEARER_RECEIPTS: &str = "wai.bearer.receipts"; pub const CAP_ACCESS_SERVICES: &str = "wai.access.services"; pub const CAP_AGE_ASSURANCE: &str = "wai.age.assurance"; pub const CAP_MEDIA_EXPLORATION: &str = "wai.media.exploration"; +pub const CAP_LIVE_TERMINATION: &str = "wai.live.termination"; /// One row per string SPEC §5 registers that is a payload, companion or record: /// `(string, SPEC section, edition, registered)`. @@ -342,6 +343,7 @@ const ROWS: &[(&str, &str, Option<&str>, &str)] = &[ (CAP_ACCESS_SERVICES, S_INTERACTIVE, None, "2026-10-21"), (CAP_AGE_ASSURANCE, S_INTERACTIVE, None, "2026-10-21"), (CAP_MEDIA_EXPLORATION, S_INTERACTIVE, None, "2026-10-21"), + (CAP_LIVE_TERMINATION, S_INTERACTIVE, None, "2026-10-22"), (CAP_AUDIO_IAMF, S_IMMERSIVE, None, "2026-06-17"), (CAP_AUDIO_AC4, S_IMMERSIVE, None, "2026-06-17"), (CAP_AUDIO_IVAS, S_IMMERSIVE, None, "2026-06-17"), @@ -410,6 +412,7 @@ const RECORDS: &[&str] = &[ CAP_ACCESS_SERVICES, CAP_AGE_ASSURANCE, CAP_MEDIA_EXPLORATION, + CAP_LIVE_TERMINATION, ]; /// A named identifier SPEC uses that has the shape of a capability string but is diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 073498658..b55e56164 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -465,6 +465,12 @@ pub mod energy_steering; // of the frame on screen. serde_json and BLAKE3 only, so it builds for wasm32. #[cfg(feature = "browser_sink")] pub mod browser_sink; +// Stream termination (extensions/stream-termination.md): a sink's signed stream log, its +// termination with a reason the log's observations support, a receipt per segment with the +// rendition's match against the display declaration (capability-constraints §4), and the hop +// totals per class. SHA-256 and Ed25519 only, so it builds for wasm32. +#[cfg(feature = "stream_termination")] +pub mod stream_termination; // Live moderation (extensions/live-moderation.md): `wai.live.moderation` — the room authority's // review policy, the statement that follows a `decision` event, notices and complaints, lines // of decisions by digest, the review state machine, gaps explained by the decisions that diff --git a/wai/wai-rs/src/stream_termination.rs b/wai/wai-rs/src/stream_termination.rs new file mode 100644 index 000000000..b6d4bf0da --- /dev/null +++ b/wai/wai-rs/src/stream_termination.rs @@ -0,0 +1,1287 @@ +//! Stream termination — `wai.live.termination` ([extensions/stream-termination.md]), and the +//! display match of capability-constraints §4 ([extensions/capability-constraints.md]). +//! +//! A sink's signed account of one stream: a chain of `WSL1` records under its session key, +//! opening with the stream, how it started, its termination policy, its display declaration +//! and the ladder it was offered; then a receipt for each segment, with the rendition's match +//! against the panel and each hop's joules with their acquisition class; the observations of +//! whether anyone could see it; and a termination with its reason, the media time and the +//! totals per hop and class. +//! +//! What this module implements, by section: +//! +//! - capability-constraints §4: the declaration ([`Declaration`]), a rendition's exceedances +//! ([`exceedances`]), the least rendition ([`least`]), the admissible set ([`admissible`]) +//! and the match a receipt records ([`match_of`]); +//! - §3: the records, their encoding, digest and signature ([`Record`]), and reading (§3.9, +//! [`Record::parse`]); +//! - §6: the verification of a log ([`verify_log`]), the state at an instant (§6.2) and the +//! facts an accepted log states (§6.3, [`Accepted`]). +//! +//! The corpus `live-conformance/termination/` is written by [`corpus::generate`] and checked +//! by `live-conformance/stream_termination_verify.py`, which shares no code with this module. +//! +//! [extensions/stream-termination.md]: ../../../extensions/stream-termination.md +//! [extensions/capability-constraints.md]: ../../../extensions/capability-constraints.md + +use ed25519_dalek::{Signer, SigningKey}; +use serde_json::{json, Value}; +use sha2::{Digest, Sha256}; + +use crate::energy_class::{check_resolution, CounterFiltering, EnergyProvenance, Uncertainty}; + +pub mod corpus; +#[cfg(test)] +mod tests; + +/// The record this extension registers. +pub const CAPABILITY: &str = "wai.live.termination"; +/// The magic of a stream-log record (§3.1). +pub const MAGIC: [u8; 4] = *b"WSL1"; +/// The version this revision defines. +pub const VERSION: u8 = 1; +/// The domain a record's signature is made under (§3.8). +pub const DOMAIN: &[u8] = b"wai:stream-log\x01"; +/// The bytes of a record before its body (§3.1). +pub const HEADER_LEN: usize = 82; +/// A display declaration's bytes (§3.3). +pub const DECLARATION_LEN: usize = 22; +/// A rendition's bytes in a ladder (§3.2). +pub const RENDITION_LEN: usize = 14; +/// The most renditions a ladder holds (§3.9). +pub const MAX_LADDER: usize = 32; +/// The most hop entries a segment receipt holds (§3.9). +pub const MAX_HOPS: usize = 4; +/// An audio segment's `rendition` (§3.4). +pub const NO_RENDITION: u8 = 255; +/// The longest calibration reference read (§3.9). +pub const MAX_CALIBRATION_REF: usize = 255; +/// The transfer characteristics of standard dynamic range a rendition with none is read as +/// (capability-constraints §4.2). +pub const TRANSFER_SDR: u8 = 1; +/// PQ (capability-constraints §3.1). +pub const TRANSFER_PQ: u8 = 16; +/// HLG. +pub const TRANSFER_HLG: u8 = 18; + +/// A record's `kind` (§3.1). +pub mod kind { + pub const OPEN: u8 = 1; + pub const SEGMENT: u8 = 2; + pub const OBSERVATION: u8 = 3; + pub const DISPLAY: u8 = 4; + pub const TERMINATE: u8 = 5; +} + +/// An open record's `media` (§3.2). +pub mod media { + pub const VIDEO: u8 = 1; + pub const AUDIO: u8 = 2; +} + +/// An open record's `start` (§3.2). +pub mod start { + pub const USER: u8 = 1; + pub const AUTOPLAY: u8 = 2; +} + +/// The viewer's autoplay setting (§4.4). +pub mod setting { + pub const ON: u8 = 1; + pub const OFF: u8 = 2; +} + +/// The platform's autoplay answer (§4.4). +pub mod ua_policy { + pub const NOT_ASKED: u8 = 0; + pub const ALLOWED: u8 = 1; + pub const ALLOWED_MUTED: u8 = 2; + pub const DISALLOWED: u8 = 3; +} + +/// A hop's role (§2). +pub mod role { + pub const ORIGIN: u8 = 1; + pub const RELAY: u8 = 2; + pub const SINK_DECODE: u8 = 3; + pub const DISPLAY: u8 = 4; + /// Each role's name in the facts of §6.3. + pub fn name(r: u8) -> &'static str { + match r { + ORIGIN => "origin", + RELAY => "relay", + SINK_DECODE => "sink_decode", + DISPLAY => "display", + _ => "unknown", + } + } +} + +/// An observation's `signal` and values (§3.6). +pub mod signal { + pub const VISIBILITY: u8 = 1; + pub const OCCLUSION: u8 = 2; + pub const DISPLAY_POWER: u8 = 3; + pub const INTERACTION: u8 = 4; + /// Visible, not occluded, on; the one value of an interaction. + pub const SEEN: u8 = 1; + /// Hidden, occluded, off. + pub const UNSEEN: u8 = 2; +} + +/// A terminate record's `reason` (§3.7). +pub mod why { + pub const HIDDEN: u8 = 1; + pub const OCCLUDED: u8 = 2; + pub const DISPLAY_OFF: u8 = 3; + pub const IDLE: u8 = 4; + pub const AUTOPLAY_UNATTENDED: u8 = 5; + pub const USER: u8 = 6; + pub const ENDED: u8 = 7; + /// Each reason's token. + pub fn token(w: u8) -> &'static str { + match w { + HIDDEN => "hidden", + OCCLUDED => "occluded", + DISPLAY_OFF => "display_off", + IDLE => "idle", + AUTOPLAY_UNATTENDED => "autoplay_unattended", + USER => "user", + ENDED => "ended", + _ => "unknown", + } + } +} + +/// The bits of a match (capability-constraints §4.2). +pub mod exceed { + pub const RESOLUTION: u8 = 1; + pub const REFRESH: u8 = 2; + pub const BIT_DEPTH: u8 = 4; + pub const DYNAMIC_RANGE: u8 = 8; + pub const NONE_WITHIN: u8 = 16; + /// Every bit a match may set. + pub const ALL: u8 = 31; +} + +/// The reasons a record or a log is refused (§7, and the codes it borrows). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Reason { + Truncated, + Malformed, + UnknownVersion, + BadKey, + BadSignature, + EnergyInvalid, + ReasonUnknown, + DisplayInvalid, + RecordInvalid, + AfterTermination, + OrderInvalid, + SinkChanged, + AutoplayAgainstSetting, + MediaOrderInvalid, + RenditionUnknown, + MatchMismatch, + RenditionAbovePanel, + ReasonNotApplicable, + ReasonUnsupported, + TotalsMismatch, +} + +impl Reason { + /// The codes this extension adds, in §7's order. + pub const OWN: [Reason; 14] = [ + Reason::ReasonUnknown, + Reason::DisplayInvalid, + Reason::RecordInvalid, + Reason::AfterTermination, + Reason::OrderInvalid, + Reason::SinkChanged, + Reason::AutoplayAgainstSetting, + Reason::MediaOrderInvalid, + Reason::RenditionUnknown, + Reason::MatchMismatch, + Reason::RenditionAbovePanel, + Reason::ReasonNotApplicable, + Reason::ReasonUnsupported, + Reason::TotalsMismatch, + ]; + /// Every code a verifier of this extension raises. + pub const ALL: [Reason; 20] = [ + Reason::Truncated, + Reason::Malformed, + Reason::UnknownVersion, + Reason::BadKey, + Reason::BadSignature, + Reason::EnergyInvalid, + Reason::ReasonUnknown, + Reason::DisplayInvalid, + Reason::RecordInvalid, + Reason::AfterTermination, + Reason::OrderInvalid, + Reason::SinkChanged, + Reason::AutoplayAgainstSetting, + Reason::MediaOrderInvalid, + Reason::RenditionUnknown, + Reason::MatchMismatch, + Reason::RenditionAbovePanel, + Reason::ReasonNotApplicable, + Reason::ReasonUnsupported, + Reason::TotalsMismatch, + ]; + + pub fn code(self) -> &'static str { + match self { + Reason::Truncated => "truncated", + Reason::Malformed => "malformed", + Reason::UnknownVersion => "unknown_version", + Reason::BadKey => "bad_key", + Reason::BadSignature => "bad_signature", + Reason::EnergyInvalid => "energy_invalid", + Reason::ReasonUnknown => "reason_unknown", + Reason::DisplayInvalid => "display_invalid", + Reason::RecordInvalid => "record_invalid", + Reason::AfterTermination => "after_termination", + Reason::OrderInvalid => "order_invalid", + Reason::SinkChanged => "sink_changed", + Reason::AutoplayAgainstSetting => "autoplay_against_setting", + Reason::MediaOrderInvalid => "media_order_invalid", + Reason::RenditionUnknown => "rendition_unknown", + Reason::MatchMismatch => "match_mismatch", + Reason::RenditionAbovePanel => "rendition_above_panel", + Reason::ReasonNotApplicable => "reason_not_applicable", + Reason::ReasonUnsupported => "reason_unsupported", + Reason::TotalsMismatch => "totals_mismatch", + } + } +} + +impl std::fmt::Display for Reason { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.code()) + } +} + +impl std::error::Error for Reason {} + +fn sha256(parts: &[&[u8]]) -> [u8; 32] { + let mut h = Sha256::new(); + for p in parts { + h.update(p); + } + h.finalize().into() +} + +// ─────────────────────────── capability-constraints §4 ─────────────────────────── + +/// A display declaration (capability-constraints §4.1). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub struct Declaration { + pub long: u32, + pub short: u32, + pub refresh_milli: u32, + pub bit_depth: u8, + pub hdr: bool, + pub long_mm: u32, + pub short_mm: u32, +} + +impl Declaration { + /// A panel of `w` × `h` pixels in either orientation. + pub fn panel(w: u32, h: u32, refresh_milli: u32, bit_depth: u8, hdr: bool) -> Declaration { + Declaration { long: w.max(h), short: w.min(h), refresh_milli, bit_depth, hdr, long_mm: 0, short_mm: 0 } + } + + /// The 22 bytes of §3.3. + pub fn encode(&self, o: &mut Vec) { + o.extend_from_slice(&self.long.to_le_bytes()); + o.extend_from_slice(&self.short.to_le_bytes()); + o.extend_from_slice(&self.refresh_milli.to_le_bytes()); + o.push(self.bit_depth); + o.push(self.hdr as u8); + o.extend_from_slice(&self.long_mm.to_le_bytes()); + o.extend_from_slice(&self.short_mm.to_le_bytes()); + } + + /// capability-constraints §4.1's rules over the members as read; `hdr` as its byte. + fn holds(long: u32, short: u32, refresh: u32, depth: u8, hdr: u8) -> bool { + short >= 1 && short <= long && refresh >= 1 && (1..=16).contains(&depth) && hdr <= 1 + } + + pub fn to_json(&self) -> Value { + json!({ "long": self.long, "short": self.short, "refresh_milli": self.refresh_milli, "bit_depth": self.bit_depth, + "hdr": self.hdr, "long_mm": self.long_mm, "short_mm": self.short_mm }) + } + + /// The JSON form a page passes (the members of capability-constraints §4.1). + pub fn from_json(v: &Value) -> Option { + let n = |k: &str| v[k].as_u64().and_then(|x| u32::try_from(x).ok()); + Some(Declaration { + long: n("long")?, + short: n("short")?, + refresh_milli: n("refresh_milli")?, + bit_depth: v["bit_depth"].as_u64().and_then(|x| u8::try_from(x).ok())?, + hdr: v["hdr"].as_bool()?, + long_mm: n("long_mm").unwrap_or(0), + short_mm: n("short_mm").unwrap_or(0), + }) + } +} + +/// A rendition of a ladder (§3.2; capability-constraints §3.1's parameters). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Rendition { + pub width: u32, + pub height: u32, + pub frame_rate_milli: u32, + pub bit_depth: u8, + pub transfer: u8, +} + +impl Rendition { + pub fn new(width: u32, height: u32, frame_rate_milli: u32, bit_depth: u8, transfer: u8) -> Rendition { + Rendition { width, height, frame_rate_milli, bit_depth, transfer } + } + + fn encode(&self, o: &mut Vec) { + o.extend_from_slice(&self.width.to_le_bytes()); + o.extend_from_slice(&self.height.to_le_bytes()); + o.extend_from_slice(&self.frame_rate_milli.to_le_bytes()); + o.push(self.bit_depth); + o.push(self.transfer); + } + + /// Whether its transfer is one of the high-dynamic-range ones, PQ or HLG. + pub fn is_hdr(&self) -> bool { + self.transfer == TRANSFER_PQ || self.transfer == TRANSFER_HLG + } + + /// The order key of the least rendition (capability-constraints §4.2). + fn order(&self) -> (u64, u32, u8, u8) { + (self.width as u64 * self.height as u64, self.frame_rate_milli, self.bit_depth, self.is_hdr() as u8) + } + + pub fn to_json(&self) -> Value { + json!({ "width": self.width, "height": self.height, "frame_rate_milli": self.frame_rate_milli, "bit_depth": self.bit_depth, "transfer": self.transfer }) + } + + pub fn from_json(v: &Value) -> Option { + let n = |k: &str| v[k].as_u64().and_then(|x| u32::try_from(x).ok()); + let b = |k: &str| v[k].as_u64().and_then(|x| u8::try_from(x).ok()); + Some(Rendition { width: n("width")?, height: n("height")?, frame_rate_milli: n("frame_rate_milli")?, bit_depth: b("bit_depth")?, transfer: b("transfer").unwrap_or(TRANSFER_SDR) }) + } +} + +/// A rendition's exceedances against a declaration (capability-constraints §4.2, bits 0 to 3). +pub fn exceedances(r: &Rendition, d: &Declaration) -> u8 { + let mut x = 0; + if r.width.max(r.height) > d.long || r.width.min(r.height) > d.short { + x |= exceed::RESOLUTION; + } + if r.frame_rate_milli > d.refresh_milli { + x |= exceed::REFRESH; + } + if r.bit_depth > d.bit_depth { + x |= exceed::BIT_DEPTH; + } + if r.is_hdr() && !d.hdr { + x |= exceed::DYNAMIC_RANGE; + } + x +} + +/// The least rendition of a ladder: the first in the order of (`w` × `h`, `r`, `d`, HDR), +/// and of two equal there the one listed first. `None` for an empty ladder. +pub fn least(ladder: &[Rendition]) -> Option { + (0..ladder.len()).min_by_key(|&i| (ladder[i].order(), i)) +} + +/// The admissible renditions, by index: those within the declaration, or the least alone +/// where none is. +pub fn admissible(ladder: &[Rendition], d: &Declaration) -> Vec { + let within: Vec = (0..ladder.len()).filter(|&i| exceedances(&ladder[i], d) == 0).collect(); + if within.is_empty() { + least(ladder).into_iter().collect() + } else { + within + } +} + +/// The match a receipt records for rendition `i` of `ladder` (capability-constraints §4.2). +pub fn match_of(ladder: &[Rendition], i: usize, d: &Declaration) -> u8 { + let none_within = ladder.iter().all(|r| exceedances(r, d) != 0); + exceedances(&ladder[i], d) | if none_within { exceed::NONE_WITHIN } else { 0 } +} + +/// The rendition a sink chooses for a panel: the admissible one of the most pixels, then the +/// highest frame rate, depth and range, then listed first. A choice among the admissible ones is +/// the deployment's; this is the reference page's. +pub fn choose(ladder: &[Rendition], d: &Declaration) -> Option { + admissible(ladder, d).into_iter().max_by_key(|&i| (ladder[i].order(), std::cmp::Reverse(i))) +} + +// ─────────────────────────────── §3: the records ─────────────────────────────── + +/// A hop entry (§3.5). The class is kept as its encoded bytes (energy-measurement §2.7's tag +/// and evidence), so that a corpus can hold a class a reader refuses. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HopEntry { + pub role: u8, + pub joules: u64, + pub class: Vec, +} + +impl HopEntry { + /// A figure with its class, or the unmetered marker for `None` (with `joules` 0). + pub fn new(role: u8, joules: u64, class: Option<&EnergyProvenance>) -> HopEntry { + let mut c = Vec::new(); + crate::energy_class::write_optional(class, &mut c); + HopEntry { role, joules, class: c } + } + + /// The unmetered marker. + pub fn unmetered(role: u8) -> HopEntry { + HopEntry { role, joules: 0, class: vec![0] } + } + + pub fn tag(&self) -> u8 { + self.class.first().copied().unwrap_or(0) + } + + fn encode(&self, o: &mut Vec) { + o.push(self.role); + o.extend_from_slice(&self.joules.to_le_bytes()); + o.extend_from_slice(&self.class); + } + + /// live-metering §3.6's `energy_invalid` rules over the class as read (§3.9 step 2). + fn energy_ok(&self) -> bool { + match parse_class(&self.class) { + Some(None) => self.joules == 0, + Some(Some(p)) => self.joules != 0 && check_resolution(self.joules, &p).is_ok(), + None => false, + } + } +} + +/// A class's bytes as a class: `Some(None)` for the unmetered marker, `None` for one the rules +/// refuse. +fn parse_class(c: &[u8]) -> Option> { + let tag = *c.first()?; + match tag { + 0 => Some(None), + 2 => Some(Some(EnergyProvenance::ModelBased)), + 3 => Some(Some(EnergyProvenance::Estimator)), + 4 => { + let ppm = u32::from_be_bytes(c.get(1..5)?.try_into().ok()?); + let window = u64::from_be_bytes(c.get(5..13)?.try_into().ok()?); + let filtering = match c.get(13)? { + 0 => CounterFiltering::Unknown, + 1 => CounterFiltering::Off, + 2 => CounterFiltering::On, + _ => return None, + }; + EnergyProvenance::on_chip_counter(Uncertainty::new(ppm, window).ok()?, filtering).ok().map(Some) + } + 5 => { + let ppm = u32::from_be_bytes(c.get(1..5)?.try_into().ok()?); + let window = u64::from_be_bytes(c.get(5..13)?.try_into().ok()?); + let s = std::str::from_utf8(c.get(17..)?).ok()?; + EnergyProvenance::calibrated_instrument(Uncertainty::new(ppm, window).ok()?, s).ok().map(Some) + } + _ => None, + } +} + +/// The label of a class tag in the facts of §6.3. +pub fn tag_label(tag: u8) -> &'static str { + match tag { + 2 => "ModelBased", + 3 => "Estimator", + 4 => "OnChipCounter", + 5 => "CalibratedInstrument", + _ => "unmetered", + } +} + +/// The open record's body (§3.2). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Open { + pub stream: [u8; 32], + pub media: u8, + pub start: u8, + pub autoplay: u8, + pub ua_policy: u8, + pub unseen_grace_ms: u32, + pub idle_ms: u32, + pub autoplay_grace_ms: u32, + pub display: Declaration, + pub ladder: Vec, +} + +/// A segment receipt's body (§3.4). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Segment { + pub segment: [u8; 32], + pub objects: u32, + pub bytes: u64, + pub media_start: u64, + pub media_end: u64, + pub rendition: u8, + pub matched: u8, + pub hops: Vec, +} + +/// A total of a terminate record (§3.7). +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Total { + pub role: u8, + pub tag: u8, + pub figures: u32, + pub joules: u64, +} + +/// A terminate record's body (§3.7). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Terminate { + pub reason: u8, + pub media: u64, + pub segments: u32, + pub totals: Vec, + /// (role, count). + pub unmetered: Vec<(u8, u32)>, +} + +/// A record's body, by kind. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Body { + Open(Open), + Segment(Segment), + Observation { signal: u8, value: u8 }, + Display(Declaration), + Terminate(Terminate), +} + +impl Body { + pub fn kind(&self) -> u8 { + match self { + Body::Open(_) => kind::OPEN, + Body::Segment(_) => kind::SEGMENT, + Body::Observation { .. } => kind::OBSERVATION, + Body::Display(_) => kind::DISPLAY, + Body::Terminate(_) => kind::TERMINATE, + } + } + + fn encode(&self, o: &mut Vec) { + match self { + Body::Open(b) => { + o.extend_from_slice(&b.stream); + o.extend_from_slice(&[b.media, b.start, b.autoplay, b.ua_policy]); + o.extend_from_slice(&b.unseen_grace_ms.to_le_bytes()); + o.extend_from_slice(&b.idle_ms.to_le_bytes()); + o.extend_from_slice(&b.autoplay_grace_ms.to_le_bytes()); + if b.media == media::AUDIO && b.display == Declaration::default() { + o.extend_from_slice(&[0; DECLARATION_LEN]); + } else { + b.display.encode(o); + } + o.push(b.ladder.len() as u8); + for r in &b.ladder { + r.encode(o); + } + } + Body::Segment(s) => { + o.extend_from_slice(&s.segment); + o.extend_from_slice(&s.objects.to_le_bytes()); + o.extend_from_slice(&s.bytes.to_le_bytes()); + o.extend_from_slice(&s.media_start.to_le_bytes()); + o.extend_from_slice(&s.media_end.to_le_bytes()); + o.push(s.rendition); + o.push(s.matched); + o.push(s.hops.len() as u8); + for h in &s.hops { + h.encode(o); + } + } + Body::Observation { signal, value } => o.extend_from_slice(&[*signal, *value]), + Body::Display(d) => d.encode(o), + Body::Terminate(t) => { + o.push(t.reason); + o.extend_from_slice(&t.media.to_le_bytes()); + o.extend_from_slice(&t.segments.to_le_bytes()); + o.push(t.totals.len() as u8); + for x in &t.totals { + o.extend_from_slice(&[x.role, x.tag]); + o.extend_from_slice(&x.figures.to_le_bytes()); + o.extend_from_slice(&x.joules.to_le_bytes()); + } + o.push(t.unmetered.len() as u8); + for (r, c) in &t.unmetered { + o.push(*r); + o.extend_from_slice(&c.to_le_bytes()); + } + } + } + } +} + +/// A `WSL1` record (§3.1). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Record { + pub sink: [u8; 32], + pub seq: u32, + pub prev: [u8; 32], + pub t: u64, + pub body: Body, + pub sig: [u8; 64], +} + +impl Record { + /// An unsigned record; [`Record::sign`] signs it. + pub fn new(sink: [u8; 32], seq: u32, prev: [u8; 32], t: u64, body: Body) -> Record { + Record { sink, seq, prev, t, body, sig: [0; 64] } + } + + /// The bytes the digest covers: every field before `sig`. + pub fn signed_bytes(&self) -> Vec { + let mut o = Vec::with_capacity(160); + o.extend_from_slice(&MAGIC); + o.push(VERSION); + o.push(self.body.kind()); + o.extend_from_slice(&self.sink); + o.extend_from_slice(&self.seq.to_le_bytes()); + o.extend_from_slice(&self.prev); + o.extend_from_slice(&self.t.to_le_bytes()); + self.body.encode(&mut o); + o + } + + pub fn to_bytes(&self) -> Vec { + let mut o = self.signed_bytes(); + o.extend_from_slice(&self.sig); + o + } + + /// §3.8: SHA-256 over the bytes before `sig`. + pub fn digest(&self) -> [u8; 32] { + sha256(&[&self.signed_bytes()]) + } + + /// Signs with `key`, whose public half becomes `sink`. + pub fn sign(mut self, key: &SigningKey) -> Record { + self.sink = key.verifying_key().to_bytes(); + let mut m = DOMAIN.to_vec(); + m.extend_from_slice(&self.signed_bytes()); + self.sig = key.sign(&m).to_bytes(); + self + } + + /// §3.9: reads exactly `b` as one record, checks its rules and its signature. + pub fn parse(b: &[u8]) -> Result { + let (r, hdr_ok) = read_structure(b)?; + rules(&r, hdr_ok)?; + let mut m = DOMAIN.to_vec(); + m.extend_from_slice(&b[..b.len() - 64]); + crate::sig::verify_strict(&r.sink, &m, &r.sig).map_err(|e| match e { + crate::sig::SigError::BadKey => Reason::BadKey, + crate::sig::SigError::BadSignature => Reason::BadSignature, + })?; + Ok(r) + } +} + +/// A cursor; running out is `truncated`. +struct Rd<'a> { + b: &'a [u8], + p: usize, +} + +impl<'a> Rd<'a> { + fn take(&mut self, n: usize) -> Result<&'a [u8], Reason> { + let e = self.p.checked_add(n).filter(|&e| e <= self.b.len()).ok_or(Reason::Truncated)?; + let s = &self.b[self.p..e]; + self.p = e; + Ok(s) + } + fn arr(&mut self) -> Result<[u8; N], Reason> { + Ok(self.take(N)?.try_into().expect("N bytes")) + } + fn u8(&mut self) -> Result { + Ok(self.take(1)?[0]) + } + fn u32(&mut self) -> Result { + Ok(u32::from_le_bytes(self.arr()?)) + } + fn u64(&mut self) -> Result { + Ok(u64::from_le_bytes(self.arr()?)) + } +} + +/// A declaration, and whether its `hdr` byte is 0 or 1 (§3.9 step 3 refuses another). +fn read_declaration(r: &mut Rd) -> Result<(Declaration, bool), Reason> { + let (long, short, refresh_milli) = (r.u32()?, r.u32()?, r.u32()?); + let (bit_depth, hdr) = (r.u8()?, r.u8()?); + let (long_mm, short_mm) = (r.u32()?, r.u32()?); + Ok((Declaration { long, short, refresh_milli, bit_depth, hdr: hdr != 0, long_mm, short_mm }, hdr <= 1)) +} + +/// The class's bytes, read as energy-measurement §2.7 writes them (§3.9). +fn read_class(r: &mut Rd) -> Result, Reason> { + let tag = r.u8()?; + let mut c = vec![tag]; + match tag { + 4 => c.extend_from_slice(r.take(13)?), + 5 => { + let head = r.take(12)?; + let n = u32::from_be_bytes(r.arr()?); + if n as usize > MAX_CALIBRATION_REF { + return Err(Reason::Malformed); + } + c.extend_from_slice(head); + c.extend_from_slice(&n.to_be_bytes()); + c.extend_from_slice(r.take(n as usize)?); + } + _ => {} + } + Ok(c) +} + +/// The record as its bytes give it, before any rule but the structural ones of §3.9, and +/// whether its declaration's `hdr` byte, where it has one, is 0 or 1. +fn read_structure(b: &[u8]) -> Result<(Record, bool), Reason> { + if b.len() < 4 { + return Err(Reason::Truncated); + } + if b[..4] != MAGIC { + return Err(Reason::Malformed); + } + if b.len() < 5 { + return Err(Reason::Truncated); + } + if b[4] != VERSION { + return Err(Reason::UnknownVersion); + } + let mut r = Rd { b, p: 5 }; + let k = r.u8()?; + if !(kind::OPEN..=kind::TERMINATE).contains(&k) { + return Err(Reason::Malformed); + } + let sink = r.arr()?; + let seq = r.u32()?; + let prev = r.arr()?; + let t = r.u64()?; + let mut hdr_ok = true; + let body = match k { + kind::OPEN => { + let stream = r.arr()?; + let [media, start, autoplay, ua_policy] = r.arr::<4>()?; + let (unseen_grace_ms, idle_ms, autoplay_grace_ms) = (r.u32()?, r.u32()?, r.u32()?); + let (display, ok) = read_declaration(&mut r)?; + hdr_ok = ok; + let n = r.u8()? as usize; + let mut ladder = Vec::with_capacity(n); + for _ in 0..n { + let (width, height, frame_rate_milli) = (r.u32()?, r.u32()?, r.u32()?); + let (bit_depth, transfer) = (r.u8()?, r.u8()?); + ladder.push(Rendition { width, height, frame_rate_milli, bit_depth, transfer }); + } + Body::Open(Open { stream, media, start, autoplay, ua_policy, unseen_grace_ms, idle_ms, autoplay_grace_ms, display, ladder }) + } + kind::SEGMENT => { + let segment = r.arr()?; + let objects = r.u32()?; + let bytes = r.u64()?; + let (media_start, media_end) = (r.u64()?, r.u64()?); + let (rendition, matched) = (r.u8()?, r.u8()?); + let n = r.u8()? as usize; + let mut hops = Vec::with_capacity(n); + for _ in 0..n { + let role = r.u8()?; + let joules = r.u64()?; + let class = read_class(&mut r)?; + hops.push(HopEntry { role, joules, class }); + } + Body::Segment(Segment { segment, objects, bytes, media_start, media_end, rendition, matched, hops }) + } + kind::OBSERVATION => { + let [signal, value] = r.arr::<2>()?; + Body::Observation { signal, value } + } + kind::DISPLAY => { + let (d, ok) = read_declaration(&mut r)?; + hdr_ok = ok; + Body::Display(d) + } + _ => { + let reason = r.u8()?; + let media = r.u64()?; + let segments = r.u32()?; + let m = r.u8()? as usize; + let mut totals = Vec::with_capacity(m); + for _ in 0..m { + let [role, tag] = r.arr::<2>()?; + totals.push(Total { role, tag, figures: r.u32()?, joules: r.u64()? }); + } + let k = r.u8()? as usize; + let mut unmetered = Vec::with_capacity(k); + for _ in 0..k { + unmetered.push((r.u8()?, r.u32()?)); + } + Body::Terminate(Terminate { reason, media, segments, totals, unmetered }) + } + }; + let sig = r.arr()?; + if r.p != b.len() { + return Err(Reason::Malformed); + } + Ok((Record { sink, seq, prev, t, body, sig }, hdr_ok)) +} + +/// §3.9 steps 1 to 4; `hdr_ok`, whether the record's declaration's `hdr` byte is 0 or 1. +fn rules(r: &Record, hdr_ok: bool) -> Result<(), Reason> { + if let Body::Terminate(t) = &r.body { + if !(why::HIDDEN..=why::ENDED).contains(&t.reason) { + return Err(Reason::ReasonUnknown); + } + } + if let Body::Segment(s) = &r.body { + if !s.hops.iter().all(HopEntry::energy_ok) { + return Err(Reason::EnergyInvalid); + } + } + let decl_ok = |d: &Declaration| hdr_ok && Declaration::holds(d.long, d.short, d.refresh_milli, d.bit_depth, d.hdr as u8); + match &r.body { + Body::Open(o) if o.media == media::AUDIO => { + if o.display != Declaration::default() || !hdr_ok { + return Err(Reason::DisplayInvalid); + } + } + Body::Open(o) => { + if !decl_ok(&o.display) { + return Err(Reason::DisplayInvalid); + } + } + Body::Display(d) => { + if !decl_ok(d) { + return Err(Reason::DisplayInvalid); + } + } + _ => {} + } + let ok = match &r.body { + Body::Open(o) => { + [o.media, o.start, o.autoplay].iter().all(|v| (1..=2).contains(v)) + && o.ua_policy <= 3 + && if o.media == media::VIDEO { + (1..=MAX_LADDER).contains(&o.ladder.len()) + && o.ladder.iter().all(|x| x.width >= 1 && x.height >= 1 && x.frame_rate_milli >= 1 && (1..=16).contains(&x.bit_depth)) + } else { + o.ladder.is_empty() + } + } + Body::Segment(s) => { + s.objects >= 1 + && s.media_start < s.media_end + && s.matched & !exceed::ALL == 0 + && s.hops.len() <= MAX_HOPS + && s.hops.iter().all(|h| (role::ORIGIN..=role::DISPLAY).contains(&h.role)) + && s.hops.windows(2).all(|w| w[0].role < w[1].role) + } + Body::Observation { signal, value } => match *signal { + signal::VISIBILITY | signal::OCCLUSION | signal::DISPLAY_POWER => (1..=2).contains(value), + signal::INTERACTION => *value == 1, + _ => false, + }, + Body::Display(_) => true, + Body::Terminate(t) => { + t.totals.iter().all(|x| (1..=4).contains(&x.role) && (2..=5).contains(&x.tag) && x.figures >= 1) + && t.totals.windows(2).all(|w| (w[0].role, w[0].tag) < (w[1].role, w[1].tag)) + && t.unmetered.iter().all(|&(r, c)| (1..=4).contains(&r) && c >= 1) + && t.unmetered.windows(2).all(|w| w[0].0 < w[1].0) + } + }; + if ok { + Ok(()) + } else { + Err(Reason::RecordInvalid) + } +} + +// ─────────────────────────────── §6: verification ─────────────────────────────── + +/// Totals and unmetered counts by role and class (§3.7, §6.3). +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Sums { + /// (role, tag) → (figures, joules). + pub totals: std::collections::BTreeMap<(u8, u8), (u32, u128)>, + /// role → count. + pub unmetered: std::collections::BTreeMap, +} + +impl Sums { + fn add(&mut self, hops: &[HopEntry]) { + for h in hops { + if h.joules == 0 { + *self.unmetered.entry(h.role).or_default() += 1; + } else { + let e = self.totals.entry((h.role, h.tag())).or_default(); + e.0 += 1; + e.1 += h.joules as u128; + } + } + } + + /// Whether a terminate record states exactly these. + fn stated_by(&self, t: &Terminate) -> bool { + let totals: Vec<(u8, u8, u32, u128)> = self.totals.iter().map(|(&(r, g), &(f, j))| (r, g, f, j)).collect(); + let stated: Vec<(u8, u8, u32, u128)> = t.totals.iter().map(|x| (x.role, x.tag, x.figures, x.joules as u128)).collect(); + let unmetered: Vec<(u8, u32)> = self.unmetered.iter().map(|(&r, &c)| (r, c)).collect(); + totals == stated && unmetered == t.unmetered + } + + /// The terminate record's lists for these sums; `None` where a sum does not fit a u64. + pub fn to_terminate(&self, reason: u8, media: u64, segments: u32) -> Option { + let mut totals = Vec::new(); + for (&(role, tag), &(figures, joules)) in &self.totals { + totals.push(Total { role, tag, figures, joules: u64::try_from(joules).ok()? }); + } + Some(Terminate { reason, media, segments, totals, unmetered: self.unmetered.iter().map(|(&r, &c)| (r, c)).collect() }) + } + + pub fn to_json(&self) -> Value { + json!({ + "totals": self.totals.iter().map(|(&(r, g), &(f, j))| json!({ "role": role::name(r), "class": tag_label(g), "figures": f, "joules": j as u64 })).collect::>(), + "unmetered": self.unmetered.iter().map(|(&r, &c)| json!({ "role": role::name(r), "count": c })).collect::>(), + }) + } +} + +/// What an accepted log states (§6.3). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Accepted { + pub records: usize, + pub segments: u32, + pub media: u8, + /// The terminate record's reason and `media`, or `None` for an open log. + pub terminated: Option<(u8, u64)>, + pub sums: Sums, + pub absent: Vec, + /// Video only: (within, none_within). + pub panel: Option<(u32, u32)>, + /// Video only. + pub unseen_us: Option, + /// Video only: the unwatched segments and their sums. + pub unwatched: Option<(u32, Sums)>, +} + +impl Accepted { + pub fn to_json(&self) -> Value { + let sums = self.sums.to_json(); + json!({ + "outcome": "accept", + "records": self.records, + "segments": self.segments, + "media": if self.media == media::VIDEO { "video" } else { "audio" }, + "terminated": self.terminated.map(|(w, _)| json!(why::token(w))).unwrap_or(Value::Null), + "media_end": self.terminated.map(|(_, m)| json!(m)).unwrap_or(Value::Null), + "totals": sums["totals"], + "unmetered": sums["unmetered"], + "absent": self.absent.iter().map(|&r| role::name(r)).collect::>(), + "panel": self.panel.map(|(w, n)| json!({ "within": w, "none_within": n })).unwrap_or(Value::Null), + "unseen_us": self.unseen_us.map(|u| json!(u)).unwrap_or(Value::Null), + "unwatched": self.unwatched.as_ref().map(|(n, s)| { + let j = s.to_json(); + json!({ "segments": n, "totals": j["totals"], "unmetered": j["unmetered"] }) + }).unwrap_or(Value::Null), + }) + } +} + +/// The state of §6.2 as the records are read. +#[derive(Clone, Debug)] +struct State { + /// For visibility, occlusion and display power: (value of the last observation, since). + last: [Option<(u8, u64)>; 3], + t0: u64, + last_interaction: Option, + unseen_since: Option, + unseen_us: u64, +} + +impl State { + fn new(t0: u64) -> State { + State { last: [None; 3], t0, last_interaction: None, unseen_since: None, unseen_us: 0 } + } + + fn value(&self, s: u8) -> u8 { + self.last[s as usize - 1].map_or(signal::SEEN, |(v, _)| v) + } + + fn since(&self, s: u8) -> u64 { + self.last[s as usize - 1].map_or(self.t0, |(_, t)| t) + } + + fn unseen(&self) -> bool { + (1..=3).any(|s| self.value(s) == signal::UNSEEN) + } + + fn observe(&mut self, s: u8, v: u8, t: u64) { + if s == signal::INTERACTION { + self.last_interaction = Some(t); + return; + } + let was = self.unseen(); + let slot = &mut self.last[s as usize - 1]; + match slot { + Some((pv, _)) if *pv == v => {} + _ => *slot = Some((v, t)), + } + match (was, self.unseen()) { + (false, true) => self.unseen_since = Some(t), + (true, false) => { + self.unseen_us += t - self.unseen_since.take().expect("unseen since"); + } + _ => {} + } + } + + /// §6.2: whether the state at `t` supports `reason`. + fn supports(&self, open: &Open, reason: u8, t: u64) -> bool { + let grace = open.unseen_grace_ms as u64 * 1000; + let held = |s: u8| self.value(s) == signal::UNSEEN && t - self.since(s) >= grace; + match reason { + why::HIDDEN => held(signal::VISIBILITY), + why::OCCLUDED => held(signal::OCCLUSION), + why::DISPLAY_OFF => held(signal::DISPLAY_POWER), + why::IDLE => open.idle_ms > 0 && t - self.last_interaction.unwrap_or(self.t0) >= open.idle_ms as u64 * 1000, + why::AUTOPLAY_UNATTENDED => { + open.start == start::AUTOPLAY && open.autoplay_grace_ms > 0 && self.last_interaction.is_none() && t - self.t0 >= open.autoplay_grace_ms as u64 * 1000 + } + _ => true, + } + } +} + +/// §6.1: verifies a log, given its records' bytes in order. A refusal is the reason and the +/// record's index. +pub fn verify_log(records: &[Vec]) -> Result { + let mut open: Option = None; + let mut sink = [0u8; 32]; + let mut prev_digest = [0u8; 32]; + let mut prev_t = 0u64; + let mut terminated: Option<(u8, u64)> = None; + let mut state = State::new(0); + let mut declaration = Declaration::default(); + let mut last_segment: Option<(u64, u64)> = None; + let mut sums = Sums::default(); + let mut unwatched = (0u32, Sums::default()); + let mut panel = (0u32, 0u32); + let mut segments = 0u32; + let mut seen_roles = [false; 5]; + let mut last_t = 0u64; + for (i, b) in records.iter().enumerate() { + let at = |r: Reason| (r, i); + // Step 1. + let r = Record::parse(b).map_err(at)?; + // Step 2. + if terminated.is_some() { + return Err(at(Reason::AfterTermination)); + } + // Step 3. + let first = i == 0; + if (r.body.kind() == kind::OPEN) != first || r.seq as usize != i || r.prev != prev_digest || (!first && r.t < prev_t) { + return Err(at(Reason::OrderInvalid)); + } + if first { + sink = r.sink; + } else if r.sink != sink { + return Err(at(Reason::SinkChanged)); + } + prev_digest = r.digest(); + prev_t = r.t; + last_t = r.t; + match r.body { + Body::Open(o) => { + // Step 4. + if o.start == start::AUTOPLAY && (o.autoplay == setting::OFF || o.ua_policy == ua_policy::DISALLOWED) { + return Err(at(Reason::AutoplayAgainstSetting)); + } + state = State::new(r.t); + declaration = o.display; + open = Some(o); + } + Body::Display(d) => { + // Step 5. + if open.as_ref().expect("an open record first").media == media::AUDIO { + return Err(at(Reason::DisplayInvalid)); + } + declaration = d; + } + Body::Observation { signal, value } => state.observe(signal, value, r.t), + Body::Segment(s) => { + let o = open.as_ref().expect("an open record first"); + // Step 6.1. + if last_segment.is_some_and(|(_, end)| s.media_start < end) { + return Err(at(Reason::MediaOrderInvalid)); + } + if o.media == media::VIDEO { + // Steps 6.2 to 6.4. + let k = s.rendition as usize; + if k >= o.ladder.len() { + return Err(at(Reason::RenditionUnknown)); + } + if s.matched != match_of(&o.ladder, k, &declaration) { + return Err(at(Reason::MatchMismatch)); + } + if !admissible(&o.ladder, &declaration).contains(&k) { + return Err(at(Reason::RenditionAbovePanel)); + } + if s.matched == 0 { + panel.0 += 1; + } + if s.matched & exceed::NONE_WITHIN != 0 { + panel.1 += 1; + } + let grace = o.unseen_grace_ms as u64 * 1000; + if state.unseen() && r.t - state.unseen_since.expect("unseen since") >= grace { + unwatched.0 += 1; + unwatched.1.add(&s.hops); + } + } else { + if s.rendition != NO_RENDITION { + return Err(at(Reason::RenditionUnknown)); + } + if s.matched != 0 { + return Err(at(Reason::MatchMismatch)); + } + } + for h in &s.hops { + seen_roles[h.role as usize] = true; + } + sums.add(&s.hops); + segments += 1; + last_segment = Some((s.media_start, s.media_end)); + } + Body::Terminate(t) => { + let o = open.as_ref().expect("an open record first"); + // Step 7.1. + if last_segment.is_some_and(|(a, z)| t.media < a || t.media > z) { + return Err(at(Reason::MediaOrderInvalid)); + } + // Step 7.2. + if o.media == media::AUDIO && [why::HIDDEN, why::OCCLUDED, why::DISPLAY_OFF].contains(&t.reason) { + return Err(at(Reason::ReasonNotApplicable)); + } + // Step 7.3. + if !state.supports(o, t.reason, r.t) { + return Err(at(Reason::ReasonUnsupported)); + } + // Step 7.4. + if t.segments != segments || !sums.stated_by(&t) { + return Err(at(Reason::TotalsMismatch)); + } + terminated = Some((t.reason, t.media)); + } + } + } + let Some(o) = open else { + // §6.1 step 3: a log of no record has no open record. + return Err((Reason::OrderInvalid, 0)); + }; + let video = o.media == media::VIDEO; + let unseen_us = video.then(|| state.unseen_us + state.unseen_since.map_or(0, |s| last_t - s)); + Ok(Accepted { + records: records.len(), + segments, + media: o.media, + terminated, + sums, + absent: (role::ORIGIN..=role::DISPLAY).filter(|&r| !seen_roles[r as usize]).collect(), + panel: video.then_some(panel), + unseen_us, + unwatched: video.then_some(unwatched), + }) +} + +/// [`verify_log`]'s outcome as the corpus states it: the facts of §6.3, or +/// `{"outcome":"refuse","reason":…,"at":…}`. +pub fn outcome_json(records: &[Vec]) -> Value { + match verify_log(records) { + Ok(a) => a.to_json(), + Err((r, at)) => json!({ "outcome": "refuse", "reason": r.code(), "at": at }), + } +} + +// ─────────────────────────────── a sink's writer ─────────────────────────────── + +/// A sink's log as it writes it: each record chained to the one before it and signed, the +/// sums kept as segments are receipted so that a terminate record states them. +pub struct Writer { + key: SigningKey, + records: Vec>, + prev: [u8; 32], + sums: Sums, + segments: u32, + terminated: bool, +} + +impl Writer { + /// Opens a log with its open record at `t`. + pub fn open(key: SigningKey, t: u64, open: Open) -> Writer { + let mut w = Writer { key, records: Vec::new(), prev: [0; 32], sums: Sums::default(), segments: 0, terminated: false }; + w.push(t, Body::Open(open)); + w + } + + fn push(&mut self, t: u64, body: Body) -> &[u8] { + let rec = Record::new([0; 32], self.records.len() as u32, self.prev, t, body).sign(&self.key); + self.prev = rec.digest(); + self.records.push(rec.to_bytes()); + self.records.last().expect("pushed") + } + + /// The records written so far. + pub fn records(&self) -> &[Vec] { + &self.records + } + + pub fn is_terminated(&self) -> bool { + self.terminated + } + + /// A segment receipt; refused once the log is terminated (§4.3). + pub fn segment(&mut self, t: u64, s: Segment) -> Result<&[u8], Reason> { + if self.terminated { + return Err(Reason::AfterTermination); + } + self.sums.add(&s.hops); + self.segments += 1; + Ok(self.push(t, Body::Segment(s))) + } + + pub fn observe(&mut self, t: u64, signal: u8, value: u8) -> Result<&[u8], Reason> { + if self.terminated { + return Err(Reason::AfterTermination); + } + Ok(self.push(t, Body::Observation { signal, value })) + } + + pub fn display(&mut self, t: u64, d: Declaration) -> Result<&[u8], Reason> { + if self.terminated { + return Err(Reason::AfterTermination); + } + Ok(self.push(t, Body::Display(d))) + } + + /// The terminate record, with the totals of the segments receipted. + pub fn terminate(&mut self, t: u64, reason: u8, media: u64) -> Result<&[u8], Reason> { + if self.terminated { + return Err(Reason::AfterTermination); + } + let body = self.sums.to_terminate(reason, media, self.segments).ok_or(Reason::TotalsMismatch)?; + self.terminated = true; + Ok(self.push(t, Body::Terminate(body))) + } +} diff --git a/wai/wai-rs/src/stream_termination/corpus.rs b/wai/wai-rs/src/stream_termination/corpus.rs new file mode 100644 index 000000000..c9e304f5c --- /dev/null +++ b/wai/wai-rs/src/stream_termination/corpus.rs @@ -0,0 +1,755 @@ +//! The conformance corpus of extensions/stream-termination.md §8: +//! `live-conformance/termination/vectors.json`. +//! +//! [`generate`] writes it from fixed seeds: every key is SHA-256 of this corpus's label and an +//! index, every stream and segment digest SHA-256 of another label and a name, every time a +//! constant, and Ed25519 signing is deterministic (RFC 8032). The panels, ladders, timings and +//! joule figures are synthetic, chosen to exercise the rules, and claim nothing about any +//! device or deployment. Each log states the outcome it was written to have, and the generator +//! asserts that this module reaches it. +//! +//! [`verify`] checks a corpus file against this module. `live-conformance/stream_termination_verify.py` +//! checks the same file from the extension's text, sharing no code with this module. + +use super::*; +use serde_json::{json, Map, Value}; + +const S: u64 = 1_000_000; +const MS: u64 = 1_000; +/// 2026-10-16T00:00:00Z, as the live corpora's `t0`. +const T0: u64 = 1_792_108_800_000_000; + +const SINK: u8 = 0; +const OTHER: u8 = 1; +const AUDIO_SINK: u8 = 2; + +fn key(i: u8) -> SigningKey { + SigningKey::from_bytes(&sha256(&[b"wai:stream-termination/corpus/key\x00", &[i]])) +} + +fn stream(name: &str) -> [u8; 32] { + sha256(&[b"wai:stream-termination/corpus/stream\x00", name.as_bytes()]) +} + +fn segment_digest(name: &str, k: u32) -> [u8; 32] { + sha256(&[b"wai:stream-termination/corpus/segment\x00", name.as_bytes(), &k.to_le_bytes()]) +} + +fn hex(b: &[u8]) -> String { + b.iter().map(|x| format!("{x:02x}")).collect() +} + +/// The ladder most logs are offered: 360p, 720p and 1080p at 30, and a 2160p60 10-bit PQ. +fn ladder() -> Vec { + vec![ + Rendition::new(640, 360, 30_000, 8, TRANSFER_SDR), + Rendition::new(1280, 720, 30_000, 8, TRANSFER_SDR), + Rendition::new(1920, 1080, 30_000, 8, TRANSFER_SDR), + Rendition::new(3840, 2160, 60_000, 10, TRANSFER_PQ), + ] +} + +/// A 1080p60 8-bit standard-dynamic-range panel, 1210 × 680 mm. +fn tv() -> Declaration { + Declaration { long: 1920, short: 1080, refresh_milli: 60_000, bit_depth: 8, hdr: false, long_mm: 1210, short_mm: 680 } +} + +/// A 2160p120 10-bit high-dynamic-range panel. +fn hdr_tv() -> Declaration { + Declaration { long: 3840, short: 2160, refresh_milli: 120_000, bit_depth: 10, hdr: true, long_mm: 1440, short_mm: 810 } +} + +/// A 720p60 panel, as a page declares one: no physical size. +fn laptop() -> Declaration { + Declaration::panel(1280, 720, 60_000, 8, false) +} + +fn ladder_json(l: &[Rendition]) -> Value { + Value::Array(l.iter().map(Rendition::to_json).collect()) +} + +struct Policy { + start: u8, + autoplay: u8, + ua_policy: u8, + unseen_grace_ms: u32, + idle_ms: u32, + autoplay_grace_ms: u32, +} + +const WATCHED: Policy = Policy { start: start::USER, autoplay: setting::ON, ua_policy: ua_policy::ALLOWED, unseen_grace_ms: 10_000, idle_ms: 0, autoplay_grace_ms: 0 }; + +fn video_open(name: &str, p: &Policy, display: Declaration, ladder: Vec) -> Open { + Open { + stream: stream(name), + media: media::VIDEO, + start: p.start, + autoplay: p.autoplay, + ua_policy: p.ua_policy, + unseen_grace_ms: p.unseen_grace_ms, + idle_ms: p.idle_ms, + autoplay_grace_ms: p.autoplay_grace_ms, + display, + ladder, + } +} + +fn audio_open(name: &str) -> Open { + Open { media: media::AUDIO, display: Declaration::default(), ladder: Vec::new(), ..video_open(name, &WATCHED, tv(), Vec::new()) } +} + +fn counter(ppm: u32) -> EnergyProvenance { + EnergyProvenance::on_chip_counter(Uncertainty::new(ppm, 2 * S).expect("an uncertainty"), CounterFiltering::Unknown).expect("a counter") +} + +fn calibrated() -> EnergyProvenance { + EnergyProvenance::calibrated_instrument(Uncertainty::new(4_000, 2 * S).expect("an uncertainty"), "cal:corpus-meter-0001").expect("an instrument") +} + +/// The hops of most segments: the origin unmetered, the sink's decode on its counter, the +/// display as a stated estimate; the relay absent. +fn hops(k: u32) -> Vec { + vec![ + HopEntry::unmetered(role::ORIGIN), + HopEntry::new(role::SINK_DECODE, 180_000 + 1_000 * k as u64, Some(&counter(25_000))), + HopEntry::new(role::DISPLAY, 1_400_000, Some(&EnergyProvenance::Estimator)), + ] +} + +/// One log as it is written, its records kept unsigned until [`Log::bytes`] chains and signs +/// them, so that a control can change a field and still be signed. +struct Log { + name: String, + items: Vec, +} + +struct Item { + t: u64, + body: Body, + seq: Option, + prev: Option<[u8; 32]>, + key: u8, +} + +impl Log { + fn new(name: &str, t: u64, open: Open) -> Log { + let mut l = Log { name: name.into(), items: Vec::new() }; + let k = if open.media == media::AUDIO { AUDIO_SINK } else { SINK }; + l.items.push(Item { t: T0 + t, body: Body::Open(open), seq: None, prev: None, key: k }); + l + } + + fn open(&self) -> &Open { + match &self.items[0].body { + Body::Open(o) => o, + _ => unreachable!("the first item is the open record"), + } + } + + fn push(&mut self, t: u64, body: Body) -> &mut Log { + let key = self.items[0].key; + self.items.push(Item { t: T0 + t, body, seq: None, prev: None, key }); + self + } + + /// Segment `k` of rendition `r`, its match the honest one under the declaration in effect, + /// over media [`k` × `len`, (`k` + 1) × `len`). + fn seg(&mut self, t: u64, k: u32, r: u8, len: u64, h: Vec) -> &mut Log { + let matched = if self.open().media == media::VIDEO { match_of(&self.open().ladder, r as usize, &self.declaration()) } else { 0 }; + let s = Segment { + segment: segment_digest(&self.name, k), + objects: (len / (S / 30)) as u32, + bytes: 250_000 * len / S, + media_start: k as u64 * len, + media_end: (k as u64 + 1) * len, + rendition: r, + matched, + hops: h, + }; + self.push(t, Body::Segment(s)) + } + + fn obs(&mut self, t: u64, s: u8, v: u8) -> &mut Log { + self.push(t, Body::Observation { signal: s, value: v }) + } + + fn display(&mut self, t: u64, d: Declaration) -> &mut Log { + self.push(t, Body::Display(d)) + } + + /// The terminate record, its totals those of the segments pushed. + fn term(&mut self, t: u64, reason: u8, media: u64) -> &mut Log { + let mut sums = Sums::default(); + let mut n = 0; + for i in &self.items { + if let Body::Segment(s) = &i.body { + sums.add(&s.hops); + n += 1; + } + } + let body = sums.to_terminate(reason, media, n).expect("sums fit"); + self.push(t, Body::Terminate(body)) + } + + /// The declaration in effect after the last item. + fn declaration(&self) -> Declaration { + self.items + .iter() + .rev() + .find_map(|i| match &i.body { + Body::Display(d) => Some(*d), + Body::Open(o) => Some(o.display), + _ => None, + }) + .expect("an open record") + } + + fn last(&mut self) -> &mut Item { + self.items.last_mut().expect("an item") + } + + /// Every record, chained and signed in order. + fn records(&self) -> Vec { + let mut out: Vec = Vec::new(); + let mut prev = [0u8; 32]; + for (i, it) in self.items.iter().enumerate() { + let rec = Record::new([0; 32], it.seq.unwrap_or(i as u32), it.prev.unwrap_or(prev), it.t, it.body.clone()).sign(&key(it.key)); + prev = rec.digest(); + out.push(rec); + } + out + } + + fn bytes(&self) -> Vec> { + self.records().iter().map(Record::to_bytes).collect() + } +} + +/// A log and the outcome it was written to have. +struct Case { + name: String, + about: &'static str, + records: Vec>, + want: Option, +} + +fn case(log: &Log, about: &'static str, want: Option) -> Case { + Case { name: log.name.clone(), about, records: log.bytes(), want } +} + +fn raw(name: &str, about: &'static str, records: Vec>, want: Option) -> Case { + Case { name: name.into(), about, records, want } +} + +/// The headline log: watched, hidden at 5 s, a segment within the grace, one past it, and +/// the termination as hidden at 15.8 s. +fn hidden_log(name: &str) -> Log { + let mut l = Log::new(name, 0, video_open(name, &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)).seg(4 * S, 1, 2, 2 * S, hops(1)).obs(5 * S, signal::VISIBILITY, signal::UNSEEN); + l.seg(6 * S, 2, 2, 2 * S, hops(2)).seg(15_500 * MS, 3, 2, 2 * S, hops(3)); + l +} + +fn accepted() -> Vec { + let mut out = Vec::new(); + + let mut l = hidden_log("hidden"); + l.term(15_800 * MS, why::HIDDEN, 7 * S); + out.push(case(&l, "a watched video stream whose tab goes to the background at 5 s: a segment within the 10 s grace, one past it (unwatched), and the termination as hidden at 15.8 s; its origin unmetered, its decode on a counter, its display a stated estimate, its relay absent", None)); + + let p = Policy { unseen_grace_ms: 5_000, ..WATCHED }; + let mut l = Log::new("occluded", 0, video_open("occluded", &p, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)).obs(3 * S, signal::OCCLUSION, signal::UNSEEN).seg(4 * S, 1, 2, 2 * S, hops(1)).term(8 * S, why::OCCLUDED, 3 * S); + out.push(case(&l, "the presenting element scrolled out of the viewport at 3 s; terminated as occluded at 8 s, its 5 s grace run out", None)); + + let p = Policy { unseen_grace_ms: 2_000, ..WATCHED }; + let mut l = Log::new("display_off", 0, video_open("display_off", &p, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)).obs(3 * S, signal::DISPLAY_POWER, signal::UNSEEN).obs(3 * S, signal::VISIBILITY, signal::UNSEEN).term(5 * S, why::DISPLAY_OFF, S); + out.push(case(&l, "the display sleeps at 3 s and the document goes hidden with it; terminated as display off at 5 s, after its 2 s grace", None)); + + let p = Policy { idle_ms: 30_000, ..WATCHED }; + let mut l = Log::new("idle", 0, video_open("idle", &p, tv(), ladder())); + l.obs(S, signal::INTERACTION, 1).obs(4 * S, signal::INTERACTION, 1); + l.seg(10 * S, 0, 2, 10 * S, hops(0)).seg(20 * S, 1, 2, 10 * S, hops(1)).seg(30 * S, 2, 2, 10 * S, hops(2)).term(34 * S, why::IDLE, 30 * S); + out.push(case(&l, "interactions at 1 s and 4 s, then none: terminated as idle at 34 s, its 30 s timeout run out; the segments are 10 s spans of a MoQ track", None)); + + let p = Policy { start: start::AUTOPLAY, ua_policy: ua_policy::ALLOWED_MUTED, autoplay_grace_ms: 20_000, ..WATCHED }; + let mut l = Log::new("autoplay_unattended", 0, video_open("autoplay_unattended", &p, tv(), ladder())); + for k in 0..10 { + l.seg(2 * (k as u64 + 1) * S, k, 1, 2 * S, hops(k)); + } + l.term(20 * S, why::AUTOPLAY_UNATTENDED, 20 * S); + out.push(case(&l, "started by autoplay, the viewer's setting on and the platform's answer allowed-muted; no interaction in 20 s, terminated as autoplayed with no interaction", None)); + + let mut l = Log::new("user_stop", 0, video_open("user_stop", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, vec![HopEntry::new(role::ORIGIN, 900_000, Some(&EnergyProvenance::ModelBased)), HopEntry::new(role::SINK_DECODE, 210_000, Some(&calibrated()))]); + l.seg(4 * S, 1, 2, 2 * S, vec![HopEntry::unmetered(role::ORIGIN), HopEntry::new(role::SINK_DECODE, 190_000, Some(&counter(25_000)))]); + l.seg(6 * S, 2, 2, 2 * S, vec![HopEntry::unmetered(role::ORIGIN), HopEntry::new(role::SINK_DECODE, 200_000, Some(&counter(25_000)))]); + l.term(6_500 * MS, why::USER, 5_500 * MS); + out.push(case(&l, "stopped by the viewer: the sink decode's figures under two classes, a total for each and none across them; the origin once modelled and twice unmetered; relay and display absent", None)); + + let mut l = Log::new("audio_hidden", 0, audio_open("audio_hidden")); + let ah = || vec![HopEntry::unmetered(role::ORIGIN), HopEntry::unmetered(role::SINK_DECODE)]; + l.seg(2 * S, 0, NO_RENDITION, 2 * S, ah()).obs(3 * S, signal::VISIBILITY, signal::UNSEEN); + for k in 1..6 { + l.seg(2 * (k as u64 + 1) * S, k, NO_RENDITION, 2 * S, ah()); + } + l.term(12 * S, why::ENDED, 12 * S); + out.push(case(&l, "an audio stream goes on while its document is hidden, which hidden does not end, and ends with the presentation", None)); + + let mut l = Log::new("open", 0, video_open("open", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 1, 2 * S, hops(0)).seg(4 * S, 1, 2, 2 * S, hops(1)); + out.push(case(&l, "a log with no terminate record: accepted as open", None)); + + let small = Declaration::panel(360, 640, 60_000, 8, false); + let ladder2 = vec![Rendition::new(854, 480, 30_000, 8, TRANSFER_SDR), Rendition::new(1280, 720, 30_000, 8, TRANSFER_SDR)]; + let mut l = Log::new("none_within", 0, video_open("none_within", &WATCHED, small, ladder2)); + l.seg(2 * S, 0, 0, 2 * S, hops(0)).seg(4 * S, 1, 0, 2 * S, hops(1)).term(5 * S, why::ENDED, 4 * S); + out.push(case(&l, "a 640 × 360 panel declared in portrait, and a ladder whose least rendition is 854 × 480: served the least, its match recording the resolution and none within", None)); + + let mut l = Log::new("display_change", 0, video_open("display_change", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)).display(3 * S, Declaration::panel(720, 1280, 60_000, 8, false)).seg(4 * S, 1, 1, 2 * S, hops(1)).term(5 * S, why::ENDED, 4 * S); + out.push(case(&l, "the window moves to a 720p panel, declared in portrait, at 3 s: the sink moves to the 720p rendition, within the new declaration", None)); + + let mut l = Log::new("hdr", 0, video_open("hdr", &WATCHED, hdr_tv(), ladder())); + l.seg(2 * S, 0, 3, 2 * S, hops(0)).seg(4 * S, 1, 3, 2 * S, hops(1)).term(5 * S, why::USER, 3 * S); + out.push(case(&l, "a 2160p120 10-bit high-dynamic-range panel served the 2160p60 10-bit PQ rendition, within it", None)); + out +} + +/// A copy of `log` with its record `i`'s bytes changed by `f`. +fn changed(log: &Log, i: usize, f: impl FnOnce(&mut Vec)) -> Vec> { + let mut b = log.bytes(); + f(&mut b[i]); + b +} + +/// Record `i` of `log` re-signed after `f` changes its bytes before the signature. +fn resigned(log: &Log, i: usize, f: impl FnOnce(&mut Vec)) -> Vec> { + let mut b = log.bytes(); + let n = b[i].len() - 64; + let mut body = b[i][..n].to_vec(); + f(&mut body); + let mut m = DOMAIN.to_vec(); + m.extend_from_slice(&body); + let sig = key(log.items[i].key).sign(&m).to_bytes(); + body.extend_from_slice(&sig); + b[i] = body; + b +} + +fn refused() -> Vec { + let mut out = Vec::new(); + let base = || { + let mut l = Log::new("base", 0, video_open("base", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)).seg(4 * S, 1, 2, 2 * S, hops(1)).term(5 * S, why::USER, 3 * S); + l + }; + + // §6.1 step 2: the headline. + let mut l = hidden_log("segment_after_termination"); + l.term(15_800 * MS, why::HIDDEN, 7 * S).seg(17_500 * MS, 4, 2, 2 * S, hops(4)); + out.push(case(&l, "a stream terminated as hidden, then a segment receipt chained after the termination", Some(Reason::AfterTermination))); + let mut l = base(); + l.name = "observation_after_termination".into(); + l.obs(6 * S, signal::INTERACTION, 1); + out.push(case(&l, "any record after a terminate record", Some(Reason::AfterTermination))); + + // §6.1 step 7.3. + let mut l = Log::new("hidden_before_grace", 0, video_open("hidden_before_grace", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)).obs(5 * S, signal::VISIBILITY, signal::UNSEEN).seg(6 * S, 1, 2, 2 * S, hops(1)).term(14_999_999, why::HIDDEN, 3 * S); + out.push(case(&l, "terminated as hidden 1 µs before its 10 s grace ran out", Some(Reason::ReasonUnsupported))); + let mut l = Log::new("hidden_while_visible", 0, video_open("hidden_while_visible", &WATCHED, tv(), ladder())); + l.obs(3 * S, signal::VISIBILITY, signal::UNSEEN).obs(5 * S, signal::VISIBILITY, signal::SEEN).seg(6 * S, 0, 2, 2 * S, hops(0)).term(20 * S, why::HIDDEN, S); + out.push(case(&l, "hidden at 3 s, visible again at 5 s, and terminated as hidden at 20 s", Some(Reason::ReasonUnsupported))); + let mut l = Log::new("occluded_never", 0, video_open("occluded_never", &WATCHED, tv(), ladder())); + l.obs(3 * S, signal::VISIBILITY, signal::UNSEEN).seg(6 * S, 0, 2, 2 * S, hops(0)).term(20 * S, why::OCCLUDED, S); + out.push(case(&l, "hidden, but terminated as occluded, which no observation states", Some(Reason::ReasonUnsupported))); + let mut l = Log::new("display_off_never", 0, video_open("display_off_never", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)).term(30 * S, why::DISPLAY_OFF, S); + out.push(case(&l, "terminated as display off with no observation of the display", Some(Reason::ReasonUnsupported))); + let mut l = Log::new("idle_no_policy", 0, video_open("idle_no_policy", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)).term(600 * S, why::IDLE, S); + out.push(case(&l, "terminated as idle under an idle timeout of 0, never", Some(Reason::ReasonUnsupported))); + let p = Policy { idle_ms: 30_000, ..WATCHED }; + let mut l = Log::new("idle_too_soon", 0, video_open("idle_too_soon", &p, tv(), ladder())); + l.obs(5 * S, signal::INTERACTION, 1).seg(10 * S, 0, 2, 10 * S, hops(0)).term(34 * S, why::IDLE, 5 * S); + out.push(case(&l, "terminated as idle 29 s after the last interaction, under a 30 s timeout", Some(Reason::ReasonUnsupported))); + let p = Policy { start: start::AUTOPLAY, autoplay_grace_ms: 20_000, ..WATCHED }; + let mut l = Log::new("autoplay_after_interaction", 0, video_open("autoplay_after_interaction", &p, tv(), ladder())); + l.obs(3 * S, signal::INTERACTION, 1).seg(4 * S, 0, 1, 2 * S, hops(0)).term(25 * S, why::AUTOPLAY_UNATTENDED, S); + out.push(case(&l, "started by autoplay, then an interaction, and terminated as autoplayed with no interaction", Some(Reason::ReasonUnsupported))); + let p = Policy { autoplay_grace_ms: 20_000, ..WATCHED }; + let mut l = Log::new("autoplay_started_by_user", 0, video_open("autoplay_started_by_user", &p, tv(), ladder())); + l.seg(4 * S, 0, 1, 2 * S, hops(0)).term(25 * S, why::AUTOPLAY_UNATTENDED, S); + out.push(case(&l, "started by a user activation, and terminated as autoplayed with no interaction", Some(Reason::ReasonUnsupported))); + + // §6.1 step 7.2. + let mut l = Log::new("audio_terminated_hidden", 0, audio_open("audio_terminated_hidden")); + l.obs(S, signal::VISIBILITY, signal::UNSEEN).seg(2 * S, 0, NO_RENDITION, 2 * S, vec![HopEntry::unmetered(role::SINK_DECODE)]).term(30 * S, why::HIDDEN, S); + out.push(case(&l, "an audio stream terminated as hidden", Some(Reason::ReasonNotApplicable))); + + // §6.1 step 6. + let mut l = Log::new("above_panel", 0, video_open("above_panel", &WATCHED, laptop(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)); + out.push(case(&l, "a 720p panel sent the 1080p rendition while the 720p one was offered; its match recorded honestly", Some(Reason::RenditionAbovePanel))); + let small = Declaration::panel(360, 640, 60_000, 8, false); + let ladder2 = vec![Rendition::new(854, 480, 30_000, 8, TRANSFER_SDR), Rendition::new(1280, 720, 30_000, 8, TRANSFER_SDR)]; + let mut l = Log::new("not_the_least", 0, video_open("not_the_least", &WATCHED, small, ladder2)); + l.seg(2 * S, 0, 1, 2 * S, hops(0)); + out.push(case(&l, "no rendition within the panel, and one sent that is not the least", Some(Reason::RenditionAbovePanel))); + let mut l = Log::new("hdr_to_sdr_panel", 0, video_open("hdr_to_sdr_panel", &WATCHED, Declaration::panel(3840, 2160, 60_000, 10, false), ladder())); + l.seg(2 * S, 0, 3, 2 * S, hops(0)); + out.push(case(&l, "a PQ rendition sent to a panel that presents standard dynamic range, while SDR renditions were offered", Some(Reason::RenditionAbovePanel))); + let mut l = Log::new("match_understated", 0, video_open("match_understated", &WATCHED, laptop(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)); + if let Body::Segment(s) = &mut l.last().body { + s.matched = 0; + } + out.push(case(&l, "the 1080p rendition on a 720p panel, its match written as within", Some(Reason::MatchMismatch))); + let mut l = Log::new("match_after_display_change", 0, video_open("match_after_display_change", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)).display(3 * S, laptop()); + let before = l.open().display; + l.seg(4 * S, 1, 1, 2 * S, hops(1)); + if let Body::Segment(s) = &mut l.last().body { + s.matched = match_of(&ladder(), 1, &before) | exceed::BIT_DEPTH; + } + out.push(case(&l, "a segment's match not computed against the declaration in effect after a display record", Some(Reason::MatchMismatch))); + let mut l = Log::new("rendition_out_of_ladder", 0, video_open("rendition_out_of_ladder", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)); + if let Body::Segment(s) = &mut l.last().body { + s.rendition = 4; + } + out.push(case(&l, "a segment of rendition 4 of a four-rendition ladder", Some(Reason::RenditionUnknown))); + let mut l = Log::new("audio_rendition", 0, audio_open("audio_rendition")); + l.seg(2 * S, 0, 0, 2 * S, vec![HopEntry::unmetered(role::SINK_DECODE)]); + out.push(case(&l, "an audio segment naming rendition 0", Some(Reason::RenditionUnknown))); + let mut l = Log::new("audio_match", 0, audio_open("audio_match")); + l.seg(2 * S, 0, NO_RENDITION, 2 * S, vec![HopEntry::unmetered(role::SINK_DECODE)]); + if let Body::Segment(s) = &mut l.last().body { + s.matched = exceed::RESOLUTION; + } + out.push(case(&l, "an audio segment with a match", Some(Reason::MatchMismatch))); + + // §6.1 steps 6.1, 7.1. + let mut l = Log::new("segments_overlap", 0, video_open("segments_overlap", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)).seg(4 * S, 1, 2, 2 * S, hops(1)); + if let Body::Segment(s) = &mut l.last().body { + s.media_start = 1_999_999; + } + out.push(case(&l, "a segment starting 1 µs before the one before it ends", Some(Reason::MediaOrderInvalid))); + let mut l = Log::new("terminate_media_outside", 0, video_open("terminate_media_outside", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)).seg(4 * S, 1, 2, 2 * S, hops(1)).term(5 * S, why::USER, 4 * S + 1); + out.push(case(&l, "a termination's media time past the last segment's end", Some(Reason::MediaOrderInvalid))); + + // §6.1 step 4. + let p = Policy { start: start::AUTOPLAY, autoplay: setting::OFF, ..WATCHED }; + let l = Log::new("autoplay_setting_off", 0, video_open("autoplay_setting_off", &p, tv(), ladder())); + out.push(case(&l, "started by autoplay while the viewer's autoplay setting is off", Some(Reason::AutoplayAgainstSetting))); + let p = Policy { start: start::AUTOPLAY, ua_policy: ua_policy::DISALLOWED, ..WATCHED }; + let l = Log::new("autoplay_disallowed", 0, video_open("autoplay_disallowed", &p, tv(), ladder())); + out.push(case(&l, "started by autoplay while the platform answered disallowed", Some(Reason::AutoplayAgainstSetting))); + + // §6.1 step 7.4. + let mut l = Log::new("totals_across_classes", 0, video_open("totals_across_classes", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, vec![HopEntry::new(role::SINK_DECODE, 210_000, Some(&calibrated()))]); + l.seg(4 * S, 1, 2, 2 * S, vec![HopEntry::new(role::SINK_DECODE, 190_000, Some(&counter(25_000)))]).term(5 * S, why::USER, 3 * S); + if let Body::Terminate(t) = &mut l.last().body { + t.totals = vec![Total { role: role::SINK_DECODE, tag: 4, figures: 2, joules: 400_000 }]; + } + out.push(case(&l, "the sink decode's counter and instrument figures summed into one total", Some(Reason::TotalsMismatch))); + let mut l = base(); + l.name = "totals_wrong_sum".into(); + if let Body::Terminate(t) = &mut l.last().body { + t.totals[0].joules += 1; + } + out.push(case(&l, "a total 1 µJ above its figures' sum", Some(Reason::TotalsMismatch))); + let mut l = base(); + l.name = "unmetered_count_wrong".into(); + if let Body::Terminate(t) = &mut l.last().body { + t.unmetered[0].1 = 1; + } + out.push(case(&l, "the origin's unmetered count one short", Some(Reason::TotalsMismatch))); + let mut l = base(); + l.name = "unmetered_as_absent".into(); + if let Body::Terminate(t) = &mut l.last().body { + t.unmetered.clear(); + } + out.push(case(&l, "the unmetered origin left out of the termination, as if it were absent", Some(Reason::TotalsMismatch))); + let mut l = base(); + l.name = "segments_count_wrong".into(); + if let Body::Terminate(t) = &mut l.last().body { + t.segments = 3; + } + out.push(case(&l, "a termination stating three segment receipts of two", Some(Reason::TotalsMismatch))); + + // §6.1 step 3. + let mut l = base(); + l.name = "seq_gap".into(); + l.items[2].seq = Some(3); + out.push(case(&l, "a record whose seq is not its index", Some(Reason::OrderInvalid))); + let mut l = base(); + l.name = "prev_wrong".into(); + l.items[2].prev = Some([7; 32]); + out.push(case(&l, "a record whose prev is not the digest of the one before it", Some(Reason::OrderInvalid))); + let mut l = base(); + l.name = "time_backwards".into(); + l.items[2].t = l.items[1].t - 1; + out.push(case(&l, "a record made 1 µs before the one before it", Some(Reason::OrderInvalid))); + let mut l = base(); + l.name = "second_open".into(); + l.items[1].body = Body::Open(video_open("second_open", &WATCHED, tv(), ladder())); + out.push(case(&l, "an open record after the first", Some(Reason::OrderInvalid))); + let mut l = base(); + l.name = "first_not_open".into(); + l.items.remove(0); + let records = { + let mut out: Vec> = Vec::new(); + let mut prev = [0u8; 32]; + for (i, it) in l.items.iter().enumerate() { + let rec = Record::new([0; 32], i as u32, prev, it.t, it.body.clone()).sign(&key(SINK)); + prev = rec.digest(); + out.push(rec.to_bytes()); + } + out + }; + out.push(raw("first_not_open", "a log whose first record is a segment receipt", records, Some(Reason::OrderInvalid))); + out.push(raw("empty", "a log of no record", Vec::new(), Some(Reason::OrderInvalid))); + let mut l = base(); + l.name = "sink_changed".into(); + l.items[2].key = OTHER; + out.push(case(&l, "a record signed by another key, chained correctly", Some(Reason::SinkChanged))); + + // §3.9: the hop entries' classes. + let mut l = base(); + l.name = "hwshunt_hop".into(); + if let Body::Segment(s) = &mut l.items[1].body { + s.hops[1].class = vec![1]; + } + out.push(case(&l, "a hop figure of the legacy class HwShunt, tag 1", Some(Reason::EnergyInvalid))); + let mut l = base(); + l.name = "below_resolution_floor".into(); + if let Body::Segment(s) = &mut l.items[1].body { + s.hops[1] = HopEntry::new(role::SINK_DECODE, 10, Some(&counter(1_000))); + } + out.push(case(&l, "a counter figure of 10 µJ declared at 1000 ppm, below the resolution floor", Some(Reason::EnergyInvalid))); + let mut l = base(); + l.name = "marker_with_class".into(); + if let Body::Segment(s) = &mut l.items[1].body { + s.hops[0] = HopEntry { role: role::ORIGIN, joules: 0, class: vec![2] }; + } + out.push(case(&l, "a zero figure with a class: an unmetered hop written as a measurement of zero", Some(Reason::EnergyInvalid))); + let mut l = base(); + l.name = "figure_without_class".into(); + if let Body::Segment(s) = &mut l.items[1].body { + s.hops[0] = HopEntry { role: role::ORIGIN, joules: 5, class: vec![0] }; + } + out.push(case(&l, "a non-zero figure with tag 0", Some(Reason::EnergyInvalid))); + + // §3.9 steps 1, 3 and 4. + let mut l = base(); + l.name = "reason_unknown".into(); + if let Body::Terminate(t) = &mut l.last().body { + t.reason = 8; + } + out.push(case(&l, "a termination of reason 8", Some(Reason::ReasonUnknown))); + let l = Log::new("short_above_long", 0, video_open("short_above_long", &WATCHED, Declaration { long: 1080, short: 1920, ..tv() }, ladder())); + out.push(case(&l, "a declaration whose shorter side is above its longer", Some(Reason::DisplayInvalid))); + let l = Log::new("hdr_byte", 0, video_open("hdr_byte", &WATCHED, tv(), ladder())); + // The open record's `hdr` byte: after the header, the stream, four bytes, three u32 and + // the declaration's first 13 bytes. + let at = HEADER_LEN + 32 + 4 + 12 + 13; + out.push(raw("hdr_byte", "a declaration whose hdr byte is 2", resigned(&l, 0, |b| b[at] = 2), Some(Reason::DisplayInvalid))); + let l = Log::new("audio_declaration", 0, Open { display: tv(), ..audio_open("audio_declaration") }); + out.push(case(&l, "an audio stream's open record with a display declaration", Some(Reason::DisplayInvalid))); + let mut l = Log::new("display_in_audio", 0, audio_open("display_in_audio")); + l.display(S, tv()); + out.push(case(&l, "a display record in the log of an audio stream", Some(Reason::DisplayInvalid))); + let mut l = Log::new("display_zero_refresh", 0, video_open("display_zero_refresh", &WATCHED, tv(), ladder())); + l.display(S, Declaration { refresh_milli: 0, ..tv() }); + out.push(case(&l, "a display record declaring a refresh rate of 0", Some(Reason::DisplayInvalid))); + let l = Log::new("empty_ladder", 0, video_open("empty_ladder", &WATCHED, tv(), Vec::new())); + out.push(case(&l, "a video stream offered no rendition", Some(Reason::RecordInvalid))); + let l = Log::new("media_three", 0, Open { media: 3, ..video_open("media_three", &WATCHED, tv(), ladder()) }); + out.push(case(&l, "an open record of media 3", Some(Reason::RecordInvalid))); + let mut l = Log::new("signal_five", 0, video_open("signal_five", &WATCHED, tv(), ladder())); + l.obs(S, 5, 1); + out.push(case(&l, "an observation of signal 5", Some(Reason::RecordInvalid))); + let mut l = Log::new("interaction_value_two", 0, video_open("interaction_value_two", &WATCHED, tv(), ladder())); + l.obs(S, signal::INTERACTION, 2); + out.push(case(&l, "an interaction observation of value 2", Some(Reason::RecordInvalid))); + let mut l = Log::new("empty_segment", 0, video_open("empty_segment", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)); + if let Body::Segment(s) = &mut l.last().body { + s.media_end = s.media_start; + } + out.push(case(&l, "a segment whose media ends where it starts", Some(Reason::RecordInvalid))); + let mut l = Log::new("hops_out_of_order", 0, video_open("hops_out_of_order", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)); + if let Body::Segment(s) = &mut l.last().body { + s.hops.swap(0, 1); + } + out.push(case(&l, "a segment's hop entries out of the order of their roles", Some(Reason::RecordInvalid))); + let mut l = Log::new("match_bit_five", 0, video_open("match_bit_five", &WATCHED, tv(), ladder())); + l.seg(2 * S, 0, 2, 2 * S, hops(0)); + if let Body::Segment(s) = &mut l.last().body { + s.matched = 32; + } + out.push(case(&l, "a match with bit 5 set", Some(Reason::RecordInvalid))); + let mut l = base(); + l.name = "total_of_tag_zero".into(); + if let Body::Terminate(t) = &mut l.last().body { + t.totals.insert(0, Total { role: role::ORIGIN, tag: 0, figures: 2, joules: 0 }); + } + out.push(case(&l, "a total of tag 0: a sum over unmetered figures", Some(Reason::RecordInvalid))); + + // §3.9 reading and §3.8. + let l = base(); + let b = l.bytes(); + out.push(raw("truncated", "a segment receipt cut 1 byte short", changed(&l, 1, |r| { + r.pop(); + }), Some(Reason::Truncated))); + out.push(raw("trailing_byte", "a segment receipt with 1 byte after its signature", changed(&l, 1, |r| r.push(0)), Some(Reason::Malformed))); + out.push(raw("bad_magic", "a record whose magic is WSL2", changed(&l, 0, |r| r[3] = b'2'), Some(Reason::Malformed))); + out.push(raw("kind_six", "a record of kind 6", resigned(&l, 1, |r| r[5] = 6), Some(Reason::Malformed))); + out.push(raw("unknown_version", "a record of version 2", changed(&l, 0, |r| r[4] = 2), Some(Reason::UnknownVersion))); + out.push(raw("bad_signature", "a segment receipt with one bit of its signature flipped", changed(&l, 1, |r| { + let n = r.len(); + r[n - 1] ^= 1; + }), Some(Reason::BadSignature))); + let mut ident = b.clone(); + ident[0][6..38].copy_from_slice(&{ + let mut k = [0u8; 32]; + k[0] = 1; + k + }); + out.push(raw("bad_key", "an open record whose session key is the identity point, of small order", ident, Some(Reason::BadKey))); + let mut l = base(); + l.name = "long_reference".into(); + if let Body::Segment(s) = &mut l.items[1].body { + let mut c = vec![5]; + c.extend_from_slice(&4_000u32.to_be_bytes()); + c.extend_from_slice(&(2 * S).to_be_bytes()); + c.extend_from_slice(&300u32.to_be_bytes()); + c.extend(std::iter::repeat_n(b'a', 300)); + s.hops[1].class = c; + } + out.push(case(&l, "a calibration reference of 300 bytes", Some(Reason::Malformed))); + out +} + +/// The matches of capability-constraints §4.2. +fn matches() -> Vec { + let l = ladder(); + let cases: Vec<(&str, &str, Declaration, Vec, usize)> = vec![ + ("within", "1080p30 8-bit SDR on a 1080p60 8-bit panel", tv(), l.clone(), 2), + ("resolution", "1080p on a 720p panel", laptop(), l.clone(), 2), + ("portrait_panel", "1920 × 1080 on a panel declared as 1080 × 1920: sides without orientation", Declaration::panel(1080, 1920, 60_000, 8, false), l.clone(), 2), + ("portrait_rendition", "a 1080 × 1920 rendition on a 1920 × 1080 panel", tv(), vec![Rendition::new(1080, 1920, 30_000, 8, TRANSFER_SDR)], 0), + ("refresh", "60 pictures a second on a 30 Hz panel", Declaration::panel(1920, 1080, 30_000, 8, false), vec![Rendition::new(1280, 720, 30_000, 8, TRANSFER_SDR), Rendition::new(1280, 720, 60_000, 8, TRANSFER_SDR)], 1), + ("refresh_measured", "59.94 pictures a second on a panel measured at 60 Hz", tv(), vec![Rendition::new(1920, 1080, 59_940, 8, TRANSFER_SDR)], 0), + ("bit_depth", "a 10-bit SDR rendition on an 8-bit panel", tv(), vec![Rendition::new(1280, 720, 30_000, 8, TRANSFER_SDR), Rendition::new(1280, 720, 30_000, 10, TRANSFER_SDR)], 1), + ("hlg_on_sdr", "a 10-bit HLG rendition on a 10-bit SDR panel", Declaration::panel(1920, 1080, 60_000, 10, false), vec![Rendition::new(1920, 1080, 30_000, 10, TRANSFER_SDR), Rendition::new(1920, 1080, 30_000, 10, TRANSFER_HLG)], 1), + ("pq_on_hdr", "2160p60 10-bit PQ on a 2160p120 10-bit HDR panel", hdr_tv(), l.clone(), 3), + ("every_exceedance", "2160p60 10-bit PQ on a 720p30 8-bit SDR panel", Declaration::panel(1280, 720, 30_000, 8, false), l.clone(), 3), + ("none_within_least", "a 640 × 360 panel offered 480p and 720p: the least, 480p, alone admissible", Declaration::panel(640, 360, 60_000, 8, false), vec![Rendition::new(854, 480, 30_000, 8, TRANSFER_SDR), Rendition::new(1280, 720, 30_000, 8, TRANSFER_SDR)], 0), + ("none_within_other", "the same panel, the 720p rendition", Declaration::panel(640, 360, 60_000, 8, false), vec![Rendition::new(854, 480, 30_000, 8, TRANSFER_SDR), Rendition::new(1280, 720, 30_000, 8, TRANSFER_SDR)], 1), + ("least_by_rate", "none within; two of one size, the least the one of lower frame rate", Declaration::panel(640, 360, 25_000, 8, false), vec![Rendition::new(1280, 720, 60_000, 8, TRANSFER_SDR), Rendition::new(1280, 720, 30_000, 8, TRANSFER_SDR)], 1), + ("least_tie", "none within; two identical renditions, the least the one listed first", Declaration::panel(640, 360, 60_000, 8, false), vec![Rendition::new(1280, 720, 30_000, 8, TRANSFER_SDR), Rendition::new(1280, 720, 30_000, 8, TRANSFER_SDR)], 1), + ("least_by_range", "none within; one size, rate and depth, the least the SDR one", Declaration::panel(640, 360, 60_000, 10, false), vec![Rendition::new(1280, 720, 30_000, 10, TRANSFER_PQ), Rendition::new(1280, 720, 30_000, 10, TRANSFER_SDR)], 0), + ]; + cases + .into_iter() + .map(|(name, about, d, l, i)| { + json!({ + "name": name, + "about": about, + "declaration": d.to_json(), + "ladder": ladder_json(&l), + "rendition": i, + "exceedances": exceedances(&l[i], &d), + "match": match_of(&l, i, &d), + "admissible": admissible(&l, &d), + "least": least(&l), + }) + }) + .collect() +} + +fn log_json(c: &Case) -> Value { + let got = outcome_json(&c.records); + let reason = got["reason"].as_str().map(String::from); + assert_eq!(reason.as_deref(), c.want.map(Reason::code), "{}: {got}", c.name); + let mut m = Map::new(); + m.insert("name".into(), json!(c.name)); + m.insert("about".into(), json!(c.about)); + m.insert("records".into(), Value::Array(c.records.iter().map(|r| json!(hex(r))).collect())); + for (k, v) in got.as_object().expect("an object") { + m.insert(k.clone(), v.clone()); + } + Value::Object(m) +} + +/// The corpus, as `vectors.json` holds it. +pub fn generate() -> String { + let logs: Vec = accepted().iter().chain(refused().iter()).map(log_json).collect(); + let doc = json!({ + "corpus": "wai.live.termination", + "spec": "extensions/stream-termination.md, extensions/capability-constraints.md §4", + "generator": "wai_stream_termination_vectors gen", + "note": "Synthetic panels, ladders, timings and joule figures, chosen to exercise the rules; they claim nothing about any device or deployment. Keys are SHA-256 of a label and an index (see the generator); times are microseconds since the Unix epoch.", + "sink_keys": [hex(&key(SINK).verifying_key().to_bytes()), hex(&key(OTHER).verifying_key().to_bytes()), hex(&key(AUDIO_SINK).verifying_key().to_bytes())], + "matches": matches(), + "logs": logs, + }); + let mut s = serde_json::to_string_pretty(&doc).expect("json"); + s.push('\n'); + s +} + +fn unhex(s: &str) -> Option> { + if s.len() % 2 != 0 { + return None; + } + (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok()).collect() +} + +/// Every match and log of a corpus file checked against this module: the failures, by name. +pub fn verify(doc: &str) -> Vec { + let mut fails = Vec::new(); + let Ok(v) = serde_json::from_str::(doc) else { + return vec!["vectors.json is not JSON".into()]; + }; + for m in v["matches"].as_array().into_iter().flatten() { + let name = m["name"].as_str().unwrap_or("?"); + let (Some(d), Some(l)) = (Declaration::from_json(&m["declaration"]), m["ladder"].as_array().map(|a| a.iter().filter_map(Rendition::from_json).collect::>())) else { + fails.push(format!("match {name}: does not read")); + continue; + }; + let i = m["rendition"].as_u64().unwrap_or(u64::MAX) as usize; + if i >= l.len() { + fails.push(format!("match {name}: no rendition {i}")); + continue; + } + let got = json!({ "exceedances": exceedances(&l[i], &d), "match": match_of(&l, i, &d), "admissible": admissible(&l, &d), "least": least(&l) }); + for k in ["exceedances", "match", "admissible", "least"] { + if got[k] != m[k] { + fails.push(format!("match {name}: {k} is {} here, {} in the corpus", got[k], m[k])); + } + } + } + for g in v["logs"].as_array().into_iter().flatten() { + let name = g["name"].as_str().unwrap_or("?"); + let Some(records) = g["records"].as_array().map(|a| a.iter().filter_map(|x| x.as_str().and_then(unhex)).collect::>()) else { + fails.push(format!("log {name}: no records")); + continue; + }; + let got = outcome_json(&records); + for (k, want) in got.as_object().expect("an object") { + if g[k] != *want { + fails.push(format!("log {name}: {k} is {want} here, {} in the corpus", g[k])); + } + } + } + fails +} diff --git a/wai/wai-rs/src/stream_termination/tests.rs b/wai/wai-rs/src/stream_termination/tests.rs new file mode 100644 index 000000000..fc14d2b83 --- /dev/null +++ b/wai/wai-rs/src/stream_termination/tests.rs @@ -0,0 +1,81 @@ +//! Unit tests of the display match (capability-constraints §4.2) and of a sink's writer. + +use super::*; + +fn ladder() -> Vec { + vec![ + Rendition::new(640, 360, 30_000, 8, TRANSFER_SDR), + Rendition::new(1280, 720, 30_000, 8, TRANSFER_SDR), + Rendition::new(1920, 1080, 30_000, 8, TRANSFER_SDR), + Rendition::new(3840, 2160, 60_000, 10, TRANSFER_PQ), + ] +} + +#[test] +fn sides_have_no_orientation() { + let r = Rendition::new(1920, 1080, 30_000, 8, TRANSFER_SDR); + assert_eq!(exceedances(&r, &Declaration::panel(1080, 1920, 60_000, 8, false)), 0); + assert_eq!(exceedances(&Rendition::new(1080, 1920, 30_000, 8, TRANSFER_SDR), &Declaration::panel(1920, 1080, 60_000, 8, false)), 0); + assert_eq!(exceedances(&r, &Declaration::panel(1280, 720, 60_000, 8, false)), exceed::RESOLUTION); +} + +#[test] +fn each_exceedance_alone() { + let d = Declaration::panel(3840, 2160, 60_000, 8, false); + assert_eq!(exceedances(&Rendition::new(3840, 2160, 60_001, 8, TRANSFER_SDR), &d), exceed::REFRESH); + assert_eq!(exceedances(&Rendition::new(3840, 2160, 60_000, 9, TRANSFER_SDR), &d), exceed::BIT_DEPTH); + assert_eq!(exceedances(&Rendition::new(3840, 2160, 60_000, 8, TRANSFER_HLG), &d), exceed::DYNAMIC_RANGE); + assert_eq!(exceedances(&Rendition::new(3840, 2160, 60_000, 8, TRANSFER_PQ), &Declaration { hdr: true, ..d }), 0); + // A transfer that is neither PQ nor HLG is not above any panel. + assert_eq!(exceedances(&Rendition::new(3840, 2160, 60_000, 8, 14), &d), 0); +} + +#[test] +fn the_least_alone_where_none_is_within() { + let small = Declaration::panel(320, 240, 60_000, 8, false); + let l = ladder(); + assert_eq!(admissible(&l, &small), vec![0]); + assert_eq!(match_of(&l, 0, &small), exceed::RESOLUTION | exceed::NONE_WITHIN); + assert_eq!(match_of(&l, 3, &small), exceed::RESOLUTION | exceed::REFRESH | exceed::BIT_DEPTH | exceed::DYNAMIC_RANGE | exceed::NONE_WITHIN); + let tv = Declaration::panel(1920, 1080, 60_000, 8, false); + assert_eq!(admissible(&l, &tv), vec![0, 1, 2]); + assert_eq!(choose(&l, &tv), Some(2)); + assert_eq!(choose(&l, &small), Some(0)); + assert_eq!(least(&[]), None); +} + +#[test] +fn a_writer_refuses_after_its_termination_and_its_log_verifies() { + let key = SigningKey::from_bytes(&[7; 32]); + let open = Open { + stream: [1; 32], + media: media::VIDEO, + start: start::USER, + autoplay: setting::ON, + ua_policy: ua_policy::NOT_ASKED, + unseen_grace_ms: 1_000, + idle_ms: 0, + autoplay_grace_ms: 0, + display: Declaration::panel(1920, 1080, 60_000, 8, false), + ladder: ladder(), + }; + let mut w = Writer::open(key, 1_000_000, open); + let seg = |k: u64| Segment { segment: [k as u8; 32], objects: 30, bytes: 1000, media_start: k * 1_000_000, media_end: (k + 1) * 1_000_000, rendition: 2, matched: 0, hops: vec![HopEntry::unmetered(role::SINK_DECODE)] }; + w.segment(2_000_000, seg(0)).unwrap(); + w.observe(2_500_000, signal::VISIBILITY, signal::UNSEEN).unwrap(); + w.terminate(3_500_000, why::HIDDEN, 1_000_000).unwrap(); + assert_eq!(w.segment(4_000_000, seg(1)).unwrap_err(), Reason::AfterTermination); + let a = verify_log(w.records()).unwrap(); + assert_eq!(a.terminated, Some((why::HIDDEN, 1_000_000))); + assert_eq!(a.unseen_us, Some(1_000_000)); + assert_eq!(a.absent, vec![role::ORIGIN, role::RELAY, role::DISPLAY]); +} + +#[test] +fn reasons_are_listed_once() { + let mut codes: Vec<&str> = Reason::ALL.iter().map(|r| r.code()).collect(); + codes.sort(); + codes.dedup(); + assert_eq!(codes.len(), Reason::ALL.len()); + assert!(Reason::OWN.iter().all(|r| Reason::ALL.contains(r))); +} diff --git a/wai/wai-rs/tests/stream_termination.rs b/wai/wai-rs/tests/stream_termination.rs new file mode 100644 index 000000000..cec63c0f5 --- /dev/null +++ b/wai/wai-rs/tests/stream_termination.rs @@ -0,0 +1,96 @@ +//! `wai.live.termination` (extensions/stream-termination.md) and the display match of +//! capability-constraints §4: the committed corpus `live-conformance/termination/vectors.json` +//! regenerates byte for byte from its fixed seeds, every match and log in it gets the outcome it +//! states, every reason the extension adds refuses a log, the accepted logs are the cases §8 +//! names, and the extension's §7 lists exactly the reasons the reference reports. +#![cfg(feature = "stream_termination")] + +use std::collections::BTreeSet; +use std::path::PathBuf; +use wai::stream_termination::{corpus, Reason}; + +fn wai_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") +} + +fn read(p: &str) -> String { + std::fs::read_to_string(wai_dir().join(p)).unwrap_or_else(|e| panic!("{p}: {e}")) +} + +fn committed() -> String { + read("live-conformance/termination/vectors.json") +} + +#[test] +fn the_committed_corpus_regenerates_from_its_seeds() { + assert!(committed() == corpus::generate(), "live-conformance/termination/vectors.json differs from `wai_stream_termination_vectors gen`"); +} + +#[test] +fn every_match_and_log_gets_its_stated_outcome() { + let fails = corpus::verify(&committed()); + assert!(fails.is_empty(), "{}", fails.join("\n")); +} + +/// Every reason refuses a log; the accepted logs are §8's; and the facts that make the +/// invisible seen are what the cases were written to show. +#[test] +fn the_corpus_covers_every_reason_and_case() { + let doc: serde_json::Value = serde_json::from_str(&committed()).unwrap(); + let logs = doc["logs"].as_array().unwrap(); + let seen: BTreeSet<&str> = logs.iter().filter_map(|v| v["reason"].as_str()).collect(); + for r in Reason::ALL { + assert!(seen.contains(r.code()), "nothing in the corpus is refused {r}"); + } + let get = |n: &str| logs.iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no log {n}")); + for (n, why) in [ + ("hidden", "hidden"), + ("occluded", "occluded"), + ("display_off", "display_off"), + ("idle", "idle"), + ("autoplay_unattended", "autoplay_unattended"), + ("user_stop", "user"), + ("audio_hidden", "ended"), + ("none_within", "ended"), + ("display_change", "ended"), + ("hdr", "user"), + ] { + assert_eq!(get(n)["outcome"], "accept", "{n}"); + assert_eq!(get(n)["terminated"], why, "{n}"); + } + assert_eq!(get("open")["terminated"], serde_json::Value::Null); + // The hidden log: one segment past the grace is unwatched; its hops are per class, the + // origin unmetered, the relay absent. + let h = get("hidden"); + assert_eq!(h["unwatched"]["segments"], 1); + assert_eq!(h["unseen_us"], 10_800_000); + assert_eq!(h["absent"], serde_json::json!(["relay"])); + assert_eq!(h["unmetered"], serde_json::json!([{ "role": "origin", "count": 4 }])); + // Two classes of one hop, two totals. + let classes: Vec<&str> = get("user_stop")["totals"].as_array().unwrap().iter().filter(|t| t["role"] == "sink_decode").map(|t| t["class"].as_str().unwrap()).collect(); + assert_eq!(classes, ["OnChipCounter", "CalibratedInstrument"]); + assert_eq!(get("none_within")["panel"], serde_json::json!({ "within": 0, "none_within": 2 })); + assert_eq!(get("audio_hidden")["unwatched"], serde_json::Value::Null); + assert_eq!(get("segment_after_termination")["reason"], "after_termination"); + assert_eq!(get("segment_after_termination")["at"], 7); +} + +/// §7 of the extension lists exactly the codes `Reason::OWN` has, each once, in order. +#[test] +fn the_extension_lists_the_reasons() { + let text = read("extensions/stream-termination.md"); + let at = text.find("\n## 7. Reasons").expect("a §7"); + let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); + let listed: Vec = text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect(); + let want: Vec = Reason::OWN.iter().map(|r| r.code().to_string()).collect(); + assert_eq!(listed, want, "§7's table and Reason::OWN differ"); +} + +/// capability-constraints §4.2's match table names the bits the reference sets, in order. +#[test] +fn capability_constraints_lists_the_match_bits() { + let text = read("extensions/capability-constraints.md"); + let at = text.find("\n### 4.2 The match").expect("a §4.2"); + let names: Vec = text[at..].lines().skip_while(|l| !l.starts_with("| bit")).filter(|l| l.starts_with("| ") && !l.starts_with("| bit") && !l.starts_with("|---")).take_while(|l| l.starts_with("| ")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect(); + assert_eq!(names, ["resolution", "refresh", "bit_depth", "dynamic_range"]); +} -- 2.54.0 (Apple Git-157) From 12fad91a8b2f832b892f6a29a84e33fe56bb33cd Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 13:03:53 -0400 Subject: [PATCH 032/242] wai: the stream-termination verifier in Python; the register gains wai:transfer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit stream_termination_verify.py is the corpus's second implementation, written from the two extensions' text; it agrees with wai-rs on every match and log and requires every mutated copy to fail. The browser sink's register follows capability-constraints §3.1, and a test holds the two to each other. Co-Authored-By: Claude Opus 5.5 --- .../stream_termination_verify.py | 592 ++++++++++++++++++ wai/live-conformance/termination/vectors.json | 110 +++- wai/wai-rs/src/browser_sink.rs | 3 +- wai/wai-rs/src/stream_termination.rs | 2 +- wai/wai-rs/src/stream_termination/corpus.rs | 2 +- wai/wai-rs/tests/browser_sink.rs | 11 + 6 files changed, 706 insertions(+), 14 deletions(-) create mode 100644 wai/live-conformance/stream_termination_verify.py diff --git a/wai/live-conformance/stream_termination_verify.py b/wai/live-conformance/stream_termination_verify.py new file mode 100644 index 000000000..a9ccc418c --- /dev/null +++ b/wai/live-conformance/stream_termination_verify.py @@ -0,0 +1,592 @@ +#!/usr/bin/env python3 +"""Independent verifier of the stream-termination corpus (extensions/stream-termination.md, +and the display match of extensions/capability-constraints.md §4). + +Written from the two extensions' text. It shares no code with wai-rs, which generates the +corpus, and never runs it. It checks Ed25519 signatures with +../claim-conformance/_ed25519.py, WAI's strict rule (SPEC §7 *Signatures*) written from +RFC 8032 with the standard library, and SHA-256 with hashlib. Everything else is here: + +- the display match (capability-constraints §4.2): a rendition's exceedances, the least + rendition, the admissible set and the match a receipt records; +- the WSL1 records (§3): their layout, reading and rules (§3.9) and their signature (§3.8); +- a log's verification (§6.1), the state at an instant (§6.2) and the facts an accepted log + states (§6.3): the totals and unmetered counts by role and class, the absent roles, the + panel counts, the unseen time and the unwatched segments. + +For every match it requires the exceedances, match, admissible set and least rendition the +corpus states; for every log the outcome, and the reason and index of a refusal or every fact +of an acceptance. Of the corpus, that every reason of §7 refuses a log and that §7 lists +exactly those reasons, and that capability-constraints §4.2's table names the four bits this +verifier sets. Then it mutates copies of the corpus, one defect each, and requires every copy +to fail. + + python3 stream_termination_verify.py + +Exit status 0 only if every check holds and every mutated copy fails. Python 3.8 or later; +standard library only. +""" +import copy +import hashlib +import json +import os +import re +import struct +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance")) +import _ed25519 as ED # noqa: E402 + +MAGIC = b"WSL1" +DOMAIN = b"wai:stream-log\x01" +OPEN, SEGMENT, OBSERVATION, DISPLAY, TERMINATE = 1, 2, 3, 4, 5 +VIDEO, AUDIO = 1, 2 +USER_START, AUTOPLAY_START = 1, 2 +SETTING_OFF = 2 +DISALLOWED = 3 +VISIBILITY, OCCLUSION, DISPLAY_POWER, INTERACTION = 1, 2, 3, 4 +SEEN, UNSEEN = 1, 2 +HIDDEN, OCCLUDED, DISPLAY_OFF, IDLE, AUTOPLAY_UNATTENDED, USER, ENDED = 1, 2, 3, 4, 5, 6, 7 +WHY = {1: "hidden", 2: "occluded", 3: "display_off", 4: "idle", 5: "autoplay_unattended", 6: "user", 7: "ended"} +ROLE = {1: "origin", 2: "relay", 3: "sink_decode", 4: "display"} +LABEL = {2: "ModelBased", 3: "Estimator", 4: "OnChipCounter", 5: "CalibratedInstrument"} +NO_RENDITION = 255 +FLOOR = 288676 +RESOLUTION, REFRESH, BIT_DEPTH, DYNAMIC_RANGE, NONE_WITHIN = 1, 2, 4, 8, 16 +HDR_TRANSFERS = (16, 18) + +REASONS = [ + "reason_unknown", "display_invalid", "record_invalid", "after_termination", "order_invalid", + "sink_changed", "autoplay_against_setting", "media_order_invalid", "rendition_unknown", + "match_mismatch", "rendition_above_panel", "reason_not_applicable", "reason_unsupported", + "totals_mismatch", +] +BORROWED = ["truncated", "malformed", "unknown_version", "bad_key", "bad_signature", "energy_invalid"] + + +class Stop(Exception): + def __init__(self, reason): + super().__init__(reason) + self.reason = reason + + +# ------------------------------------------------- capability-constraints §4.2: the match + +def exceedances(r, d): + """Bits 0 to 3 of §4.2's table: r = (w, h, rate, depth, transfer), d a declaration dict.""" + w, h, rate, depth, transfer = r + x = 0 + if max(w, h) > d["long"] or min(w, h) > d["short"]: + x |= RESOLUTION + if rate > d["refresh_milli"]: + x |= REFRESH + if depth > d["bit_depth"]: + x |= BIT_DEPTH + if transfer in HDR_TRANSFERS and not d["hdr"]: + x |= DYNAMIC_RANGE + return x + + +def least(ladder): + """The first in the order of (w × h, r, d, 1 for PQ or HLG), then the first listed.""" + if not ladder: + return None + key = lambda i: (ladder[i][0] * ladder[i][1], ladder[i][2], ladder[i][3], 1 if ladder[i][4] in HDR_TRANSFERS else 0, i) + return min(range(len(ladder)), key=key) + + +def admissible(ladder, d): + within = [i for i, r in enumerate(ladder) if exceedances(r, d) == 0] + return within if within else ([least(ladder)] if ladder else []) + + +def match(ladder, i, d): + none = all(exceedances(r, d) != 0 for r in ladder) + return exceedances(ladder[i], d) | (NONE_WITHIN if none else 0) + + +def rendition_of(j): + return (j["width"], j["height"], j["frame_rate_milli"], j["bit_depth"], j.get("transfer", 1)) + + +# ------------------------------------------------------------------ §3: the records + +class Reader: + def __init__(self, b): + self.b, self.p = b, 0 + + def take(self, n): + if self.p + n > len(self.b): + raise Stop("truncated") + s = self.b[self.p:self.p + n] + self.p += n + return s + + def u8(self): + return self.take(1)[0] + + def u32(self): + return struct.unpack("IQB", r.take(13)) + return (4, ppm, window, filtering) + if tag == 5: + ppm, window, n = struct.unpack(">IQI", r.take(16)) + if n > 255: + raise Stop("malformed") + return (5, ppm, window, r.take(n)) + return (tag,) + + +def energy_ok(joules, cls): + """live-metering §3.6's energy_invalid rules, as §3.9 step 2 applies them.""" + tag = cls[0] + if tag not in (0, 2, 3, 4, 5): + return False + if (tag == 0) != (joules == 0): + return False + if tag == 4: + _, ppm, window, filtering = cls + return ppm > 0 and window > 0 and filtering in (0, 1, 2) and joules * ppm >= FLOOR + if tag == 5: + _, ppm, window, ref = cls + try: + text = ref.decode("utf-8") + except UnicodeDecodeError: + return False + return ppm > 0 and window > 0 and text.strip() != "" and joules * ppm >= FLOOR + return True + + +def declaration_ok(d): + return 1 <= d["short"] <= d["long"] and d["refresh_milli"] >= 1 and 1 <= d["bit_depth"] <= 16 and d["hdr"] in (0, 1) + + +def read_record(b): + """§3.9: the record, its rules and its signature; raises Stop with the reason.""" + if len(b) < 4: + raise Stop("truncated") + if b[:4] != MAGIC: + raise Stop("malformed") + if len(b) < 5: + raise Stop("truncated") + if b[4] != 1: + raise Stop("unknown_version") + r = Reader(b) + r.take(5) + kind = r.u8() + if kind not in (1, 2, 3, 4, 5): + raise Stop("malformed") + rec = {"kind": kind, "sink": r.take(32), "seq": r.u32(), "prev": r.take(32), "t": r.u64()} + if kind == OPEN: + rec["stream"] = r.take(32) + rec["media"], rec["start"], rec["autoplay"], rec["ua_policy"] = r.take(4) + rec["unseen_grace_ms"], rec["idle_ms"], rec["autoplay_grace_ms"] = r.u32(), r.u32(), r.u32() + rec["display_bytes"] = b[r.p:r.p + 22] + rec["display"] = read_declaration(r) + rec["ladder"] = [(r.u32(), r.u32(), r.u32(), r.u8(), r.u8()) for _ in range(r.u8())] + elif kind == SEGMENT: + rec["segment"] = r.take(32) + rec["objects"], rec["bytes"] = r.u32(), r.u64() + rec["media_start"], rec["media_end"] = r.u64(), r.u64() + rec["rendition"], rec["match"] = r.u8(), r.u8() + hops = [] + for _ in range(r.u8()): + role, joules = r.u8(), r.u64() + hops.append((role, joules, read_class(r))) + rec["hops"] = hops + elif kind == OBSERVATION: + rec["signal"], rec["value"] = r.u8(), r.u8() + elif kind == DISPLAY: + rec["display"] = read_declaration(r) + else: + rec["reason"], rec["media"], rec["segments"] = r.u8(), r.u64(), r.u32() + rec["totals"] = [(r.u8(), r.u8(), r.u32(), r.u64()) for _ in range(r.u8())] + rec["unmetered"] = [(r.u8(), r.u32()) for _ in range(r.u8())] + rec["sig"] = r.take(64) + if r.p != len(b): + raise Stop("malformed") + # Step 1. + if kind == TERMINATE and rec["reason"] not in WHY: + raise Stop("reason_unknown") + # Step 2. + if kind == SEGMENT and not all(energy_ok(j, c) for _, j, c in rec["hops"]): + raise Stop("energy_invalid") + # Step 3. + if kind == OPEN: + if rec["media"] == AUDIO: + if rec["display_bytes"] != bytes(22): + raise Stop("display_invalid") + elif not declaration_ok(rec["display"]): + raise Stop("display_invalid") + if kind == DISPLAY and not declaration_ok(rec["display"]): + raise Stop("display_invalid") + # Step 4. + if not rules_hold(rec): + raise Stop("record_invalid") + bad = ED.verify(rec["sink"], DOMAIN + b[:-64], rec["sig"]) + if bad: + raise Stop(bad) + return rec + + +def ascending(keys): + return all(a < b for a, b in zip(keys, keys[1:])) + + +def rules_hold(rec): + k = rec["kind"] + if k == OPEN: + if not (rec["media"] in (1, 2) and rec["start"] in (1, 2) and rec["autoplay"] in (1, 2) and rec["ua_policy"] <= 3): + return False + if rec["media"] == VIDEO: + return 1 <= len(rec["ladder"]) <= 32 and all(w >= 1 and h >= 1 and rate >= 1 and 1 <= d <= 16 for w, h, rate, d, _ in rec["ladder"]) + return rec["ladder"] == [] + if k == SEGMENT: + roles = [h[0] for h in rec["hops"]] + return (rec["objects"] >= 1 and rec["media_start"] < rec["media_end"] and rec["match"] < 32 + and len(roles) <= 4 and all(1 <= x <= 4 for x in roles) and ascending(roles)) + if k == OBSERVATION: + if rec["signal"] in (VISIBILITY, OCCLUSION, DISPLAY_POWER): + return rec["value"] in (1, 2) + return rec["signal"] == INTERACTION and rec["value"] == 1 + if k == TERMINATE: + t = rec["totals"] + u = rec["unmetered"] + return (all(1 <= ro <= 4 and 2 <= tg <= 5 and f >= 1 for ro, tg, f, _ in t) and ascending([(x[0], x[1]) for x in t]) + and all(1 <= ro <= 4 and c >= 1 for ro, c in u) and ascending([x[0] for x in u])) + return True + + +# ------------------------------------------------------------------ §6: verification + +def sums_of(segments): + totals, unmetered = {}, {} + for s in segments: + for role, joules, cls in s["hops"]: + if joules == 0: + unmetered[role] = unmetered.get(role, 0) + 1 + else: + f, j = totals.get((role, cls[0]), (0, 0)) + totals[(role, cls[0])] = (f + 1, j + joules) + return totals, unmetered + + +def sums_json(totals, unmetered): + return ([{"role": ROLE[r], "class": LABEL[g], "figures": f, "joules": j} for (r, g), (f, j) in sorted(totals.items())], + [{"role": ROLE[r], "count": c} for r, c in sorted(unmetered.items())]) + + +def digest(b): + return hashlib.sha256(b[:-64]).digest() + + +def verify_log(blobs): + """§6.1: ("refuse", reason, index) or ("accept", facts).""" + if not blobs: + return ("refuse", "order_invalid", 0) + o = None + prev, prev_t = bytes(32), 0 + ended = None + obs = {VISIBILITY: [], OCCLUSION: [], DISPLAY_POWER: []} + last_interaction = None + unseen = False + unseen_since = None + unseen_us = 0 + decl = None + segments, unwatched = [], [] + within = none_within = 0 + last_t = 0 + + def value(s): + return obs[s][-1][0] if obs[s] else SEEN + + def since(s): + """The t of the earliest observation of the trailing run of one value, or T0.""" + if not obs[s]: + return o["t"] + v = obs[s][-1][0] + t = obs[s][-1][1] + for vv, tt in reversed(obs[s]): + if vv != v: + break + t = tt + return t + + def is_unseen(): + return any(value(s) == UNSEEN for s in obs) + + for i, b in enumerate(blobs): + try: + rec = read_record(b) + except Stop as e: + return ("refuse", e.reason, i) + if ended is not None: + return ("refuse", "after_termination", i) + first = i == 0 + if (rec["kind"] == OPEN) != first or rec["seq"] != i or rec["prev"] != prev or (not first and rec["t"] < prev_t): + return ("refuse", "order_invalid", i) + if not first and rec["sink"] != o["sink"]: + return ("refuse", "sink_changed", i) + prev, prev_t, last_t = digest(b), rec["t"], rec["t"] + k = rec["kind"] + if k == OPEN: + if rec["start"] == AUTOPLAY_START and (rec["autoplay"] == SETTING_OFF or rec["ua_policy"] == DISALLOWED): + return ("refuse", "autoplay_against_setting", i) + o = rec + decl = rec["display"] + elif k == DISPLAY: + if o["media"] == AUDIO: + return ("refuse", "display_invalid", i) + decl = rec["display"] + elif k == OBSERVATION: + if rec["signal"] == INTERACTION: + last_interaction = rec["t"] + else: + obs[rec["signal"]].append((rec["value"], rec["t"])) + now = is_unseen() + if now and not unseen: + unseen_since = rec["t"] + elif unseen and not now: + unseen_us += rec["t"] - unseen_since + unseen_since = None + unseen = now + elif k == SEGMENT: + if segments and rec["media_start"] < segments[-1]["media_end"]: + return ("refuse", "media_order_invalid", i) + if o["media"] == VIDEO: + ladder = o["ladder"] + if rec["rendition"] >= len(ladder): + return ("refuse", "rendition_unknown", i) + if rec["match"] != match(ladder, rec["rendition"], decl): + return ("refuse", "match_mismatch", i) + if rec["rendition"] not in admissible(ladder, decl): + return ("refuse", "rendition_above_panel", i) + within += rec["match"] == 0 + none_within += 1 if rec["match"] & NONE_WITHIN else 0 + if unseen and rec["t"] - unseen_since >= o["unseen_grace_ms"] * 1000: + unwatched.append(rec) + else: + if rec["rendition"] != NO_RENDITION: + return ("refuse", "rendition_unknown", i) + if rec["match"] != 0: + return ("refuse", "match_mismatch", i) + segments.append(rec) + else: + if segments and not (segments[-1]["media_start"] <= rec["media"] <= segments[-1]["media_end"]): + return ("refuse", "media_order_invalid", i) + if o["media"] == AUDIO and rec["reason"] in (HIDDEN, OCCLUDED, DISPLAY_OFF): + return ("refuse", "reason_not_applicable", i) + if not supports(o, rec["reason"], rec["t"], value, since, last_interaction): + return ("refuse", "reason_unsupported", i) + totals, unmetered = sums_of(segments) + stated_t = {(ro, tg): (f, j) for ro, tg, f, j in rec["totals"]} + stated_u = dict(rec["unmetered"]) + if (rec["segments"] != len(segments) or stated_t != totals or stated_u != unmetered + or len(stated_t) != len(rec["totals"]) or len(stated_u) != len(rec["unmetered"])): + return ("refuse", "totals_mismatch", i) + ended = rec + video = o["media"] == VIDEO + totals, unmetered = sums_of(segments) + tj, uj = sums_json(totals, unmetered) + present = {h[0] for s in segments for h in s["hops"]} + facts = { + "outcome": "accept", + "record_count": len(blobs), + "segments": len(segments), + "media": "video" if video else "audio", + "terminated": WHY[ended["reason"]] if ended else None, + "media_end": ended["media"] if ended else None, + "totals": tj, + "unmetered": uj, + "absent": [ROLE[r] for r in (1, 2, 3, 4) if r not in present], + "panel": {"within": within, "none_within": none_within} if video else None, + "unseen_us": (unseen_us + (last_t - unseen_since if unseen else 0)) if video else None, + "unwatched": None, + } + if video: + wt, wu = sums_json(*sums_of(unwatched)) + facts["unwatched"] = {"segments": len(unwatched), "totals": wt, "unmetered": wu} + return ("accept", facts) + + +def supports(o, reason, t, value, since, last_interaction): + """§6.2's table at a terminate record made at t.""" + grace = o["unseen_grace_ms"] * 1000 + if reason == HIDDEN: + return value(VISIBILITY) == UNSEEN and t - since(VISIBILITY) >= grace + if reason == OCCLUDED: + return value(OCCLUSION) == UNSEEN and t - since(OCCLUSION) >= grace + if reason == DISPLAY_OFF: + return value(DISPLAY_POWER) == UNSEEN and t - since(DISPLAY_POWER) >= grace + if reason == IDLE: + last = last_interaction if last_interaction is not None else o["t"] + return o["idle_ms"] > 0 and t - last >= o["idle_ms"] * 1000 + if reason == AUTOPLAY_UNATTENDED: + return (o["start"] == AUTOPLAY_START and o["autoplay_grace_ms"] > 0 and last_interaction is None + and t - o["t"] >= o["autoplay_grace_ms"] * 1000) + return True + + +# ------------------------------------------------------------------ the corpus + +def check(doc, quiet=False): + fails = [] + for m in doc["matches"]: + d = dict(m["declaration"]) + d["hdr"] = 1 if d["hdr"] is True else 0 if d["hdr"] is False else d["hdr"] + ladder = [rendition_of(x) for x in m["ladder"]] + i = m["rendition"] + got = {"exceedances": exceedances(ladder[i], d), "match": match(ladder, i, d), "admissible": admissible(ladder, d), "least": least(ladder)} + for k, v in got.items(): + if m.get(k) != v: + fails.append("match %s: %s is %r here, %r in the corpus" % (m["name"], k, v, m.get(k))) + for g in doc["logs"]: + try: + blobs = [bytes.fromhex(x) for x in g["records"]] + except (ValueError, TypeError): + fails.append("log %s: records do not read as hex" % g["name"]) + continue + res = verify_log(blobs) + if res[0] == "refuse": + want = {"outcome": "refuse", "reason": res[1], "at": res[2]} + else: + want = res[1] + for k, v in want.items(): + if g.get(k) != v: + fails.append("log %s: %s is %r here, %r in the corpus" % (g["name"], k, v, g.get(k))) + if res[0] == "refuse" and any(k in g for k in ("totals", "segments", "panel")): + fails.append("log %s: a refused log states facts" % g["name"]) + if not quiet: + print("%s every match and log gets the outcome the corpus states (%d matches, %d logs)" + % ("PASS" if not fails else "FAIL", len(doc["matches"]), len(doc["logs"]))) + return fails + + +def section(path, start): + with open(path, encoding="utf-8") as f: + text = f.read() + at = text.index(start) + end = text.find("\n## ", at + 1) + nxt = text.find("\n### ", at + 1) + return text[at:end if end != -1 else len(text)], text[at:nxt if nxt != -1 else len(text)] + + +def reasons_listed(): + sec, _ = section(os.path.join(HERE, "..", "extensions", "stream-termination.md"), "\n## 7. Reasons") + return [m.group(1) for m in re.finditer(r"^\| `([a-z_]+)` \|", sec, re.M)] + + +def match_bits_listed(): + _, sec = section(os.path.join(HERE, "..", "extensions", "capability-constraints.md"), "\n### 4.2 The match") + return [(int(m.group(1)), m.group(2)) for m in re.finditer(r"^\| (\d) \| `([a-z_]+)` \|", sec, re.M)] + + +def flip(h, at): + b = bytearray(bytes.fromhex(h)) + b[at] ^= 1 + return b.hex() + + +def mutations(doc): + """Copies of the corpus, each with one defect, by name.""" + out = [] + + def mutate(name, f): + d = copy.deepcopy(doc) + f(d) + out.append((name, d)) + + def log(d, n): + return next(g for g in d["logs"] if g["name"] == n) + + def mt(d, n): + return next(m for m in d["matches"] if m["name"] == n) + + mutate("a hidden log's terminate record, one byte", lambda d: log(d, "hidden")["records"].__setitem__(-1, flip(log(d, "hidden")["records"][-1], 100))) + mutate("a hidden log's segment, its signature", lambda d: log(d, "hidden")["records"].__setitem__(1, flip(log(d, "hidden")["records"][1], -1))) + mutate("a hidden log with its observation left out", lambda d: log(d, "hidden")["records"].pop(3)) + mutate("a hidden log's unwatched count", lambda d: log(d, "hidden")["unwatched"].update(segments=0)) + mutate("a hidden log's unseen time", lambda d: log(d, "hidden").update(unseen_us=10800001)) + mutate("a hidden log's absent roles", lambda d: log(d, "hidden").update(absent=[])) + mutate("a hidden log's reason", lambda d: log(d, "hidden").update(terminated="user")) + mutate("the segment after the termination dropped", lambda d: log(d, "segment_after_termination")["records"].pop()) + mutate("a refused log's index", lambda d: log(d, "segment_after_termination").update(at=6)) + mutate("a refused log's reason", lambda d: log(d, "above_panel").update(reason="match_mismatch")) + mutate("two classes of one hop summed in the facts", lambda d: log(d, "user_stop").update(totals=[t for t in log(d, "user_stop")["totals"] if t["class"] != "CalibratedInstrument"])) + mutate("a total's joules", lambda d: log(d, "user_stop")["totals"][0].update(joules=log(d, "user_stop")["totals"][0]["joules"] + 1)) + mutate("an unmetered count", lambda d: log(d, "user_stop")["unmetered"][0].update(count=3)) + mutate("the panel counts", lambda d: log(d, "none_within").update(panel={"within": 2, "none_within": 0})) + mutate("an audio log given unwatched facts", lambda d: log(d, "audio_hidden").update(unwatched={"segments": 0, "totals": [], "unmetered": []})) + mutate("a match's exceedances", lambda d: mt(d, "every_exceedance").update(exceedances=7)) + mutate("a match's none_within bit", lambda d: mt(d, "none_within_least").update(match=1)) + mutate("a match's admissible set", lambda d: mt(d, "none_within_other").update(admissible=[0, 1])) + mutate("the least of a tie", lambda d: mt(d, "least_tie").update(least=1)) + mutate("the least by rate", lambda d: mt(d, "least_by_rate").update(least=0)) + mutate("a portrait panel's sides", lambda d: mt(d, "portrait_panel")["declaration"].update(long=1080, short=1080)) + mutate("an HDR panel declared SDR", lambda d: mt(d, "pq_on_hdr")["declaration"].update(hdr=False)) + mutate("the only log refused sink_changed", lambda d: d["logs"].remove(log(d, "sink_changed"))) + mutate("an accepted log's records reordered", lambda d: log(d, "occluded")["records"].reverse()) + mutate("an open log given a termination", lambda d: log(d, "open").update(terminated="ended")) + return out + + +def main(corpus_dir): + fails = [] + st = ED._self_test() + print(("PASS" if not st else "FAIL") + " Ed25519 strict rule self-test " + ",".join(st)) + if st: + fails.append("ed25519 self-test") + with open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8") as f: + doc = json.load(f) + fails += check(doc) + seen = {g.get("reason") for g in doc["logs"]} + missing = [r for r in REASONS + BORROWED if r not in seen] + if missing: + fails.append("no log is refused %s" % ", ".join(missing)) + print("%s every reason of §7, and every borrowed code, refuses a log" % ("PASS" if not missing else "FAIL")) + listed = reasons_listed() + if listed != REASONS: + fails.append("§7 lists %s" % listed) + print("%s §7 lists exactly the reasons this verifier knows" % ("PASS" if listed == REASONS else "FAIL")) + bits = match_bits_listed() + want_bits = [(0, "resolution"), (1, "refresh"), (2, "bit_depth"), (3, "dynamic_range")] + if bits != want_bits: + fails.append("capability-constraints §4.2 lists %s" % bits) + print("%s capability-constraints §4.2 names the four bits this verifier sets" % ("PASS" if bits == want_bits else "FAIL")) + for label, mutated in mutations(doc): + caught = check(mutated, quiet=True) + if not caught: + seen2 = {g.get("reason") for g in mutated["logs"]} + caught = ["reason %s refuses no log" % r for r in REASONS + BORROWED if r not in seen2] + print(("PASS" if caught else "FAIL") + " mutated copy fails: %s%s" % (label, " (%s)" % caught[0][:120] if caught else "")) + if not caught: + fails.append("a mutated copy verifies: %s" % label) + for f in fails: + print("FAIL " + f) + print(("PASS" if not fails else "FAIL") + " stream-termination corpus: %d matches, %d logs" % (len(doc["matches"]), len(doc["logs"]))) + return 1 if fails else 0 + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("usage: stream_termination_verify.py ", file=sys.stderr) + sys.exit(2) + sys.exit(main(sys.argv[1])) diff --git a/wai/live-conformance/termination/vectors.json b/wai/live-conformance/termination/vectors.json index 3c78f9ef6..934cf9a6e 100644 --- a/wai/live-conformance/termination/vectors.json +++ b/wai/live-conformance/termination/vectors.json @@ -619,8 +619,17 @@ { "name": "hidden", "about": "a watched video stream whose tab goes to the background at 5 s: a segment within the 10 s grace, one past it (unwatched), and the termination as hidden at 15.8 s; its origin unmetered, its decode on a counter, its display a stated estimate, its relay absent", - "records": 7, + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600bbb8ae481b6614b1b5f825a734a0c567e20562bd747e18ada3d98b80196c39ff01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a1085d8d28ee1ca56b1c0e4758f15748166ae04b86857ed4b90d7c718534a2e69ede15c467c560aac548841a027c3d0cf50b1a5e14a0781461ef5757bbd036fe50d", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000f24c4b1a6e2d12c5ea7c99358a22516757f282b19ce6c14592af016d323723c480c48ad8e95d0600849807e7e0c9119140fb625fb8ac383ce2c1d8a83c5fa25a780de4c30b8069123c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c1500000000000318598153fa63ed9d3fcbf4bc5e6cd0e7995b35bcfaf792e06966d1bb2ac36460c993d7170c4a1e1f81797b9b650a88515b9330d2f3e12fcd3937c86dc04cd100", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90200000046f1871aa36823264ae571be95d3dbc0798914d8bf2b6819b35954601f405f6a0049a9d8e95d0600ed0d2c4213fa41f8497eac25b2270da5d4573cd8a0ea7d1275f949c40c7ba5073c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c1500000000000312083d01d6da5f9db0b987d2398ba1586c525762b0db1afd55e1e275d0c6f2bb1bdd2e7f66b5732a3f0be3328c7715927e806635a5e631fdfe478306981da305", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000b38b97f4d09236c84c27c94991d090e243bd663b46dfe3bb10fa4dcca62f290e408bb8d8e95d06000102ae8b3da49a0d65352333e040b5f48c1ba67fd886ffcbb75200b15eca6d6a1d3c34ee4a011fabd36eaaf2dbf3c7e1fd119a75fb9b474765880e7a839fededea0f", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90400000067a881faa66c28935d9ce055802c86c8559a9fa200da6afe25459bed1245a17780cdc7d8e95d06005d879c9409f7f43e15051b4924e0c2da16d019f8c524444b0b50a2c4e8a5d8793c00000020a107000000000000093d0000000000808d5b00000000000200030100000000000000000003f0c602000000000004000061a800000000001e84800004c05c15000000000003f53228aad22a1cf28a1189acb147d122585d3a6d989614792e190dbf3225d222d2ade11c67d00dffde9f914b0a726a3b4b6322240cd10dc2846a4924de206b0d", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c905000000a20cd959385730f252242968120c1d8a18853cabd213a3d8d8b1eb48fb95f645e0c258d9e95d0600e83e4885817cf16f4e4f68785e503d777cb6503e78d519e99551818940aed8353c00000020a1070000000000808d5b000000000000127a00000000000200030100000000000000000003d8ca02000000000004000061a800000000001e84800004c05c15000000000003bfabf20dae05ba7ca50abc42945b8c68b70a2544dfea798a76e00d7b44c9aad98f6704c1ed4778e820672eece4601364cd35bc807eb16e17e68c847339223c0d", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c906000000c413014d2ad37b559d591eaa600eadf2b8ebb6e8e6fbf06006f8e04c775c668ec0565dd9e95d060001c0cf6a00000000000400000002030404000000f0130b00000000000403040000000073550000000000010104000000d9f32d8076986bff2a53d0bb6c08e2d4f75012ae206bf975185fa9a0290e2641588362940c933451329725845524339d40035779df273d7db416d555ab40c10f" + ], "outcome": "accept", + "record_count": 7, "segments": 4, "media": "video", "terminated": "hidden", @@ -680,8 +689,15 @@ { "name": "occluded", "about": "the presenting element scrolled out of the viewport at 3 s; terminated as occluded at 8 s, its 5 s grace run out", - "records": 5, + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600accc222c7cbec1684beb4874bffaee590aa5a4ee7603f05782e6591e111b1d6e01010101881300000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a109e005fd05f6cac65b86ea4fd2a380609e8c25c12d3eae73dba9882151ed216954a435408c516e408a82aa6167bf8b3ac06593319166af1b066a7e28f47762209", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9010000002e2fab9ae533e3dec3fde957cb0d244b880b8b1bea75c07249e2f314ebec706680c48ad8e95d0600cd291d30f0d54ac3077027a5ccce0d8ca5d51f92b152e9a4852c5868016d7a833c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c1500000000000362520eeff8005bc62a6912fd67842903698ce083a8b6bc7675863a379d51e61c62d8ba4b3a5e1295a263f6470d8f8cf5180efd3e9193a36fb0c4915ff3f65209", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90200000038e83e3725c9d1800c04f1d514341267ad872224eecb0f8426e21826449a210bc0069ad8e95d06000202375326768a3642c9a786e7ae504bcf49a75273a0b2ded4f7d225da2d8aa505d758eb6431826f9e00acac63c665e473529ad38824680d90d4e5c4c5ef228d5700", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000abc85ec45887465b9a7c1d49d960f4ed940fd08a9bfa7990bcabc67193f2a06e0049a9d8e95d06006bcfaa0a252fe4aa1d387ece47ba1a553d9e97808d4f0f81a47875be2ce6c66e3c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003351bf43f88d01bfdecf93bf38b66da796318d97db1a84ac0f6713fbce1911ac6e7685a05231364d6608bd4d5b84a1d1d4987ae008b3f102431e29c66f029730f", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c904000000773a7653c94b1ebe01389f8a4ee0de5f05f8b0c638ee6f4173ac6e0a4b3c17650052e6d8e95d060002c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000b757b2df65f2f3b5b5767a12a58118d61cf658dde3d4a262919b9dd503e02d6d5d76c351de768f1fe35dad1fcdcded398088beda173659f30dbe0dda78eb170f" + ], "outcome": "accept", + "record_count": 5, "segments": 2, "media": "video", "terminated": "occluded", @@ -723,8 +739,15 @@ { "name": "display_off", "about": "the display sleeps at 3 s and the document goes hidden with it; terminated as display off at 5 s, after its 2 s grace", - "records": 5, + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600c538c4f0832388fce7521cb4c20cdd9ae2bf20947e78f020ac81ff7175d36ec901010101d00700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a1091f2a7ec86d2b544c6a79b0578f071ad5016f0b3bbcc24f3e385506ab5c70b80113cfd53cd2cb860d32d48a63a1aa987bf8dab6c97d5a1a89b9acb9649299f09", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000a3fb18b34d793eef7e72203725055c1e71f2db5678682d4fb2aa345ea38e8a8d80c48ad8e95d0600f2f056893a559415dbfd35ecea1b1380be35f2604811796c1ab468e276715bb53c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003c02a8c85b591f1b26b597873240f6b395801d0ad77477c09a70aec6e7735f0b0364b71963a728e8efdc4bd54955ccbe9e9806d247b8e4be8a47ab2846347710c", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90200000032a07e097409296a71e72aea8cf9429835648c551fbe70596a6e8a59f376a9bac0069ad8e95d060003022ea482d59039bc5ba8b82a798e1ee2350b4b13821a00fdb9f60add7f2e4028e651e815beb119a0d660acf558d33483a7643717996afd358bfacf15ef12c61e02", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9030000009da0742befecf0748e81d00d55f113b91846a6c1a2c55d893930f6aba7b11e50c0069ad8e95d06000102a332adf29ed20d329cd31c7878f597ea8b3ef66fa0430acbfb54eb000d72d3ac21a16fe7de06a46cbb52af2393e92dfaa2c832ce60470e8fc4ae022f440f3f01", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9040000000ba6fc11ea02da96c50784f892b0300de8bc93fddb37f347e1511be9181d2122408bb8d8e95d06000340420f0000000000010000000203040100000020bf020000000000040301000000c05c15000000000001010100000083982fcb6f02d29401bd2b97fa30630072409412f4ad60d61d7c15420014d986f66ac1afa0116e19188556491d1f18a8c14dc4f4fdc14f7318b708a5c31df004" + ], "outcome": "accept", + "record_count": 5, "segments": 1, "media": "video", "terminated": "display_off", @@ -766,8 +789,17 @@ { "name": "idle", "about": "interactions at 1 s and 4 s, then none: terminated as idle at 34 s, its 30 s timeout run out; the segments are 10 s spans of a MoQ track", - "records": 7, + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06007c34225aa3223812b0445aee9805986f45d29d26b1a5f1bf18699a19110feed901010101102700003075000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10296d4ffae371f42c075ddf6bffbcea7b7a71801cd094aebd5d2210bb265f68e0f67cd5fbe1d7f9690303263130a081860b0ea9ff43f449605182e3f06741ac00", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90100000061de702b69cbdcc3a2c335b40a6777c36fba29626bddff904581e1a8c66d4bda40827bd8e95d06000401110a43d8098be59942a7a7340a7ac0610415c5464e394a360415b1480b396e201f867717e611af7185e25c5542382fce60a629a5602c2e72250b653e3110ff0a", + "57534c310103a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000461aa75c481d3c8afbffd8f2b1a9d6052d03b2eb730f6eebc78ab3fde2edb3830049a9d8e95d0600040127aad7c367ce7400a1651f1b663a9a9046c6b6a22808cd1cec6919dce32a8f1d09d918f1cf3c2af9f14279068263eca1a1e68048f7af3b52fa457a9d3461db05", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000e74578a90f8eb3aad27c9078ea757358bfba86ea85585b87aa3e7e15e1cabae580d604d9e95d06003d3a9b3a6db21f861faaf01dd77f0b7b352f99c7a8137ace1327dd9c9564df082c010000a02526000000000000000000000000008096980000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c150000000000034afcb146f0543075de3e3829b17bdf91ea401f1b8ad51aea8b8e92ad2d3c60cd57fc3ffc382a757e5d582bf531383cc1ba524d2b5fc8da374d9b3d9ef87b5b03", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9040000000ea82770300f30a1bbb79902d05d64b87065bb7c87314e75f94622303463bbdb006d9dd9e95d0600192781c00aeb8433f8b765fac1234325fe8c8d81f424a65eb0ee63c7c2d06f012c010000a0252600000000008096980000000000002d310100000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c150000000000034c2934022b1a31c7b414eed41a786038512d269d4388dc36857d5bb23ff4ef98041397bc0714d0c449ff37055edf8569621dc3b77a45e69d0d5e36ed6afc6f0e", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c905000000080a01ba20bc792a9df534aca9fc524c1c9a0c4fc4198a80ba6c81a486b5f001800336dae95d060020a2619598cc4d01468a2d931362f0bd642674e6a2a2f64d1a2040bdf7764e742c010000a025260000000000002d31010000000080c3c901000000000200030100000000000000000003f0c602000000000004000061a800000000001e84800004c05c1500000000000353105c2af6cd5afee43f6026399b71a69e591443d92d0903ddea0facde725d9000708d8acc50ac0cd47050c91621ac30222baf4592be073c8b63193c81d05f07", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9060000003f570bc5a067daf3b5fddf091eaf8083361d82022929e27b5b1d67e222782231800c73dae95d06000480c3c90100000000030000000203040300000018490800000000000403030000004016400000000000010103000000d2c9e85bad6be4bf646dbe0aba00ae84b322b60cc017a283a1e44c777d0f31ec0803206386f92091f29a8e697a80a398b8bba136079a90495ac890726f49220d" + ], "outcome": "accept", + "record_count": 7, "segments": 3, "media": "video", "terminated": "idle", @@ -809,8 +841,22 @@ { "name": "autoplay_unattended", "about": "started by autoplay, the viewer's setting on and the platform's answer allowed-muted; no interaction in 20 s, terminated as autoplayed with no interaction", - "records": 12, + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06001205e0eef69fefddda8eb91daeeabbf2013eab8217953d2c41f0f1b263ee6909010201021027000000000000204e0000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a102fc2ef53eeaa9d1a54e5c5f101c023a5896dcbb25f09021f5f18c00515c9db4840b18d0186b637aef3a4453b179e554e2cb9a34148e8fe4366b2421b0378c801", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9010000008d8cbe3b5111d172c9d9a84ff46c9a2355528991597456e0149f48d2aab5822680c48ad8e95d0600b8259cd4aa565d78157274ef741b5debeaf010aebc3ac7d24ef339ee168bee233c00000020a1070000000000000000000000000080841e0000000000010003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c1500000000000394d847f3547176feb1f905309114ab64528383be41cc12c3da9fe70d004a27a95859bb51c125a826fd8f2e1f144b90732ab0ce12dbb39fc6b1fd8c39468b840b", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000a8cbe6394f7ff057f6df10f36dbebc99d2b96998ce5127de423e55d5d99258b30049a9d8e95d0600fd729b8515ee74172a5bacb108045dc54567ad8f5e11ee6a72ada235b337620a3c00000020a107000000000080841e000000000000093d0000000000010003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003c0b5a0d213f562e711436bd3838f3841ca0a42d8bffa98b526ec644ba4515cc3df5f3d4cee01f701d344f53255d86a4ddbc0d73e4f16080e7a42e605bc0bf403", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000be27de42e90ecb2d93dd0a4e30cc32101b499a4e769bccda9416f65773c3615480cdc7d8e95d0600e3243167d22815aa22df9c282dbf5fa2b746f8bad31ebb1d1b720998663c3f973c00000020a107000000000000093d0000000000808d5b00000000000100030100000000000000000003f0c602000000000004000061a800000000001e84800004c05c15000000000003279a11327bc24b957688e4cab1068d618822a118162df89251051d7245eb7604e4a8f3a0f57b43bc07204f78f9cb9907c3adb387f07b46874879979cde167d0a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c904000000f387dda07561dd74f0128188e6640e3e2eb4574d6855acb45a42d50fe087730e0052e6d8e95d0600a0221f4874e5de7053707a9818f7f137fcccb0b6f7c458b8e8ef2cb721a4ce2e3c00000020a1070000000000808d5b000000000000127a00000000000100030100000000000000000003d8ca02000000000004000061a800000000001e84800004c05c150000000000037cc82bac6b437a58c1c6e1db7dc2d84f79ba19fb07b79bb83a210e8499da9cffd5e6817e98d0875107ad72bae29bd77706d848eeca2a23e018eab3a329af8f0c", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9050000000dae8ada58ce7b74c470964e83c48a2c8ab080d85386acb33b29f429906b19cd80d604d9e95d0600b6a1df944136725b6b7138d77130593b4381b84bcd807194f7d414b3184cef923c00000020a107000000000000127a000000000080969800000000000100030100000000000000000003c0ce02000000000004000061a800000000001e84800004c05c150000000000037d1f2316096ea6c248738fc8d749840976661fba5ceededad13c3febba3a1b3ee081d158132879fff401a428b3f786f29bc4834f9558b6bd5118f479920c470c", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c906000000bc616ac0a3f100df7a497c3d3716c2aaf62fd13a2ef8557a353fa87f3aa010e4005b23d9e95d0600c344ed81176d51bd017db4e377f62a7634ab6252223159ccfed5aae75e84fa873c00000020a10700000000008096980000000000001bb700000000000100030100000000000000000003a8d202000000000004000061a800000000001e84800004c05c1500000000000300abbb09af32cd143086a9ccd2454d719662c10df2e08b5de5bec7fdeb579bea101e50b5acb74d0586fae12bd650186749fa4f0a9a904bac8bffa3ac98c53c02", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9070000000c2b29ecff73a2ec08936d214d30a293d67b4666bd3e574a843dcfd00029354e80df41d9e95d0600d1f544ee5649e36a0af24694fb5ab6b30a427e2ed0a8cd3a7a924df6f49db9173c00000020a1070000000000001bb70000000000809fd50000000000010003010000000000000000000390d602000000000004000061a800000000001e84800004c05c15000000000003ef68f1d3d21dc7bdfb22f04b69aeda7a327ceea07e2943055b8a3876d1adf59636fb5c0c2714cce90af029880ec58a968c65d40ef465ad0388df0f69e5bded0a", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c908000000498ecddf3c2e5c3de08f0e43e1747841b5ea93e05ec79c6a7c3a50f76e7be4ff006460d9e95d0600d5cde72d51d07648c373e3aea8de7a26299b35e3ee27bb4dd9f51f145e02525d3c00000020a1070000000000809fd500000000000024f40000000000010003010000000000000000000378da02000000000004000061a800000000001e84800004c05c15000000000003ee0356a31022ab0bef6091d08e8c6d4bab74f7c00c5eddb1d182e20e7deda4847785940c4c5bfb0d3d0eb45d8e79d5ed2ad8bbc46cd1958eb63076f66b8bf308", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c909000000f06893b8a6ec4cedc4d754111f10749d7a4d1f9dddd62de6d6247b3d2c63af1080e87ed9e95d060074a58024dd7d27f4179c54d001cfba893400002bfb7b0adc722c43d0740d16533c00000020a10700000000000024f4000000000080a8120100000000010003010000000000000000000360de02000000000004000061a800000000001e84800004c05c1500000000000368e405d75e089540fb14abc6432983aadf5b5fdbe4816efc4381051abfa800a0355c40d881c77d65c9a2e1bc1aeba67ef413095dc692aaf6086a4a96b448890e", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90a00000031687aebaf11078268dd89cce7f5e22cdfcc9987a273a4ffdd88ed1ee87c72c2006d9dd9e95d06008c5ce2a34e27fe10e8ebdbc302e1a63fafc25537c7b68f65864a40ed28124baa3c00000020a107000000000080a8120100000000002d310100000000010003010000000000000000000348e202000000000004000061a800000000001e84800004c05c1500000000000312397da81052fad423e0c8c1d92dc7a3e2a1ab9106da4a42d56c11a6e5268e2d53da3b10831fe702394ec8fd6b5108ff3e988bfaea36d1a7a49159223581780f", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90b000000905da1b1bcd6a755f36adf880e640676deb9ea60cd58b3e5a9a67b683bca354f006d9dd9e95d060005002d3101000000000a0000000203040a00000008271c000000000004030a000000809fd5000000000001010a0000005bd0f32f861cfebe948b37604555a3fc8b6cc971df33a973b2b91d385b86f7749e030b4760c47cb13fcf555fe3dbfdbabf82433a4b20db2d2730ef61b46cce08" + ], "outcome": "accept", + "record_count": 12, "segments": 10, "media": "video", "terminated": "autoplay_unattended", @@ -852,8 +898,15 @@ { "name": "user_stop", "about": "stopped by the viewer: the sink decode's figures under two classes, a total for each and none across them; the origin once modelled and twice unmetered; relay and display absent", - "records": 5, + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06009968f18a97922021eded9a4265d4a776feb805237ddb224ad67d24070c3563de01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10106a79b2afe3989099390c6e4a544996604e693c5870cf4c791dc3de0950ef4d95f3ca0eb5a963fdf2ba1ed091edd6c06665ea484726ed225de7d0201b94b70f", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9010000009e2e86230a23f32ad083cd21a3c35e24bdbbfc305635bb199346a822d4b0351480c48ad8e95d06005ab9de555399dbe0a9283de245bbf2ef440fe454c9f5c2fb250963ee7bdfb14e3c00000020a1070000000000000000000000000080841e000000000002000201a0bb0d0000000000020350340300000000000500000fa000000000001e84800000001563616c3a636f727075732d6d657465722d30303031c58b48976e35a5038f731fe705fe1724d17433e92734148e27f83324797c18d12325eb095802f0e0defb32eb413c27ef22f61b2423a99e054dc211ed434a530f", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000ddf9b9cb294a4e8cdfd7a90f477231446569e2569801e5e3ecb78c87910699ee0049a9d8e95d06004216262f9ad5d745d12a7ff3c808a1874eae26f468d7d2196f2cb99eef21f3753c00000020a107000000000080841e000000000000093d0000000000020002010000000000000000000330e602000000000004000061a800000000001e8480002bace1865b1de8f9ac33d0a585b73ad8f1265991052625efd806d7166a4025c0df81f7754b6491f4cd631b820eee700d09a78d8ebf4903ea5f87aa8dd40d950e", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000a60f8e77c63f1ab3563b48ab060963e6df2b72cd84a15ec27b0465a69c635ad080cdc7d8e95d06007df37d1528fc64df5be9adafadd7e566cf5526c4a12f4676697de7a9bb97ba913c00000020a107000000000000093d0000000000808d5b00000000000200020100000000000000000003400d03000000000004000061a800000000001e8480004aaa82915ea2491dddafbfa5638cf886f7f6af6c06f2fba1311ea396cce31399ea433695ea4d5ebb8b5b32c4a6ff1df93b17f68bf7bde557def49df7b0185508", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9040000005d726fdfe0b7f287aaf8b7194cf2db58003e6278cb9bb3c311583a11cee8bb5fa06ecfd8e95d06000660ec5300000000000300000003010201000000a0bb0d000000000003040200000070f30500000000000305010000005034030000000000010102000000f252eb707764f4d49e1e7a21523bfd5f644c270fdd10129584f8519b23f11ac32acb1e192632872276f87697cc6f5d49b28e0cfb9ab4a9af33852af16edfbe05" + ], "outcome": "accept", + "record_count": 5, "segments": 3, "media": "video", "terminated": "user", @@ -902,8 +955,19 @@ { "name": "audio_hidden", "about": "an audio stream goes on while its document is hidden, which hidden does not end, and ends with the presentation", - "records": 9, + "records": [ + "57534c310101a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc00000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600ee46e57d87812ef4ac6ab76c00d4d064d76b6dc0bc24528fd0e6294d7fb585d20201010110270000000000000000000000000000000000000000000000000000000000000000004b51f20d970b98ecf2db0ed443ec39134de6f235326db59cc2317175d5f2770639672a09168fc3a2a93a4bf58a5a83dceeef85edc9a5028b98208b160e4b0f08", + "57534c310102a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc010000002762957ce4283427a456457b6391b912f6c985d9838bb88fdc66f5a0aabead7c80c48ad8e95d06003bb1c3c1ed581f83f5bdd93ddde1c16491d5758783750067c9b3c1b518c650323c00000020a1070000000000000000000000000080841e0000000000ff00020100000000000000000003000000000000000000f781eabc07a62216ff766050ee053393a7e28e6c6175d2f8e9434744bd0b689b62380f961287fdaae6ced4526f67e9f1adfc73f5b6a92d5e690ab1d17d2ade0b", + "57534c310103a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc0200000095d3b2fdb2201138b22339fdfcbf3e3723ecde4c7acd4b6c8414ad281a7cfa82c0069ad8e95d060001021b005d46827a19628d5d7e37c2e721fef865a416338a7bb2c7c44715ce56199a5237fef1f29cd98dbc98456c2ddb5c697348ce1ad0b6935ee0a642c51d489406", + "57534c310102a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc03000000ca8ee6ae6fb7a5d3791f0c8de2401f53ba9e35d399a7d77a609f3147d583dddd0049a9d8e95d06006ca5ad965de32c4da0011fc31b31ec671e62f86a2c44fe2b22416b31a4d39c9b3c00000020a107000000000080841e000000000000093d0000000000ff00020100000000000000000003000000000000000000152b1f6945d0a465169708f82cc022f15dcecbc1d34c83b975e8c1230853d63a8618fb5f1f6e21e2506c3353f80cd63ef31747b38112db6028e633cd66172606", + "57534c310102a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc040000000670d05e5da9921ed257d89f06b91a03c845a91b5b08b742de2b4e3598057dae80cdc7d8e95d0600927f8d5bf8a43ed8ee8ecee3c72021e6e0c7a6ceefe69873a62fdbd74c31ed9f3c00000020a107000000000000093d0000000000808d5b0000000000ff00020100000000000000000003000000000000000000b05c1b8f17218984e5233eaf762d4c07b1ad76eaf6a068bf0a282089361f47acd612f01a4601c4d4aef01bf4e55743ed5f0e5be7b4a1df5ba07d6a246331fe00", + "57534c310102a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc0500000018ceb7571950b50512bc580bd2ee0187dcbc84a895bbb5c0fb957967eac8d2050052e6d8e95d06009f0be36416c847fa73c35d813f46da0a90486bad696bef1111a58927b0674c523c00000020a1070000000000808d5b000000000000127a0000000000ff00020100000000000000000003000000000000000000e000e4989657eac0104db6a0b9acc4a552a8b2377a078f1e44841cd8dec4beb8cc0f2bc68798c57a20471943dfdeddea34e314927726dede6c4d869d5d040502", + "57534c310102a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc060000007f1279ec801add9b255123b8ff6c90cdd4f78248bd73ed678b7374ead82cbe0c80d604d9e95d0600656dcc2181814672ea2fe0c23fcf56870af844d1f62aa0c94d6b0e585150c1883c00000020a107000000000000127a00000000008096980000000000ff0002010000000000000000000300000000000000000002a26cf76cd87f6b016afc7845b07fa3b525413bc24b4c13a7210138a80fd979194e0118e8700f9c074dde3803353d4c0562c059da35121699207ba7c8622a09", + "57534c310102a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc070000000fa0eaddb5b54b7baa54bbea4950597f58a6b0e01ce8698a107d852a9720b11e005b23d9e95d0600af22a1bb9ac933d57a46f6d34b3f63e4400d135369317c037f10f4c8e9f609f03c00000020a10700000000008096980000000000001bb70000000000ff0002010000000000000000000300000000000000000057a3214714fb80fb4acf01766309cb7e0d34ce1d4b162f6a5a737f296d097d1ef049ceb76db1eacd9e96fccefd6f61ee73317efc3719c51fe9c951c2722f9003", + "57534c310105a1b7aeec0bae01e5b04f1a080b197462f532146812330382f84b0f4433229bbc0800000061f0b45ac64f18814c0e7ce79dfd317437b07040989c4449327faf1a50a998de005b23d9e95d060007001bb7000000000006000000000201060000000306000000a0ed9387f549066014125a937a5fcf1a2a08d2e39c8dc2df2ccfcfdfe0f4bd08f00ec5b3c2263cb244c18c3a4999d2e0a2d78de204b30cf4b40c5ecc59f49f04" + ], "outcome": "accept", + "record_count": 9, "segments": 6, "media": "audio", "terminated": "ended", @@ -930,8 +994,13 @@ { "name": "open", "about": "a log with no terminate record: accepted as open", - "records": 3, + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060080a3e857a4a6d182d3b3b81e62c4dd7e8fd2a1a1b87fca6f7ff73079ed672d2501010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a101dc3d97c128374002c2c9e753c8b0297e401d55ad3209c90733cf685de0c6aa205884392c00c0c40777ee59333295d0d89492e1ef2f6ffb25643ad5d1ddf720d", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90100000063dba507d2778e75466655e96cd7d1e8a2ca271b0c01ec90bcecd52be68d6e1380c48ad8e95d0600158c4dca0819ebdcabcfa74353b5233a1546a47df4bb7ebcdd34b2bc94825eb83c00000020a1070000000000000000000000000080841e0000000000010003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c150000000000037b9ddea33bd831c188518139e3fa7a90eb3d74d25da85184e5c855d3bdb8f9b4dcfac1dc22195f229867cbd2faa1d66d926b553605556d5ba3dc6fb6da9d3401", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000b5831d32b0495ff09dd84a0b3a069d50c327d138dbab3bf22ecd387650a2dd280049a9d8e95d06009998f09a7eabb442045e225ba68c0b6e28229290c39d82a9856c8c76622116133c00000020a107000000000080841e000000000000093d0000000000020003010000000000000000000308c302000000000004000061a800000000001e84800004c05c150000000000039dd015e45d9690503c5a9d74e6ad6b772c55c05a90c59fd356abeeb6b746ec9652a10f5cdad42b777be7ae1596739d07ca4571603dc9c5f2341b23de1e727b0e" + ], "outcome": "accept", + "record_count": 3, "segments": 2, "media": "video", "terminated": null, @@ -973,8 +1042,14 @@ { "name": "none_within", "about": "a 640 × 360 panel declared in portrait, and a ladder whose least rendition is 854 × 480: served the least, its match recording the resolution and none within", - "records": 4, + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060020a07149e06730a825704782a283bbbf62c62b064274c2d072cd2b9087ae7a7801010101102700000000000000000000800200006801000060ea0000080000000000000000000256030000e001000030750000080100050000d002000030750000080158f3c681e44c4b8330428e5bd0afa7d24fa56f7045f475bae578f596e2917b43c7588d73f617d904032dbb79559ce620e4ff615343d148476607c7ee82f61809", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c9010000003869cdff1dd602bb0a7ba408b5f91cc3d42fee1af57fcde1a105d2e92cc3931c80c48ad8e95d060045085d8d16055a64baa63dce100c1ef4ef0aac7355092fcf975bd7c2ce07f1f03c00000020a1070000000000000000000000000080841e0000000000001103010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003b8308bc18b96f50a791f8ef8f524688ab13b53d27a299bbe135098c78a4caa63ad1ba3cf341e7295a3324410dd95eabf37df28e23e4c7ac9781c51e2fad3a90e", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000512cf031f1ed15fdbec29a033b7ac1e14cd455b8a4cdb73123144b03b5ae95720049a9d8e95d06008f355b52b60baa2575203be50a6af6a2e65442c4d2fa485df3235c8cb9aed7063c00000020a107000000000080841e000000000000093d0000000000001103010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003dc2c95f62d66d9e6a7de80789aca8154c6d1648b35eb1c6be825099a0de9851c895983798ce4339f89cf5087c513d0274fdb06cac0c61fc97aea36a30453f00b", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000f253eb98d21989cc849a9dd814e695949792931bd4ad29b41179b8de0669e60e408bb8d8e95d06000700093d00000000000200000002030402000000288205000000000004030200000080b92a000000000001010200000010de604fe58bfd59493eda8a3dcf76286da4cdcdad1ed2a5b826a6210186893e67481dca006764fe828bebf5f669b3182c2e6694921a21cbc68de08abeb4ae0d" + ], "outcome": "accept", + "record_count": 4, "segments": 2, "media": "video", "terminated": "ended", @@ -1016,8 +1091,15 @@ { "name": "display_change", "about": "the window moves to a 720p panel, declared in portrait, at 3 s: the sink moves to the 720p rendition, within the new declaration", - "records": 5, + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d0600a8108cddb893c5ed6031e3bc1ec1017754ec27d94fbdfe9a812619169a88b04c01010101102700000000000000000000800700003804000060ea00000800ba040000a802000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a10bbe817f9c6ab854b0f006edadf919cdbe050e11517f3246846d1605b9daf314f1eab0e5049506d305ef8a9726f4408830b65b37c8f2c57bafda2a71976e58b05", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90100000023be8d3b2d84b13708a0c68455efda6716f088413aebb1195400d8da54a58ccc80c48ad8e95d060052c4c52b3a3dd3f098a4ce2d5deb1095a38c056e848b573650f9691eae0b3bbc3c00000020a1070000000000000000000000000080841e0000000000020003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003cb23ac661ef36c4ac5cbb76113ef6016a1bbb85e828b1bbb7fdbeb72b0a594654e5eddc72d80b5c404648bc429008ffc7c7406577b8449510e425fec90a0300e", + "57534c310104a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000ea8e12f029bd8b062219080d0c5a5b2550a4015c2c8591956b9a089b34e7186cc0069ad8e95d060000050000d002000060ea00000800000000000000000075dee229e704cdfa3f93d4e5026a375465327645c7140715b31ee8a2fcb4023b34636c16740747721e1d10ea1d7e4760a0e06bc74337ee29e5fc56ac607e4a08", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c90300000044d61af5956626da3bc673e3cd583d5936ad4fcf8b9df121587a7817a16c00a50049a9d8e95d06004c99b46853e3e32935b6528b4101d016aca34c7d76cb554eab45cc83bceb56223c00000020a107000000000080841e000000000000093d0000000000010003010000000000000000000308c302000000000004000061a800000000001e84800004c05c15000000000003df10a7bb8f06e988e21b8dac881fdf6588b0d87ca4bf94320ef2b815837e3bf3f26e7bf3027d0da028f374fb5c78c4fee2d81ba73dd55bb4ea274d9d4fe94307", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c904000000755231d56977e6f1f5e1f9c72c3eb4dd50edc98c17bc2839a74fbe6289e1b262408bb8d8e95d06000700093d00000000000200000002030402000000288205000000000004030200000080b92a000000000001010200000057fd3f4d45b770e4c39707dafb7a2dd6982e43c8b919fb8e8b40a18acada08438fda3a772493ef44de3ab58cb977a67c96ff7e7f417a27e8eaff4e2b4a756407" + ], "outcome": "accept", + "record_count": 5, "segments": 2, "media": "video", "terminated": "ended", @@ -1059,8 +1141,14 @@ { "name": "hdr", "about": "a 2160p120 10-bit high-dynamic-range panel served the 2160p60 10-bit PQ rendition, within it", - "records": 4, + "records": [ + "57534c310101a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c900000000000000000000000000000000000000000000000000000000000000000000000000406cd8e95d06006665497368dd46ba3aa6e984fc13859d200ddf19ba4c9f411bdd1f5201a1b5ee01010101102700000000000000000000000f000070080000c0d401000a01a00500002a03000004800200006801000030750000080100050000d00200003075000008018007000038040000307500000801000f00007008000060ea00000a1042b2969c8e6ce57429a4833a7844eeed5b48d1547c1d9bed74c45dc0e634b3f46430134e9200c358fcf7eff7ddd415fd14bde86b2a09c18e0c4e1e5f35a68e0b", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c901000000be53675afb17b2c1db8782a73033d79c17a1fa3ccad02f72d88fb637dfef43e280c48ad8e95d06008425705c748c7ec8fda9d852a2108760a160964caaad6398a45b78604d26d3c63c00000020a1070000000000000000000000000080841e0000000000030003010000000000000000000320bf02000000000004000061a800000000001e84800004c05c15000000000003c62d7f032d445d54f44d35bcf0d94ddfe78f823a7cafb09eb3bda7d5b5d41785a174fe34011a18c90d9156597de765a998ff92a8f0c378e4c8e8220dad729108", + "57534c310102a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c902000000610bb6876f6d554a0aeeed4c5ede6845126f26d21dfb515479c5d1820a7c4dc80049a9d8e95d0600bde5ac7efa374bd864c6f4924d112a896eb5f327fad4d0f46b4f6f29ba7f169b3c00000020a107000000000080841e000000000000093d0000000000030003010000000000000000000308c302000000000004000061a800000000001e84800004c05c1500000000000319fa4b6a9727c26d3b9fa2d4bfc83a63e10f6beb3289ef6964d857e9159c069c118938e5e89f6fa7a16f8c5743ad2f75e55acbd26302e8a91f03fe6287e07c0e", + "57534c310105a1117c4313a96838832ca189efb4aaf96e8c2ee67d89f34e66ddea2a1cb6d0c903000000c5de5bbb354c8be9d1f863d0832302fb37e344e5fd0f9ed4d78f128bcf626d3d408bb8d8e95d060006c0c62d00000000000200000002030402000000288205000000000004030200000080b92a0000000000010102000000279e80dfa81d68283b5836c7dedb0d3e2c7b2a4a711411d0454ede05bc0edf80b411854b13b786dced6f3e5617d318046427fa4551b3d1ec65412caaa2fa5505" + ], "outcome": "accept", + "record_count": 4, "segments": 2, "media": "video", "terminated": "user", diff --git a/wai/wai-rs/src/browser_sink.rs b/wai/wai-rs/src/browser_sink.rs index 6bb5f2e8e..ec016d282 100644 --- a/wai/wai-rs/src/browser_sink.rs +++ b/wai/wai-rs/src/browser_sink.rs @@ -277,7 +277,7 @@ fn form_matches(form: &str, codec: &str) -> bool { // ─────────────────────────────── §5: constraints ─────────────────────────────── /// capability-constraints §3.1: the register this revision holds. -pub const REGISTER: [&str; 9] = [ +pub const REGISTER: [&str; 10] = [ "wai:codec", "wai:frame_width", "wai:frame_height", @@ -287,6 +287,7 @@ pub const REGISTER: [&str; 9] = [ "wai:sample_rate", "wai:channel_count", "wai:bitrate", + "wai:transfer", ]; /// §5: what evaluating a capability's sets gives. diff --git a/wai/wai-rs/src/stream_termination.rs b/wai/wai-rs/src/stream_termination.rs index b6d4bf0da..50d5fd996 100644 --- a/wai/wai-rs/src/stream_termination.rs +++ b/wai/wai-rs/src/stream_termination.rs @@ -981,7 +981,7 @@ impl Accepted { let sums = self.sums.to_json(); json!({ "outcome": "accept", - "records": self.records, + "record_count": self.records, "segments": self.segments, "media": if self.media == media::VIDEO { "video" } else { "audio" }, "terminated": self.terminated.map(|(w, _)| json!(why::token(w))).unwrap_or(Value::Null), diff --git a/wai/wai-rs/src/stream_termination/corpus.rs b/wai/wai-rs/src/stream_termination/corpus.rs index c9e304f5c..870f5ad55 100644 --- a/wai/wai-rs/src/stream_termination/corpus.rs +++ b/wai/wai-rs/src/stream_termination/corpus.rs @@ -685,7 +685,7 @@ fn log_json(c: &Case) -> Value { m.insert("about".into(), json!(c.about)); m.insert("records".into(), Value::Array(c.records.iter().map(|r| json!(hex(r))).collect())); for (k, v) in got.as_object().expect("an object") { - m.insert(k.clone(), v.clone()); + assert!(m.insert(k.clone(), v.clone()).is_none(), "{}: the fact {k} would replace a member of the log", c.name); } Value::Object(m) } diff --git a/wai/wai-rs/tests/browser_sink.rs b/wai/wai-rs/tests/browser_sink.rs index d2a280ef8..cd781cf43 100644 --- a/wai/wai-rs/tests/browser_sink.rs +++ b/wai/wai-rs/tests/browser_sink.rs @@ -156,3 +156,14 @@ fn the_int_motion_envelope_wraps_the_published_payload() { let digests: Value = serde_json::from_slice(&read(corpus::WIV1_DIGESTS)).unwrap(); assert_eq!(digests["int_video"]["frames_sha256"].as_array().unwrap().len(), 5); } + +/// The register the page evaluates sets over is capability-constraints §3.1's table, name for +/// name and in order. +#[test] +fn the_register_is_capability_constraints_table() { + let text = String::from_utf8(read("extensions/capability-constraints.md")).unwrap(); + let at = text.find("\n### 3.1 ").expect("a §3.1"); + let end = at + text[at..].find("\n## 4.").expect("a §4"); + let listed: Vec<&str> = text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1)).collect(); + assert_eq!(listed, wai::browser_sink::REGISTER, "capability-constraints §3.1 and browser_sink::REGISTER differ"); +} -- 2.54.0 (Apple Git-157) From 26136c03249db2188d5e1df17f835ae51c1a64ad Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 13:13:24 -0400 Subject: [PATCH 033/242] wai: tools to measure what exactness costs int_hyper against its float model wai_determinism_cost.py selects the pinned reference set's self-test pictures, exports mbt2018_mean at all eight published qualities to integer sets (each checkpoint pinned by SHA-256), codes every picture with both codecs, decodes the float streams on any device, and computes distortion, BD-rate and the record's summary. wai_det_energy.py times a decoder child in rounds and reads its counters around them (per-task CPU energy and the GPU rail on macOS, package energy on Linux), with idle brackets. wai_det_cost (wai-rs bin, neural_int; ops_gpu for a GPU) decodes WIH1 payloads through int_payload::decode_int, and serves timed rounds. wai_determinism_cost_verify.py recomputes the record's summary from its readings and checks every figure the text quotes from it. Co-Authored-By: Claude Opus 5.5 --- wai/tools/wai_det_energy.py | 318 +++++++++ wai/tools/wai_determinism_cost.py | 860 +++++++++++++++++++++++ wai/tools/wai_determinism_cost_verify.py | 328 +++++++++ wai/wai-rs/Cargo.toml | 10 + wai/wai-rs/src/bin/wai_det_cost.rs | 255 +++++++ wai/wai-rs/tests/det_cost_bin.rs | 96 +++ 6 files changed, 1867 insertions(+) create mode 100644 wai/tools/wai_det_energy.py create mode 100644 wai/tools/wai_determinism_cost.py create mode 100644 wai/tools/wai_determinism_cost_verify.py create mode 100644 wai/wai-rs/src/bin/wai_det_cost.rs create mode 100644 wai/wai-rs/tests/det_cost_bin.rs diff --git a/wai/tools/wai_det_energy.py b/wai/tools/wai_det_energy.py new file mode 100644 index 000000000..a7d3562d4 --- /dev/null +++ b/wai/tools/wai_det_energy.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +"""Time and energy of a decode, for research/determinism-cost.md: run a decoder as a +child, let it decode a fixed workload in rounds, and read the energy counters around +each round. Standard library only (ctypes for the counters). + + wai_det_energy.py --label L --rounds R --k K [--idle-s S] [--counter C ...] + [--meta JSON] -- + +The child speaks the protocol of `wai_det_cost serve` and `wai_determinism_cost.py +fserve`: it prints `ready ` once loaded and warmed up, then for each `go ` decodes +its n jobs k times and prints `done `, and blocks on its input in +between. Counters (`--counter`, repeatable): + +- `task` (macOS): the child's own CPU energy, `ri_energy_nj` of `proc_pid_rusage` + (RUSAGE_INFO_V6), with its CPU time and the energy billed to it by system services. + Read while the child blocks, after its CPU time has stopped moving (two reads 50 ms + apart agree), so every thread's energy has folded into the count. Scope: this process, + CPU only (the count excludes the GPU and the neural engine). +- `ioreport-gpu` (macOS): the `GPU Energy` rail of the IOReport `Energy Model` group, the + whole GPU. Its share of the work is attributable only where the child held at least 99% + of the GPU time every client accumulated over the round (`accumulatedGPUTime` in the I/O + Registry), which is recorded. +- `rapl` (Linux): powercap `intel-rapl:0` (`package-0`), the whole package. + +A whole-device counter (`ioreport-gpu`, `rapl`) is read every `--sample-ms` (default 100) +through an idle bracket of `--idle-s` seconds (default 5) before each round and after the +last, and through the round. Per round it gives the idle and busy mean power and their +sample standard deviations, the intervals in which the counter advanced, the marginal +energy (busy minus the mean of the brackets on either side, times the window) and the +ratio of the marginal power to the idle standard deviation (energy-measurement §3, §4). +The output is one JSON object on standard output. +""" +import ctypes +import json +import os +import platform +import statistics +import subprocess +import sys +import threading +import time + + +# ---------------------------------------------------------------- macOS: task energy + +class Task: + """`proc_pid_rusage` of another process of this user (RUSAGE_INFO_V6).""" + OFF = {"user": 16, "system": 24, "billed_energy": 264, "serviced_energy": 272, "energy_nj": 336} + + def __init__(self, pid): + self.pid = pid + self.lib = ctypes.CDLL("/usr/lib/libproc.dylib") + self.lib.proc_pid_rusage.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_void_p] + self.lib.proc_pid_rusage.restype = ctypes.c_int + tb = (ctypes.c_uint32 * 2)() + ctypes.CDLL("/usr/lib/libSystem.dylib").mach_timebase_info(ctypes.byref(tb)) + self.tick_ns = tb[0] / tb[1] + + def read(self): + buf = (ctypes.c_uint8 * 512)() + if self.lib.proc_pid_rusage(self.pid, 6, buf) != 0: + raise OSError("proc_pid_rusage failed") + u = lambda o: int.from_bytes(bytes(buf[o:o + 8]), "little") + return {"energy_nj": u(336), "cpu_ns": (u(16) + u(24)) * self.tick_ns, + "billed_nj": u(264), "serviced_nj": u(272)} + + def settled(self): + """A read once the CPU time has stopped moving: two reads 50 ms apart agree.""" + prev = self.read() + for _ in range(200): + time.sleep(0.05) + cur = self.read() + if cur["cpu_ns"] == prev["cpu_ns"] and cur["energy_nj"] == prev["energy_nj"]: + return cur + prev = cur + raise RuntimeError("the child's CPU time never settled") + + +# ---------------------------------------------------------------- macOS: IOReport GPU rail + +class IOReportGPU: + UNIT = {"nJ": 1, "uJ": 1_000, "mJ": 1_000_000, "J": 1_000_000_000, "pJ": 0.001} + + def __init__(self): + c, v = ctypes.c_void_p, ctypes + self.cf = cf = v.CDLL("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation") + self.io = io = v.CDLL("/usr/lib/libIOReport.dylib") + for f, res, args in ( + (cf.CFStringCreateWithCString, c, [c, v.c_char_p, v.c_uint32]), + (cf.CFDictionaryGetCount, v.c_long, [c]), + (cf.CFDictionaryCreateMutableCopy, c, [c, v.c_long, c]), + (cf.CFDictionaryGetValue, c, [c, c]), + (cf.CFArrayGetCount, v.c_long, [c]), + (cf.CFArrayGetValueAtIndex, c, [c, v.c_long]), + (cf.CFStringGetCString, v.c_bool, [c, v.c_char_p, v.c_long, v.c_uint32]), + (cf.CFRelease, None, [c]), + (io.IOReportCopyChannelsInGroup, c, [c, c, v.c_uint64, v.c_uint64, v.c_uint64]), + (io.IOReportCreateSubscription, c, [c, c, v.POINTER(c), v.c_uint64, c]), + (io.IOReportCreateSamples, c, [c, c, c]), + (io.IOReportCreateSamplesDelta, c, [c, c, c]), + (io.IOReportChannelGetChannelName, c, [c]), + (io.IOReportChannelGetUnitLabel, c, [c]), + (io.IOReportSimpleGetIntegerValue, v.c_int64, [c, v.c_int32]), + ): + f.restype, f.argtypes = res, args + chans = io.IOReportCopyChannelsInGroup(self._s("Energy Model"), None, 0, 0, 0) + self.chan = cf.CFDictionaryCreateMutableCopy(None, cf.CFDictionaryGetCount(chans), chans) + subbed = c() + self.sub = io.IOReportCreateSubscription(None, self.chan, ctypes.byref(subbed), 0, None) + self.key = self._s("IOReportChannels") + + def _s(self, s): + return self.cf.CFStringCreateWithCString(None, s.encode(), 0x08000100) + + def _str(self, ref): + b = ctypes.create_string_buffer(256) + return b.value.decode() if ref and self.cf.CFStringGetCString(ref, b, 256, 0x08000100) else "" + + def sample(self): + return self.io.IOReportCreateSamples(self.sub, self.chan, None) + + def delta_nj(self, a, b): + """The GPU Energy rail's advance from sample a to sample b, in nJ.""" + d = self.io.IOReportCreateSamplesDelta(a, b, None) + arr = self.cf.CFDictionaryGetValue(d, self.key) + tot = 0 + for i in range(self.cf.CFArrayGetCount(arr)): + ch = self.cf.CFArrayGetValueAtIndex(arr, i) + if self._str(self.io.IOReportChannelGetChannelName(ch)) == "GPU Energy": + unit = self._str(self.io.IOReportChannelGetUnitLabel(ch)).strip() + tot += self.io.IOReportSimpleGetIntegerValue(ch, 0) * self.UNIT[unit] + self.cf.CFRelease(d) + return tot + + def release(self, s): + self.cf.CFRelease(s) + + +def gpu_times(): + """accumulatedGPUTime (ns) summed per process over every I/O Registry accelerator + client, from `ioreg`: each client node's `AppUsage` entries go to the pid its + `IOUserClientCreator` names, whichever of the two the node lists first.""" + import re + out = subprocess.run(["ioreg", "-r", "-c", "IOAccelerator", "-l", "-w0"], capture_output=True, text=True).stdout + per = {} + for node in re.split(r"\n\s*[|\s]*\+-o ", out): + m = re.search(r'"IOUserClientCreator" = "pid (\d+),', node) + if not m: + continue + t = sum(int(x) for x in re.findall(r'"accumulatedGPUTime"=(\d+)', node)) + per[int(m.group(1))] = per.get(int(m.group(1)), 0) + t + return per + + +# ---------------------------------------------------------------- Linux: RAPL + +class Rapl: + def __init__(self, zone="/sys/class/powercap/intel-rapl:0"): + self.f = os.path.join(zone, "energy_uj") + self.name = open(os.path.join(zone, "name")).read().strip() + self.wrap = int(open(os.path.join(zone, "max_energy_range_uj")).read()) + + def read_uj(self): + return int(open(self.f).read()) + + def delta_nj(self, a, b): + d = b - a + if d < 0: + d += self.wrap + return d * 1000 + + +# ---------------------------------------------------------------- sampling + +class Sampler: + """Reads one whole-device counter every period: (monotonic ns, interval nJ) pairs.""" + + def __init__(self, kind, period): + self.kind, self.period = kind, period + self.dev = IOReportGPU() if kind == "ioreport-gpu" else Rapl() + self.lock = threading.Lock() + self.samples = [] + self.stop = False + self.t = threading.Thread(target=self.loop, daemon=True) + self.t.start() + + def _read(self): + return self.dev.sample() if self.kind == "ioreport-gpu" else self.dev.read_uj() + + def loop(self): + prev, tp = self._read(), time.monotonic_ns() + while not self.stop: + time.sleep(self.period) + cur, tc = self._read(), time.monotonic_ns() + nj = self.dev.delta_nj(prev, cur) + if self.kind == "ioreport-gpu": + self.dev.release(prev) + with self.lock: + self.samples.append((tp, tc, nj)) + prev, tp = cur, tc + + def between(self, t0, t1): + """The intervals that lie inside [t0, t1].""" + with self.lock: + return [s for s in self.samples if s[0] >= t0 and s[1] <= t1] + + def covering(self, t0, t1): + """The intervals that overlap [t0, t1]: the window's energy, its edges included.""" + with self.lock: + return [s for s in self.samples if s[1] > t0 and s[0] < t1] + + +def bracket(iv): + """Mean and sample standard deviation of the power (W) over intervals, and the + liveness facts of energy-measurement §3.1.""" + if len(iv) < 2: + return None + p = [nj / (t1 - t0) for (t0, t1, nj) in iv] # nJ/ns = W + adv = [nj > 0 for (_, _, nj) in iv] + run = best = 0 + for a in adv: + run = run + 1 if a else 0 + best = max(best, run) + return {"intervals": len(iv), "advanced": sum(adv), "longest_run": best, + "span_ns": iv[-1][1] - iv[0][0], "mean_w": statistics.mean(p), "sd_w": statistics.stdev(p)} + + +def verdict(ratio): + if ratio is None: + return None + return "clean" if ratio >= 10 else "relative-only" if ratio >= 3 else "noisy" if ratio >= 1 else "unusable" + + +def main(argv): + a = argv[1:] + sep = a.index("--") + opts, child = a[:sep], a[sep + 1:] + def opt(name, default=None, many=False): + vals = [opts[i + 1] for i, o in enumerate(opts) if o == name] + return vals if many else (vals[-1] if vals else default) + label, rounds, k = opt("--label"), int(opt("--rounds", "7")), int(opt("--k", "1")) + idle_s, period = float(opt("--idle-s", "5")), float(opt("--sample-ms", "100")) / 1000 + counters = opt("--counter", many=True) + samplers = {c: Sampler(c, period) for c in counters if c in ("ioreport-gpu", "rapl")} + p = subprocess.Popen(child, stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, bufsize=1) + line = p.stdout.readline().split() + if not line or line[0] != "ready": + sys.exit(f"the child did not get ready: {line}") + jobs = int(line[1]) + task = Task(p.pid) if "task" in counters else None + out = {"label": label, "meta": json.loads(opt("--meta", "{}")), "rounds": [], "jobs": jobs, "k": k, + "counters": counters, "idle_s": idle_s, "sample_s": period, + "host": {"system": platform.system(), "machine": platform.machine(), "node_cpu": os.cpu_count()}, + "child": child} + def idle(): + t0 = time.monotonic_ns() + time.sleep(idle_s) + return t0, time.monotonic_ns() + brackets = [idle()] + for r in range(rounds): + rec = {"load_avg_before": os.getloadavg()} + t_before = task.settled() if task else None + g_before = gpu_times() if "ioreport-gpu" in counters else None + t0 = time.monotonic_ns() + p.stdin.write(f"go {k}\n") + p.stdin.flush() + done = p.stdout.readline().split() + t1 = time.monotonic_ns() + if not done or done[0] != "done": + sys.exit(f"round {r}: the child answered {done}") + rec.update({"decodes": int(done[1]), "child_wall_ns": int(done[2]), "window_ns": t1 - t0}) + if len(done) > 3: + rec["child_note"] = " ".join(done[3:]) + if task: + t_after = task.settled() + rec["task"] = {kk: t_after[kk] - t_before[kk] for kk in t_after} + if g_before is not None: + g_after = gpu_times() + dt = {pid: g_after.get(pid, 0) - g_before.get(pid, 0) for pid in set(g_after) | set(g_before)} + total = sum(v for v in dt.values() if v > 0) + mine = dt.get(p.pid, 0) + rec["gpu_time"] = {"child_ns": mine, "all_clients_ns": total, + "share": (mine / total) if total else None, + "vanished": sorted(pid for pid in g_before if pid not in g_after)} + rec["load_avg_after"] = os.getloadavg() + brackets.append(idle()) + for c, s in samplers.items(): + time.sleep(2 * period) + busy = bracket(s.between(t0, t1)) + cov = s.covering(t0, t1) + pre, post = bracket(s.between(*brackets[-2])), bracket(s.between(*brackets[-1])) + e = {"busy": busy, "idle_before": pre, "idle_after": post, + "window_nj": sum(nj for (_, _, nj) in cov), + "covered_ns": (cov[-1][1] - cov[0][0]) if cov else 0} + if busy and pre and post: + idle_w = (pre["mean_w"] + post["mean_w"]) / 2 + idle_sd = max(pre["sd_w"], post["sd_w"]) + marg_w = busy["mean_w"] - idle_w + e["marginal_nj"] = e["window_nj"] - idle_w * e["covered_ns"] + e["ratio"] = (marg_w / idle_sd) if idle_sd > 0 else None + e["verdict"] = verdict(e["ratio"]) + rec[c] = e + out["rounds"].append(rec) + print(f"{label} round {r + 1}/{rounds}: {rec['decodes']} decodes in {rec['child_wall_ns'] / 1e9:.2f} s" + + (f", task {rec['task']['energy_nj'] / 1e9:.2f} J" if task else "") + + "".join(f", {c} marginal {rec[c].get('marginal_nj', 0) / 1e9:.2f} J ({rec[c].get('verdict')})" for c in samplers), + file=sys.stderr, flush=True) + p.stdin.write("exit\n") + p.stdin.flush() + p.wait(timeout=60) + for s in samplers.values(): + s.stop = True + json.dump(out, sys.stdout, indent=1) + print() + + +if __name__ == "__main__": + main(sys.argv) diff --git a/wai/tools/wai_determinism_cost.py b/wai/tools/wai_determinism_cost.py new file mode 100644 index 000000000..9f6949a1e --- /dev/null +++ b/wai/tools/wai_determinism_cost.py @@ -0,0 +1,860 @@ +#!/usr/bin/env python3 +"""What exactness costs `wai.neural.int_hyper`: the measurement behind +research/determinism-cost.md and its record, research/determinism-cost/record.json. + +The float codec is the pretrained mean-scale hyperprior `mbt2018_mean` (MSE, qualities +1-8) as its own library codes and decodes it. The integer codec is the same model exported +the way tools/wai_sota_int_synth_export.py exports quality 6 (per-channel int12 weights, +activations at 2^12, integer IGDN, the integer hyper-decoder deciding every scale bucket), +coded as a `WIH1` payload (extensions/integer-payloads.md §4) and decoded by the reference +decoder (`wai_det_cost`, which runs `int_payload::decode_int`). Every step writes files +under one work directory, so each can be rerun alone and on another machine. + + wai_determinism_cost.py select pick and check the pictures + wai_determinism_cost.py export [q ...] pinned checkpoints -> integer sets + wai_determinism_cost.py encode [q ...] both codecs; the float decode on this CPU + wai_determinism_cost.py jobs the integer decode's job list + wai_determinism_cost.py fdecode [--device cpu|mps|cuda] [--threads N] + decode every float stream again + wai_determinism_cost.py fserve [--device D] [--threads N] [--ids a,b] + the timed float child of wai_det_energy.py + wai_determinism_cost.py metrics distortion of every decode + wai_determinism_cost.py bd BD-rate, integer against float + wai_determinism_cost.py record the record: raw readings and their summary + +Needs the pinned stack of tools/requirements-sota-int.txt for `export`, `encode` and +`fdecode`; `select`, `jobs`, `metrics` and `bd` need numpy and Pillow only. +""" +import hashlib +import importlib.util +import io +import json +import math +import os +import struct +import sys + +import numpy as np + +HERE = os.path.dirname(os.path.abspath(__file__)) +QUALITIES = list(range(1, 9)) + +# The published mbt2018_mean (MSE) checkpoints, by quality: the file the library's zoo +# names, and the SHA-256 of its bytes, measured when this record was made. A checkpoint +# with other bytes is refused before it is loaded. Quality 6 is the one +# wai_sota_int_synth_export.py pins, and its digest is the same. +CKPT_SHA256 = { + 1: "e522738dc4599d1538fcdb16e21aa56df9146d60e8e083542466b387c1960156", + 2: "e54a039d587afddd086b836cdd8eed4e0343d7cdd91028489ad0f3e7a72d307d", + 3: "723404a8a039371abe8a14f05160ee1614cf5e0a3fb2a4e5f7ba569b68c22601", + 4: "6dba02a30c78ff78baafd2072dd78646c27af6c4dc8416097c2c2858c1f186ce", + 5: "d504e8ebe7e6396efd0b5fd093a32136714fd7e4be03db389e6f07aee24fdb39", + 6: "a19628aba19732125e02caf5c38e233edbe2a641145a5b1779c51f4527f51dd6", + 7: "d5d441d16ba3e5a80ead947e35d2dddc12fb0d9785a8006e9b35a0cef7668d91", + 8: "8089ae3e23d9df5b7c68107c82c31da21019147aedddb03f03350de58d5385b7", +} + +# The pictures: the reference manifest's PNG entries that it marks `self_test`, each one +# picture of 768x512 or 512x768 (wai/tools/wai_visual_provenance_check.py's manifest). +MANIFEST = "wai-image-references.json" +MANIFEST_SHA256 = "db33c6633f89a66d6742a37cd348208792f6d49810da739ddf26d10dc6554b66" + + +def sha256(b): + return hashlib.sha256(b).hexdigest() + + +def jdump(obj, path): + with open(path, "w") as f: + json.dump(obj, f, indent=1, sort_keys=True) + f.write("\n") + + +def jload(path): + with open(path) as f: + return json.load(f) + + +def export_module(): + spec = importlib.util.spec_from_file_location("sota_export", os.path.join(HERE, "wai_sota_int_synth_export.py")) + m = importlib.util.module_from_spec(spec) + spec.loader.exec_module(m) + return m + + +# ---------------------------------------------------------------- select + +def cmd_select(work, root): + raw = open(os.path.join(root, MANIFEST), "rb").read() + if sha256(raw) != MANIFEST_SHA256: + sys.exit(f"the manifest's SHA-256 is {sha256(raw)}, not the pinned {MANIFEST_SHA256}") + man = json.loads(raw) + from PIL import Image + pics = [] + for e in man["entries"]: + if not (e.get("self_test") and e["path"].endswith(".png") and len(e["images"]) == 1): + continue + im = e["images"][0] + if (im["width"], im["height"]) not in ((768, 512), (512, 768)): + continue + b = open(os.path.join(root, e["path"]), "rb").read() + if sha256(b) != e["sha256"]: + sys.exit(f"an entry's bytes do not match the manifest ({e['sha256'][:16]})") + px = np.array(Image.open(io.BytesIO(b)).convert("RGB")) + h, w = px.shape[:2] + assert h % 64 == 0 and w % 64 == 0 + pid = e["sha256"][:16] + os.makedirs(os.path.join(work, "src"), exist_ok=True) + px.tofile(os.path.join(work, "src", f"{pid}.rgb")) + pics.append({"id": pid, "file_sha256": e["sha256"], "width": int(w), "height": int(h), + "rgb_sha256": sha256(px.tobytes())}) + jdump({"manifest_sha256": MANIFEST_SHA256, + "rule": "PNG entries marked self_test with one picture of 768x512 or 512x768", + "pictures": pics}, os.path.join(work, "pictures.json")) + print(f"{len(pics)} pictures") + + +def pictures(work): + return jload(os.path.join(work, "pictures.json"))["pictures"] + + +def source_rgb(work, p): + return np.fromfile(os.path.join(work, "src", f"{p['id']}.rgb"), dtype=np.uint8).reshape(p["height"], p["width"], 3) + + +# ---------------------------------------------------------------- models + +def load_model(q, allow_new=False): + """mbt2018_mean (MSE) at quality q from the published checkpoint, its bytes checked + against CKPT_SHA256 before they are loaded, as tensors only.""" + import torch + from compressai.zoo import image as zoo_image + from compressai.zoo.pretrained import load_pretrained + url = zoo_image.model_urls["mbt2018-mean"]["mse"][q] + path = os.path.join(torch.hub.get_dir(), "checkpoints", os.path.basename(url)) + if not os.path.exists(path): + os.makedirs(os.path.dirname(path), exist_ok=True) + torch.hub.download_url_to_file(url, path, progress=False) + blob = open(path, "rb").read() + got = sha256(blob) + # The zoo's file names carry the first eight hex digits of the digest. + if not os.path.basename(url).split("-")[-1].startswith(got[:8]): + sys.exit(f"q{q}: {path} has SHA-256 {got}, which does not match the prefix in its name") + want = CKPT_SHA256[q] + if want is None and not allow_new: + sys.exit(f"q{q}: no pinned digest; measured {got}") + if want is not None and got != want: + sys.exit(f"q{q}: {path} has SHA-256 {got}, pinned {want}") + state = torch.load(io.BytesIO(blob), map_location="cpu", weights_only=True) + m = zoo_image.model_architectures["mbt2018-mean"].from_state_dict(load_pretrained(state)).eval() + return m, {"quality": q, "url_name": os.path.basename(url), "sha256": got} + + +# ---------------------------------------------------------------- integer export + +def int_net(E, seq, kind): + """One integer network from a float Sequential, quantised as E (the quality-6 export + tool) quantises g_s and h_s.""" + net = {"afrac": E.AFRAC, "gf": E.GF, "shift": E.SHIFT, "layers": []} + import torch + for layer in seq.children(): + t = type(layer).__name__ + if t == "ConvTranspose2d": + wi, sc = E.q_weight_pc(layer.weight.detach().numpy()) + net["layers"].append({"type": "convT", "in_ch": int(wi.shape[0]), "out_ch": int(wi.shape[1]), + "k": int(wi.shape[2]), "stride": int(layer.stride[0]), "pad": int(layer.padding[0]), + "opad": int(layer.output_padding[0]), "weight": wi.reshape(-1).tolist(), + "mult": np.round(sc * (2 ** E.SHIFT)).astype(np.int64).tolist(), + "bias": np.round(layer.bias.detach().numpy() * (2 ** E.AFRAC)).astype(np.int64).tolist()}) + elif t == "Conv2d": + wi, sc = E.q_weight_conv(layer.weight.detach().numpy()) + net["layers"].append({"type": "conv", "in_ch": int(wi.shape[1]), "out_ch": int(wi.shape[0]), + "k": int(wi.shape[2]), "stride": int(layer.stride[0]), "pad": int(layer.padding[0]), + "weight": wi.reshape(-1).tolist(), + "mult": np.round(sc * (2 ** E.SHIFT)).astype(np.int64).tolist(), + "bias": np.round(layer.bias.detach().numpy() * (2 ** E.AFRAC)).astype(np.int64).tolist()}) + elif t == "GDN": + with torch.no_grad(): + beta = layer.beta_reparam(layer.beta).numpy() + C = beta.shape[0] + gamma = layer.gamma_reparam(layer.gamma).reshape(C, C).numpy() + net["layers"].append({"type": "igdn", "c": int(C), + "gamma": np.round(gamma * (2 ** E.GF)).astype(np.int64).reshape(-1).tolist(), + "beta": np.round(beta * (2 ** (2 * E.AFRAC))).astype(np.int64).tolist()}) + elif t == "ReLU": + net["layers"].append({"type": "relu"}) + elif t == "LeakyReLU": + net["layers"].append({"type": "leaky", "slope_q": 655, "slope_frac": 16}) + else: + sys.exit(f"{kind}: no integer form for a {t} layer") + return net + + +def requant(raw, mult, bias, shift): + """((raw * mult) >> shift) + bias per output channel, exact: in int64 where the + product provably fits, else in Python integers.""" + if np.abs(raw).max(initial=0) * np.abs(mult).max(initial=0) < 2 ** 62: + return ((raw * mult[:, None, None]) >> shift) + bias[:, None, None] + return ((raw.astype(object) * mult[:, None, None]) >> shift).astype(np.int64) + bias[:, None, None] + + +def conv_t_fast(x, L, shift): + """The integer transposed convolution of `int_hyper_synth` (weight [in,out,k,k]), + tap by tap: out[:, iy*s+ky, ix*s+kx] += w[:, :, ky, kx]^T x[:, iy, ix]. Integer sums + are exact in any order, so this equals the export tool's per-pixel scatter.""" + cin, co, k, s, p, op = L["in_ch"], L["out_ch"], L["k"], L["stride"], L["pad"], L["opad"] + w = np.array(L["weight"], dtype=np.int64).reshape(cin, co, k, k) + _, H, W = x.shape + Ho, Wo = (H - 1) * s + k + op - 2 * p, (W - 1) * s + k + op - 2 * p + raw = np.zeros((co, Ho + 2 * p, Wo + 2 * p), dtype=np.int64) + xf = x.reshape(cin, -1) + assert np.abs(x).max(initial=0) * np.abs(w).max(initial=0) * cin * k * k < 2 ** 63 + for ky in range(k): + for kx in range(k): + c = (w[:, :, ky, kx].T @ xf).reshape(co, H, W) + raw[:, ky:ky + s * H:s, kx:kx + s * W:s] += c + raw = raw[:, p:p + Ho, p:p + Wo] + return requant(raw, np.array(L["mult"], dtype=np.int64), np.array(L["bias"], dtype=np.int64), shift) + + +def conv_fast(x, L, shift): + cin, co, k, s, p = L["in_ch"], L["out_ch"], L["k"], L["stride"], L["pad"] + w = np.array(L["weight"], dtype=np.int64).reshape(co, cin, k, k) + _, H, W = x.shape + Ho, Wo = (H + 2 * p - k) // s + 1, (W + 2 * p - k) // s + 1 + xp = np.zeros((cin, H + 2 * p, W + 2 * p), dtype=np.int64) + xp[:, p:p + H, p:p + W] = x + assert np.abs(x).max(initial=0) * np.abs(w).max(initial=0) * cin * k * k < 2 ** 63 + raw = np.zeros((co, Ho, Wo), dtype=np.int64) + for ky in range(k): + for kx in range(k): + sub = xp[:, ky:ky + s * Ho:s, kx:kx + s * Wo:s].reshape(cin, -1) + raw += (w[:, :, ky, kx] @ sub).reshape(co, Ho, Wo) + return requant(raw, np.array(L["mult"], dtype=np.int64), np.array(L["bias"], dtype=np.int64), shift) + + +def run_hs(net, x): + """The integer hyper-decoder (convolutions and ReLUs only), as `int_hyper_synth` + runs it.""" + for L in net["layers"]: + t = L["type"] + if t == "convT": + x = conv_t_fast(x, L, net["shift"]) + elif t == "conv": + x = conv_fast(x, L, net["shift"]) + elif t == "relu": + x = np.maximum(x, 0) + elif t == "leaky": + x = np.where(x >= 0, x, (x * L["slope_q"]) >> L["slope_frac"]) + else: + sys.exit(f"h_s has a {t} layer") + return x + + +def cmd_export(work, qs, allow_new=False): + """Per quality, the integer set (integer-payloads §9: entropy.json, g_s.whm, h_s.whm) + and the model's identity.""" + import torch + from compressai.entropy_models import GaussianConditional + torch.use_deterministic_algorithms(True) + torch.set_num_threads(1) + E = export_module() + out = {} + for q in qs: + m, ident = load_model(q, allow_new) + g_s = int_net(E, m.g_s, "g_s") + h_s = int_net(E, m.h_s, "h_s") + eb = m.entropy_bottleneck + eb.update() + median = eb._get_medians().reshape(-1).detach().numpy().astype(np.float64) + scale_table = list(torch.exp(torch.linspace(np.log(0.11), np.log(256.0), 64)).numpy().astype(float)) + gc = GaussianConditional(scale_table) + gc.update() + ent = {"afrac": E.AFRAC, "frac": E.AFRAC, + "scale_edges": np.floor(np.array(scale_table) * (2 ** E.AFRAC)).astype(np.int64).tolist(), + "cdfs": gc._quantized_cdf.tolist(), "cdf_lengths": gc._cdf_length.reshape(-1).int().tolist(), + "offsets": gc._offset.reshape(-1).int().tolist(), + "z_cdfs": eb._quantized_cdf.tolist(), "z_cdf_lengths": eb._cdf_length.reshape(-1).int().tolist(), + "z_offsets": eb._offset.reshape(-1).int().tolist(), + "z_median_q": np.round(median * (2 ** E.AFRAC)).astype(np.int64).tolist()} + d = os.path.join(work, "sets", f"q{q}") + os.makedirs(d, exist_ok=True) + open(os.path.join(d, "g_s.whm"), "wb").write(E.model_to_bin(g_s)) + open(os.path.join(d, "h_s.whm"), "wb").write(E.model_to_bin(h_s)) + with open(os.path.join(d, "entropy.json"), "w") as f: + json.dump(ent, f) + jdump(h_s, os.path.join(d, "h_s.json")) # the encoder's copy of h_s + ident["set"] = {n: sha256(open(os.path.join(d, n), "rb").read()) for n in ("entropy.json", "g_s.whm", "h_s.whm")} + ident["N"], ident["M"] = int(m.N), int(m.M) + out[q] = ident + print(f"q{q}: N={m.N} M={m.M} checkpoint {ident['sha256'][:16]}… set g_s {ident['set']['g_s.whm'][:16]}…") + path = os.path.join(work, "models.json") + prev = jload(path) if os.path.exists(path) else {} + prev.update({str(k): v for k, v in out.items()}) + jdump(prev, path) + + +# ---------------------------------------------------------------- encode + +def to_rgb8(x_hat): + """A float reconstruction in [0, 1] as 8-bit samples: x·255 rounded half to even, as + the float library's own tensor arithmetic gives it on the device it ran on.""" + import torch + return (x_hat.clamp(0, 1) * 255).round().to(torch.uint8)[0].permute(1, 2, 0).contiguous().cpu().numpy() + + +def float_container(strings, zshape): + y, z = strings[0][0], strings[1][0] + return b"DETF" + struct.pack(" python3 -I sink_energy_verify.py --self-test -Exit status 0 only if every check holds and every mutated copy fails. Python 3.8 or later; +`--table ` verifies one measured table directory, /table.json, readings.json and +procedure.txt, exactly as each tables// of the corpus is verified: the table accepted +(§7 steps 1–4) with its readings held, `readings_sha256` the SHA-256 of readings.json's +bytes and `procedure_sha256` that of procedure.txt's, and its rows summarised. The hardware +jobs of continuous integration run it on a table the procedure has just written (§9). + +Exit status 0 only if every check holds and every mutated copy fails (with `--table`, only +if that table holds). Python 3.8 or later; standard library only. """ import copy @@ -78,7 +86,7 @@ RANK = {c: i for i, c in enumerate(CLASSES)} HARDWARE = ("CalibratedInstrument", "OnChipCounter") FILTERING = ("On", "Off", "Unknown") COUNTER_STATUS = ("live", "frozen", "needs-privilege", "no-counter", "not-implemented", "implausible") -ABSENT_REASONS = ("no_decoder", "no_hardware_path", "no_counter") +ABSENT_REASONS = ("no_decoder", "no_work_item", "no_hardware_path", "no_counter") ROUND_RULES = ("too_few_samples", "rail_not_live", "window_short", "empty_burst", "no_marginal", "below_attribution_floor", "figure_zero", "not_declarable") ROW_REASONS = ABSENT_REASONS + ROUND_RULES + ("noisy",) # §8 *row reasons* @@ -366,7 +374,7 @@ SCHEMA = { "reason": nul(enum("not_reported", "hop_unmetered", "unmetered", "display_undeclared")), "coverage": COVERAGE, "source": nul(obj({"kind": enum("live-metering-hop", "other"), "record": HEX32, "hop_joules": INT, - "hop_class": nul(STR), "share_num": INT, "share_den": INT})), + "hop_class": STR, "share_num": INT, "share_den": INT})), }, **CLASS_MEMBERS)) for p in PARTS}), "sig": HEX64, }), @@ -406,7 +414,7 @@ def shape(rec, kind): need(unit_ok(row), "record_malformed", "row %d: width, height or units (§7 step 1)" % i) need(row["capability"] in registry(), "record_malformed", "row %d: `%s` is not a capability the registry lists (§4.2)" % (i, row["capability"])) - keys = [(c["profile"], c["detail"]) for c in rec["counters"]] + keys = [(c["profile"].encode("utf-8"), c["detail"].encode("utf-8")) for c in rec["counters"]] need(all(a <= b for a, b in zip(keys, keys[1:])), "record_malformed", "`counters` is not in ascending order of (profile, detail) (§4.1)") elif kind == SEGMENT: @@ -414,6 +422,8 @@ def shape(rec, kind): need(rec["capability"] in registry(), "record_malformed", "`%s` is not a capability the registry lists (§4.2, §5.1)" % rec["capability"]) need(rec["media_us"] >= 1, "record_malformed", "`media_us` is below 1 (§5.1)") + need((rec["prev"] is None) == (rec["segment"] == 0), "record_malformed", + "`prev` is not null exactly when `segment` is 0 (§5.1, §7 step 1)") else: keys = [s["stream"] for s in rec["streams"]] need(len(keys) >= 1 and all(a < b for a, b in zip(keys, keys[1:])), "record_malformed", @@ -468,6 +478,8 @@ def check_class(x, where): prov, unc = x["energy_provenance"], x["energy_uncertainty"] filt, ref, j = x["energy_counter_filtering"], x["energy_calibration_ref"], x["joules_micro"] need(prov != "HwShunt", "hwshunt_forbidden", "%s: the class is HwShunt (energy-measurement §2.6)" % where) + need((prov is None) == (j == 0), "unmetered_as_zero", + "%s: %s beside a figure of %d (energy-measurement §2)" % (where, prov or "no class", j)) if prov is None: need(unc is None and filt is None and ref is None, "energy_class_invalid", "%s: evidence without a class (energy-measurement §2.7)" % where) @@ -488,13 +500,11 @@ def check_class(x, where): need(j * unc["relative_ppm"] >= FLOOR, "energy_class_invalid", "%s: %d x %d is below the resolution floor 288 676 (energy-measurement §2.3)" % (where, j, unc["relative_ppm"])) - need((prov is None) == (j == 0), "unmetered_as_zero", - "%s: %s beside a figure of %d (energy-measurement §2)" % (where, prov or "no class", j)) def check_hop_class(label, where): need(label != "HwShunt", "hwshunt_forbidden", "%s: the hop's class is HwShunt (§6.4)" % where) - need(label in CLASSES, "energy_class_invalid", "%s: `hop_class` %r is no class (§7 step 3)" % (where, label)) + need(label in CLASSES, "energy_class_invalid", "%s: `hop_class` %r is not a label of a class (§7 step 3)" % (where, label)) # ---------------------------------------------------------------- the integer forms @@ -594,6 +604,8 @@ def round_figure(rd, workers, minimum, sample_us): f = figure_of(m, t, wn, 2) if f < 1: return "figure_zero" + if f > MAX: + return "not_declarable" p = least_p(i["n"], i["sd_uw"], b["n"], b["sd_uw"], m, t, wn, 2, workers, f) if p is None or p > U32: return "not_declarable" @@ -620,6 +632,8 @@ def row_outcome(row, sample_us): return {"status": "unmetered", "reason": "noisy", "round": med} lo, hi = min(x[0] for x in figs), max(x[0] for x in figs) q = least_sq(12 * f * f, 10 ** 12 * (hi - lo) ** 2) + if max(p, q) > U32: + return {"status": "unmetered", "reason": "not_declarable", "round": med} return {"status": status, "reason": reason, "median": med, "range": [lo, hi], "F": f, "p": p, "q": q, "window": row["rounds"][med]["window_us"]} @@ -635,15 +649,16 @@ def marker(x): def row_key(row): - return (row["capability"], row["path"], (0, "") if row["profile"] is None else (1, row["profile"])) + """§4.1: (capability, path, decoder, profile), strings by their bytes, a null profile first.""" + return (row["capability"].encode("utf-8"), row["path"].encode("utf-8"), row["decoder"].encode("utf-8"), + (0, b"") if row["profile"] is None else (1, row["profile"].encode("utf-8"))) # ---------------------------------------------------------------- §7 step 4: the decode table def readings_bytes_of(readings): - """The bytes a readings file held inline in a case is digested as: its JCS form (an - assumption, reported as such: §4.1 digests "the readings file", and a case carries it - as an object).""" + """§4.3: the readings file is the JCS form of its object, so one held inline as an + object is digested as its JCS bytes.""" try: return jcs(readings) except (ValueError, TypeError, AttributeError): @@ -668,9 +683,10 @@ def check_readings(rec, readings, raw): need(len(readings["rows"]) == len(rec["rows"]), "table_readings_mismatch", "the readings file has %d entries for %d rows" % (len(readings["rows"]), len(rec["rows"]))) for i, (e, row) in enumerate(zip(readings["rows"], rec["rows"])): - need(isinstance(e, dict) and not getattr(e, "dup", False) and set(e) == {"capability", "path", "profile", "rounds"} + need(isinstance(e, dict) and not getattr(e, "dup", False) and set(e) == {"capability", "path", "decoder", "profile", "rounds"} and type(e["rounds"]) is list, "table_readings_mismatch", "entry %d does not read" % i) - need(e["capability"] == row["capability"] and e["path"] == row["path"] and e["profile"] == row["profile"] + need(e["capability"] == row["capability"] and e["path"] == row["path"] and e["decoder"] == row["decoder"] + and e["profile"] == row["profile"] and (e["profile"] is None) == (row["profile"] is None), "table_readings_mismatch", "entry %d does not name row %d" % (i, i)) need(len(e["rounds"]) == len(row["rounds"]), "table_readings_mismatch", @@ -718,14 +734,14 @@ def verify_table(rec, readings=None, raw=None, held_readings=False): need((row["status"], row["reason"]) == (out["status"], out["reason"]), "table_row_status", "%s: states %s/%s, its rounds give %s/%s (round %s)" % (where, row["status"], row["reason"], out["status"], out["reason"], out.get("round"))) + if row["status"] in ("absent", "unmetered"): + need(marker(row), "table_row_status", "%s: an %s row without the unmetered marker (§7 step 4)" % (where, row["status"])) if row["profile"] is not None: is_live = row["profile"] in live_profiles need(is_live == (not (row["status"] == "absent" and row["reason"] == "no_counter")), "table_row_status", "%s: its profile is %slive in `counters`" % (where, "" if is_live else "not ")) # its figure - if row["status"] == "absent": - need(marker(row), "table_row_figure", "%s: an absent row without the unmetered marker (§4.5)" % where) - else: + if row["status"] != "absent": need(row["fabrics"] == FABRICS[row["profile"]], "table_row_figure", "%s: fabrics %r, not its profile's %r (§4.2)" % (where, row["fabrics"], FABRICS[row["profile"]])) need(row["coverage"] == "Partial", "table_row_figure", "%s: coverage %r, not Partial (§4.5)" % (where, row["coverage"])) @@ -736,20 +752,21 @@ def verify_table(rec, readings=None, raw=None, held_readings=False): "%s: range %r, its rounds give %r" % (where, row["range"], out["range"])) need(row["joules_micro"] == out["F"], "table_row_figure", "%s: joules_micro %d, the median's figure is %d" % (where, row["joules_micro"], out["F"])) - need(row["energy_provenance"] in HARDWARE, "table_row_figure", - "%s: class %s, not a hardware class" % (where, row["energy_provenance"])) + need(row["energy_provenance"] == "CalibratedInstrument" or (row["energy_provenance"] == "OnChipCounter" + and row["energy_counter_filtering"] == "Unknown"), "table_row_figure", + "%s: class %s/%s, not OnChipCounter with filtering Unknown or CalibratedInstrument" + % (where, row["energy_provenance"], row["energy_counter_filtering"])) need(row["energy_uncertainty"]["window_us"] == out["window"], "table_row_figure", "%s: window_us %d, the median round's is %d" % (where, row["energy_uncertainty"]["window_us"], out["window"])) else: need(row["median"] is None and row["range"] is None, "table_row_figure", "%s: an unmetered row with a median or range" % where) - need(marker(row), "table_row_figure", "%s: an unmetered row without the unmetered marker (§4.2)" % where) # its declaration if row["status"] in ("figure", "relative"): least = max(out["p"], out["q"]) need(row["energy_uncertainty"]["relative_ppm"] >= least, "table_row_declaration", "%s: relative_ppm %d below max(p %d, q %d)" % (where, row["energy_uncertainty"]["relative_ppm"], out["p"], out["q"])) - if held_readings: + if held_readings: # §7 step 4: once every row has passed check_readings(rec, readings, raw) return identity(rec, TABLE) @@ -817,7 +834,7 @@ def verify_segment(rec, tables): need(tb["row"] < len(rows), "segment_table_mismatch", "row %d is not in the table" % tb["row"]) row = rows[tb["row"]] need(row["status"] == "figure", "segment_table_mismatch", "row %d's status is %s" % (tb["row"], row["status"])) - for k in ("capability", "path", "profile", "unit", "width", "height", "fabrics", "coverage"): + for k in ("capability", "path", "decoder", "profile", "unit", "width", "height", "fabrics", "coverage"): need(row[k] == rec[k], "segment_table_mismatch", "`%s` is not row %d's" % (k, tb["row"])) need(tb["per_completion_uj"] == row["joules_micro"] and per == row["units"], "segment_table_mismatch", "per_completion_uj or units_per_completion is not row %d's" % tb["row"]) @@ -874,7 +891,8 @@ def expected_decode(segments): else: filt = [s["energy_counter_filtering"] for s in metered] out["energy_provenance"] = "OnChipCounter" - out["energy_counter_filtering"] = "On" if "On" in filt else "Off" if all(f == "Off" for f in filt) else "Unknown" + every_off = all(s["energy_provenance"] == "OnChipCounter" and s["energy_counter_filtering"] == "Off" for s in metered) + out["energy_counter_filtering"] = "On" if "On" in filt else "Off" if every_off else "Unknown" out["window_us"] = max(s["energy_uncertainty"]["window_us"] for s in metered) out["bound"] = ceil_div(sum(s["joules_micro"] * s["energy_uncertainty"]["relative_ppm"] for s in metered), total) return out @@ -1093,6 +1111,19 @@ def prose_reasons(): # ---------------------------------------------------------------- the measured tables +def load_table_dir(d): + """One measured table's three files, as bytes (None where a file is missing).""" + files = {} + for n in ("table.json", "readings.json", "procedure.txt"): + p = os.path.join(d, n) + if os.path.isfile(p): + with open(p, "rb") as f: + files[n] = f.read() + else: + files[n] = None + return files + + def load_tables(corpus_dir): out = [] root = os.path.join(corpus_dir, "tables") @@ -1100,44 +1131,53 @@ def load_tables(corpus_dir): return out for sink in sorted(os.listdir(root)): d = os.path.join(root, sink) - if not os.path.isdir(d): - continue - files = {} - for n in ("table.json", "readings.json", "procedure.txt"): - p = os.path.join(d, n) - files[n] = open(p, "rb").read() if os.path.exists(p) else None - out.append((sink, files)) + if os.path.isdir(d): + out.append((sink, load_table_dir(d))) return out -def check_tables(tables, quiet=False): +# §9 has the corpus hold the reference sinks' measured tables. While they are being taken, a +# corpus without them is a NOTE, not a failure; set this to True once they are committed. +REQUIRE_TABLES = False + + +def check_tables(tables, quiet=False, prefix="tables/"): fails, lines = [], [] if not tables: - fails.append("no measured table under tables/ (§9: the corpus holds the reference sinks' tables)") + msg = "no measured table under tables/ (§9: the corpus holds the reference sinks' tables)" + if REQUIRE_TABLES: + fails.append(msg) + else: + lines.append("NOTE " + msg) for sink, files in tables: if any(files[n] is None for n in files): - fails.append("tables/%s: missing %s" % (sink, ", ".join(n for n in files if files[n] is None))) + fails.append("%s: missing %s" % (prefix + sink, ", ".join(n for n in files if files[n] is None))) continue try: rec = loads(files["table.json"].decode("utf-8")) readings = loads(files["readings.json"].decode("utf-8")) except (ValueError, UnicodeDecodeError) as e: - fails.append("tables/%s: does not read as JSON (%s)" % (sink, e)) + fails.append("%s: does not read as JSON (%s)" % (prefix + sink, e)) continue try: ident = verify_table(rec, readings, files["readings.json"], True) except Refused as e: - fails.append("tables/%s: refused %s — %s" % (sink, e.reason, e.why)) + fails.append("%s: refused %s — %s" % (prefix + sink, e.reason, e.why)) continue except (KeyError, TypeError, ValueError, AttributeError, IndexError) as e: - fails.append("tables/%s: verifier error %s: %s" % (sink, type(e).__name__, e)) + fails.append("%s: verifier error %s: %s" % (prefix + sink, type(e).__name__, e)) continue + try: + if not_jcs(readings, files["readings.json"]): + lines.append("NOTE %s: readings.json is not the JCS form of its object (§4.3)" % (prefix + sink)) + except ValueError: + pass if sha(files["procedure.txt"]).hex() != rec["procedure_sha256"]: - fails.append("tables/%s: procedure.txt's SHA-256 is not `procedure_sha256`" % sink) + fails.append("%s: procedure.txt's SHA-256 is not `procedure_sha256`" % (prefix + sink)) continue m = rec["machine"] - lines.append("tables/%s: %s %s, %s, %d cores, %d GiB; measured %s; %d rounds at %d us; identity %s" - % (sink, m["os"], m["arch"], m["processor"], m["cores"], m["memory_gib"], rec["measured"], + lines.append("%s: %s %s, %s, %d cores, %d GiB; measured %s; %d rounds at %d us; identity %s" + % (prefix + sink, m["os"], m["arch"], m["processor"], m["cores"], m["memory_gib"], rec["measured"], rec["rounds"], rec["sample_us"], ident.hex())) for row in rec["rows"]: lines.append(" %-28s %-8s %-11s %-10s %-12s %s" % ( @@ -1150,6 +1190,10 @@ def check_tables(tables, quiet=False): return fails, lines +def not_jcs(obj_, raw): + return jcs(obj_) != raw + + # ---------------------------------------------------------------- RFC 8032 signing, for mutations def _mul(s, p): @@ -1602,6 +1646,16 @@ def main(argv): fail += not ok if argv == ["--self-test"]: return 1 if fail else 0 + if argv[0] == "--table": + d = argv[1] + if not os.path.isdir(d): + print("FAIL %s is not a directory" % d) + return 1 + tfails, lines = check_tables([(d, load_table_dir(d))], prefix="") + for line in lines: + print(line) + print("%s the measured table in %s is accepted with its readings and procedure" % ("PASS" if not tfails else "FAIL", d)) + return 1 if fail or tfails else 0 corpus_dir = argv[0] path = os.path.join(corpus_dir, "vectors.json") tables = load_tables(corpus_dir) @@ -1647,7 +1701,8 @@ def main(argv): if __name__ == "__main__": - if len(sys.argv) != 2: - print("usage: sink_energy_verify.py | --self-test", file=sys.stderr) + args = sys.argv[1:] + if not (len(args) == 1 and args[0] != "--table" or len(args) == 2 and args[0] == "--table"): + print("usage: sink_energy_verify.py | --table | --self-test", file=sys.stderr) sys.exit(2) sys.exit(main(sys.argv[1:])) diff --git a/wai/wai-rs/src/bin/wai_sink_table.rs b/wai/wai-rs/src/bin/wai_sink_table.rs index c2f4cfc92..ae1bb6b6a 100644 --- a/wai/wai-rs/src/bin/wai_sink_table.rs +++ b/wai/wai-rs/src/bin/wai_sink_table.rs @@ -8,7 +8,7 @@ //! `run` surveys this platform's counters (energy-measurement §9), prepares each work item //! and checks its decode against its reference, then takes `N` rounds (odd, at least 7; //! default 7). In each round, for each case in an order rotated by one each round: settle -//! for one sampling interval; read the idle bracket of every live counter over the decode +//! for 2 s; read the idle bracket of every live counter over the decode //! workers parked at a start barrier; release them together as the window opens and read //! the busy bracket while each decodes the item over and over, counting completions; close //! the window; stop and join the workers. It writes `/table.json` (the signed table), @@ -42,6 +42,8 @@ use wai::sink_meter::{survey, Children, Counter, CounterStatus}; const SAMPLE: Duration = Duration::from_millis(200); const IDLE_SAMPLES: usize = 10; const BUSY_SAMPLES: usize = 14; +/// The settling before each idle bracket: a package's draw falls back from the burst before it. +const SETTLE: Duration = Duration::from_secs(2); const VECTORS: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/vectors"); const NEURAL_CONF: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../neural-conformance"); const DEFAULT_TOOL: &str = "/opt/homebrew/bin/ffmpeg"; // allow-name: the open-source media tool's install path @@ -406,7 +408,7 @@ fn decodes(cap: &str) -> bool { /// One round of one case: the readings of every counter. fn round(case: &Case, counters: &mut [Box], workers: usize) -> Vec { - std::thread::sleep(SAMPLE); + std::thread::sleep(SETTLE); let stop = Arc::new(AtomicBool::new(false)); let done = Arc::new(AtomicU64::new(0)); let release = Arc::new(Barrier::new(workers + 1)); @@ -597,9 +599,12 @@ fn main() { procedure += "wai-sink-table/1 (extensions/sink-energy.md §4.4), as this run carried it out.\n\n"; procedure += &format!("command: {}\n", args.join(" ")); procedure += &format!("sampling interval: {} us; idle bracket {IDLE_SAMPLES} intervals; busy bracket {BUSY_SAMPLES} intervals; rounds {rounds}; workers {workers}, released together from a start barrier\n", sample_us); - procedure += "order: every case once per round, the order rotated by one each round; one sampling interval of settling before each idle bracket\n"; + procedure += &format!("order: every case once per round, the order rotated by one each round; {} s of settling before each idle bracket\n", SETTLE.as_secs()); procedure += &format!("build: {} {} {}\n", build.profile.label(), build.toolchain.as_deref().unwrap_or("toolchain unknown"), build.target.as_deref().unwrap_or("target unknown")); - procedure += &format!("machine: {m}; load averages at the end {load:?}\n"); + // The processor is described by its architecture and cores, not by its product name: + // the table is public text, which names no product. + let processor = format!("{} processor, {}", m.arch, m.cores); + procedure += &format!("machine: {os}, {processor}; {} build; load averages at the end {load:?}\n", m.build); procedure += "survey:\n"; for s in &surveyed { procedure += &format!(" {} {}: {}\n", s.profile, s.status.label(), s.detail); @@ -622,7 +627,7 @@ fn main() { "procedure": se::PROCEDURE, "procedure_sha256": sha(procedure.as_bytes()), "readings_sha256": sha(&rbytes), - "machine": { "os": os, "arch": m.arch, "processor": m.cpu, "cores": std::thread::available_parallelism().map(|n| n.get()).unwrap_or(0), "memory_gib": mem >> 30 }, + "machine": { "os": os, "arch": m.arch, "processor": processor, "cores": std::thread::available_parallelism().map(|n| n.get()).unwrap_or(0), "memory_gib": mem >> 30 }, "build": { "profile": build.profile.label(), "toolchain": build.toolchain, "target": build.target }, "measured": today(), "rounds": rounds, diff --git a/wai/wai-rs/src/codecs/registry.rs b/wai/wai-rs/src/codecs/registry.rs index 9ed0a7939..543b0cc63 100644 --- a/wai/wai-rs/src/codecs/registry.rs +++ b/wai/wai-rs/src/codecs/registry.rs @@ -237,6 +237,7 @@ pub const CAP_BEARER_RECEIPTS: &str = "wai.bearer.receipts"; pub const CAP_ACCESS_SERVICES: &str = "wai.access.services"; pub const CAP_AGE_ASSURANCE: &str = "wai.age.assurance"; pub const CAP_MEDIA_EXPLORATION: &str = "wai.media.exploration"; +pub const CAP_SINK_ENERGY: &str = "wai.sink.energy"; /// One row per string SPEC §5 registers that is a payload, companion or record: /// `(string, SPEC section, edition, registered)`. @@ -342,6 +343,7 @@ const ROWS: &[(&str, &str, Option<&str>, &str)] = &[ (CAP_ACCESS_SERVICES, S_INTERACTIVE, None, "2026-10-21"), (CAP_AGE_ASSURANCE, S_INTERACTIVE, None, "2026-10-21"), (CAP_MEDIA_EXPLORATION, S_INTERACTIVE, None, "2026-10-21"), + (CAP_SINK_ENERGY, S_INTERACTIVE, None, "2026-10-22"), (CAP_AUDIO_IAMF, S_IMMERSIVE, None, "2026-06-17"), (CAP_AUDIO_AC4, S_IMMERSIVE, None, "2026-06-17"), (CAP_AUDIO_IVAS, S_IMMERSIVE, None, "2026-06-17"), @@ -410,6 +412,7 @@ const RECORDS: &[&str] = &[ CAP_ACCESS_SERVICES, CAP_AGE_ASSURANCE, CAP_MEDIA_EXPLORATION, + CAP_SINK_ENERGY, ]; /// A named identifier SPEC uses that has the shape of a capability string but is diff --git a/wai/wai-rs/src/sink_energy.rs b/wai/wai-rs/src/sink_energy.rs index fad23fd9e..ce2f8ef52 100644 --- a/wai/wai-rs/src/sink_energy.rs +++ b/wai/wai-rs/src/sink_energy.rs @@ -39,6 +39,9 @@ pub mod corpus; #[cfg(test)] mod tests; +/// The record this extension registers. +pub const CAPABILITY: &str = crate::codecs::registry::CAP_SINK_ENERGY; + /// The largest integer a record carries (§2). pub const MAX_INTEGER: u64 = (1 << 53) - 1; /// The resolution floor of energy-measurement §2.3: `joules_micro × relative_ppm` at least. diff --git a/wai/wai-rs/src/sink_energy/corpus.rs b/wai/wai-rs/src/sink_energy/corpus.rs index d50903980..80970a4e3 100644 --- a/wai/wai-rs/src/sink_energy/corpus.rs +++ b/wai/wai-rs/src/sink_energy/corpus.rs @@ -106,6 +106,7 @@ fn reference_table(signer: &SigningKey) -> (Value, Value) { synth("wai.image.png", "hardware", Some("macos-task"), "image", 1, Some((1920, 1080)), Err("no_hardware_path"), 0), synth("wai.video.av1", "software", Some("macos-gpu"), "frame", 60, Some((1280, 720)), Err("no_counter"), 0), synth("wai.video.vvc", "software", None, "frame", 60, Some((1280, 720)), Err("no_decoder"), 0), + synth("wai.neural.int_mlicpp", "software", Some("macos-task"), "image", 1, Some((256, 256)), Err("no_work_item"), 0), ]; rows.sort_by_key(|r| (r.spec.capability.clone(), r.spec.path, r.spec.decoder.clone(), r.spec.profile.map(str::to_string))); let readings = json!({ @@ -185,11 +186,21 @@ const IDLE: (u64, u64) = (2_000_000, 40_000); /// A measured segment: busy `marginal` µW over the idle, over `window` µs. fn measured(m: &mut Map, marginal: u64, window: u64) { + measured_as(m, marginal, window, None); +} + +/// A measured segment read from an instrument whose calibration `cref` names, or from the +/// on-chip counter when `None`. The instrument declares 2 % beyond the bracket. +fn measured_as(m: &mut Map, marginal: u64, window: u64, cref: Option<&str>) { let idle = summary(10, IDLE.0, IDLE.1, 10, 10); let busy = summary(10, IDLE.0 + marginal, 60_000, 10, 10); let (f, p) = bounded_figure(&idle, &busy, window).expect("a figure"); m.insert("bracket".into(), json!({ "idle": idle.to_json(), "busy": busy.to_json(), "window_us": window, "sample_us": SAMPLE_US })); - metered(m, "measured", "macos-task", f, &Class::OnChip { relative_ppm: p, window_us: window, filtering: Filtering::Unknown }); + let class = match cref { + None => Class::OnChip { relative_ppm: p, window_us: window, filtering: Filtering::Unknown }, + Some(r) => Class::Calibrated { relative_ppm: p + 20_000, window_us: window, calibration_ref: r.into() }, + }; + metered(m, "measured", "macos-task", f, &class); } /// A table segment from row `row` of `table`. @@ -406,6 +417,8 @@ pub fn generate() -> String { let d = rd_bad["rows"][fig]["rounds"][0]["busy"][3][0].as_u64().unwrap(); rd_bad["rows"][fig]["rounds"][0]["busy"][3][0] = (d + 1_000).into(); cases.push(case("table, a reading changed", "table", Some(t), Some(&rd_bad), &[], &[], &[], "table_readings_mismatch")); + let rebound = mutate(Kind::Table, t, &sink, |v| { v["readings_sha256"] = hex(&sha256(&[&canonical(&rd_bad).unwrap()])).into(); }); + cases.push(case("table, a reading changed and its digest rebound", "table", Some(&rebound), Some(&rd_bad), &[], &[], &[], "table_readings_mismatch")); // Segments. let m = |x: &Value, f: &dyn Fn(&mut Value)| mutate(Kind::Segment, x, &sink, |v| f(v)); @@ -441,6 +454,11 @@ pub fn generate() -> String { let j = v["joules_micro"].as_u64().unwrap(); v["joules_micro"] = (j + 1).into(); })), None, &[], &[], &[], "segment_estimator_mismatch")); + cases.push(case("segment, segment 0 naming a previous receipt", "segment", Some(&m(&s0, &|v| { v["prev"] = digest_hex("no such receipt").into(); })), None, &[], &[], &[], "record_malformed")); + let even = mutate(Kind::Table, t, &sink, |v| { v["rounds"] = 8.into(); }); + let on_even = m(&s1, &|v| { v["table"]["id"] = hex(&identity(Kind::Table, &even).unwrap()).into(); }); + cases.push(case("segment, from a held table that is refused", "segment", Some(&on_even), None, &[&even], &[], &[], "segment_table_mismatch")); + cases.push(case("segment, from a refused table not held", "segment", Some(&on_even), None, &[], &[], &[], "accept")); cases.push(case("segment, a figure beside no class", "segment", Some(&m(&s2, &|v| { v["joules_micro"] = 5.into(); })), None, &[], &[], &[], "unmetered_as_zero")); // Chains. @@ -455,6 +473,22 @@ pub fn generate() -> String { cases.push(case("session, four parts", "session", Some(&session), None, &tabs, &segs, &hops, "accept")); cases.push(case("session, the relay's whole figure", "session", Some(&whole), None, &[], &[&w0], &hops, "accept")); cases.push(case("session, without its hop record", "session", Some(&session), None, &tabs, &segs, &[], "accept")); + let other = json!({ "kind": "other", "record": digest_hex("origin record"), "hop_joules": 9_000_000, "hop_class": "ModelBased", "share_num": 1, "share_den": 3 }); + let with_origin = sign(Kind::Session, session_body(&sink, &all, Some(display.clone()), 120_000_000, + part_figure(share_figure(9_000_000, 1, 3).unwrap() as u64, &Class::Estimator, "Partial", other), hop_part(&hop, 120_000_000)), &sink); + cases.push(case("session, an origin's share of another kind of record", "session", Some(&with_origin), None, &tabs, &segs, &hops, "accept")); + let cal: Vec = { + let mut out: Vec = Vec::new(); + for i in 0..2u64 { + let mut x = segment_base(&sink, "stream d", i, out.last().and_then(|p| identity(Kind::Segment, p))); + x.insert("session".into(), digest_hex("session 2").into()); + measured_as(&mut x, 1_000_000 + 400_000 * i, 2_000_000 + 1_000_000 * i, Some("https://calibration.example/certificate/7")); + out.push(sign(Kind::Segment, Value::Object(x), &sink)); + } + out + }; + let calibrated = sign(Kind::Session, session_body(&sink, &cal, None, 5_000_000, part_absent("not_reported"), part_absent("not_reported")), &sink); + cases.push(case("session, decode read from a calibrated instrument", "session", Some(&calibrated), None, &[], &[&cal[0], &cal[1]], &[], "accept")); let ms = |f: &dyn Fn(&mut Value)| mutate(Kind::Session, &session, &sink, |v| f(v)); cases.push(case("session, an origin absent as unmetered", "session", Some(&ms(&|v| { v["split"]["origin"]["reason"] = "unmetered".into(); })), None, &tabs, &segs, &hops, "split_part_invalid")); cases.push(case("session, a display estimate one microjoule high", "session", Some(&ms(&|v| { diff --git a/wai/wai-rs/src/sink_meter.rs b/wai/wai-rs/src/sink_meter.rs index 47aeac207..96f69b202 100644 --- a/wai/wai-rs/src/sink_meter.rs +++ b/wai/wai-rs/src/sink_meter.rs @@ -610,3 +610,65 @@ pub fn survey(children: &Children, every: Duration, intervals: u32) -> (Vec>, usize); + impl Counter for Scripted { + fn profile(&self) -> &'static str { + "macos-task" + } + fn detail(&self) -> String { + "scripted".into() + } + fn read_nj(&mut self) -> Option { + let v = self.0.get(self.1).copied().flatten(); + self.1 += 1; + v + } + } + + /// energy-measurement §3.1 on a counter: an advance in every interval is live; a counter + /// that holds its value, or moves in one lump, or fails to read, is not. + #[test] + fn a_counter_is_live_only_when_it_advances_as_a_live_rail_does() { + let d = Duration::from_millis(0); + let rising: Vec> = (0..11).map(|i| Some(i * 1000)).collect(); + assert_eq!(liveness(&mut Scripted(rising, 0), 10, d), CounterStatus::Live); + assert_eq!(liveness(&mut Scripted(vec![Some(7); 11], 0), 10, d), CounterStatus::Frozen); + let lump: Vec> = (0..11).map(|i| Some(if i < 6 { 0 } else { 5_000 })).collect(); + assert_eq!(liveness(&mut Scripted(lump, 0), 10, d), CounterStatus::Frozen, "one lump is one advance"); + // seven of ten advance: below four of every five. + let seven: Vec> = [0, 1, 2, 3, 3, 4, 5, 5, 6, 6, 7].into_iter().map(Some).collect(); + assert_eq!(liveness(&mut Scripted(seven, 0), 10, d), CounterStatus::Frozen); + let failing = vec![Some(0), Some(1), None]; + assert_eq!(liveness(&mut Scripted(failing, 0), 10, d), CounterStatus::Frozen, "a read that fails is no reading"); + } + + /// An advance across a counter's wrap, and none where a counter with no wrap goes back. + #[test] + fn an_advance_crosses_the_wrap() { + assert_eq!(advance(10, 25, Some(100)), 15); + assert_eq!(advance(90, 5, Some(100)), 15); + assert_eq!(advance(90, 5, None), 0); + } + + /// The per-process reader reads this process, and its figure rises while it works. + #[cfg(target_os = "macos")] + #[test] + fn the_task_counter_reads_this_process() { + let mut t = TaskEnergy::open(Children::default()).expect("proc_pid_rusage reads this process"); + let a = t.read_nj().unwrap(); + let mut x = 0u64; + let start = Instant::now(); + while start.elapsed() < Duration::from_millis(50) { + x = std::hint::black_box(x.wrapping_mul(31).wrapping_add(7)); + } + std::thread::sleep(Duration::from_millis(20)); // leave the core so the work folds in + let b = t.read_nj().unwrap(); + assert!(b > a, "50 ms of work read {a} then {b} nJ"); + } +} -- 2.54.0 (Apple Git-157) From 4863036d8a00941b6996e3ed4c42ebbe385350c2 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 13:50:18 -0400 Subject: [PATCH 050/242] det_cost_bin: a refusal line must carry its code exactly The test compared a refusal line by prefix, so a decoder reporting `bad_magicx` for `bad_magic` passed; found by falsifying it. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/tests/det_cost_bin.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/wai/wai-rs/tests/det_cost_bin.rs b/wai/wai-rs/tests/det_cost_bin.rs index 1d1751d50..7765fd316 100644 --- a/wai/wai-rs/tests/det_cost_bin.rs +++ b/wai/wai-rs/tests/det_cost_bin.rs @@ -50,14 +50,16 @@ fn the_committed_vector_and_every_refusal_fixture_decode_as_the_corpus_states() let set = set_dir(v, &tmp.join(format!("v{i}"))); let payload = wai().join(v["payload"].as_str().unwrap()); jobs.push_str(&format!("{}\t{}\t-\n", set.display(), payload.display())); - want.push((payload.display().to_string(), side[v["name"].as_str().unwrap()]["sha256"].as_str().unwrap().to_string())); + let w = side[v["name"].as_str().unwrap()]["sha256"].as_str().unwrap().to_string(); + // A decoded line also gives the picture's width and height; the digest is what is held. + want.push((payload.display().to_string(), vec![w])); } assert!(!want.is_empty(), "the corpus has an int_hyper vector"); for (i, r) in idx["refusals"].as_array().unwrap().iter().filter(|r| r["capability"] == "wai.neural.int_hyper").enumerate() { let set = set_dir(r, &tmp.join(format!("r{i}"))); let payload = wai().join(r["payload"].as_str().unwrap()); jobs.push_str(&format!("{}\t{}\t-\n", set.display(), payload.display())); - want.push((payload.display().to_string(), format!("REFUSED\t{}", r["code"].as_str().unwrap()))); + want.push((payload.display().to_string(), vec!["REFUSED".to_string(), r["code"].as_str().unwrap().to_string()])); } std::fs::write(tmp.join("jobs.tsv"), &jobs).unwrap(); let out = bin().arg("decode").arg(tmp.join("jobs.tsv")).output().unwrap(); @@ -68,7 +70,12 @@ fn the_committed_vector_and_every_refusal_fixture_decode_as_the_corpus_states() for (line, (payload, w)) in lines.iter().zip(&want) { let f: Vec<&str> = line.split('\t').collect(); assert_eq!(f[0], payload); - assert!(f[1..].join("\t").starts_with(w.as_str()), "{payload}: got {line:?}, want {w:?}"); + if w[0] == "REFUSED" { + assert_eq!(&f[1..], &w[..], "{payload}: a refusal is its code, exactly"); + } else { + assert_eq!(f.len(), 4, "{payload}: {line:?}"); + assert_eq!(f[1], w[0], "{payload}: the RGB8 digest"); + } } } -- 2.54.0 (Apple Git-157) From 68977d1f288e401eb0f7a544a0ce6b52237525c6 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 13:50:23 -0400 Subject: [PATCH 051/242] wai-rs: stream_termination clippy-clean Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/stream_termination.rs | 41 ++++++++------------- wai/wai-rs/src/stream_termination/corpus.rs | 2 +- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/wai/wai-rs/src/stream_termination.rs b/wai/wai-rs/src/stream_termination.rs index 50d5fd996..5ee73459c 100644 --- a/wai/wai-rs/src/stream_termination.rs +++ b/wai/wai-rs/src/stream_termination.rs @@ -841,34 +841,25 @@ fn read_structure(b: &[u8]) -> Result<(Record, bool), Reason> { /// §3.9 steps 1 to 4; `hdr_ok`, whether the record's declaration's `hdr` byte is 0 or 1. fn rules(r: &Record, hdr_ok: bool) -> Result<(), Reason> { - if let Body::Terminate(t) = &r.body { - if !(why::HIDDEN..=why::ENDED).contains(&t.reason) { - return Err(Reason::ReasonUnknown); - } + if let Body::Terminate(t) = &r.body + && !(why::HIDDEN..=why::ENDED).contains(&t.reason) + { + return Err(Reason::ReasonUnknown); } - if let Body::Segment(s) = &r.body { - if !s.hops.iter().all(HopEntry::energy_ok) { - return Err(Reason::EnergyInvalid); - } + if let Body::Segment(s) = &r.body + && !s.hops.iter().all(HopEntry::energy_ok) + { + return Err(Reason::EnergyInvalid); } let decl_ok = |d: &Declaration| hdr_ok && Declaration::holds(d.long, d.short, d.refresh_milli, d.bit_depth, d.hdr as u8); - match &r.body { - Body::Open(o) if o.media == media::AUDIO => { - if o.display != Declaration::default() || !hdr_ok { - return Err(Reason::DisplayInvalid); - } - } - Body::Open(o) => { - if !decl_ok(&o.display) { - return Err(Reason::DisplayInvalid); - } - } - Body::Display(d) => { - if !decl_ok(d) { - return Err(Reason::DisplayInvalid); - } - } - _ => {} + let display_ok = match &r.body { + Body::Open(o) if o.media == media::AUDIO => o.display == Declaration::default() && hdr_ok, + Body::Open(o) => decl_ok(&o.display), + Body::Display(d) => decl_ok(d), + _ => true, + }; + if !display_ok { + return Err(Reason::DisplayInvalid); } let ok = match &r.body { Body::Open(o) => { diff --git a/wai/wai-rs/src/stream_termination/corpus.rs b/wai/wai-rs/src/stream_termination/corpus.rs index 172598120..06bc5b95d 100644 --- a/wai/wai-rs/src/stream_termination/corpus.rs +++ b/wai/wai-rs/src/stream_termination/corpus.rs @@ -716,7 +716,7 @@ pub fn generate() -> String { } fn unhex(s: &str) -> Option> { - if s.len() % 2 != 0 { + if !s.len().is_multiple_of(2) { return None; } (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok()).collect() -- 2.54.0 (Apple Git-157) From 56ef56f133152dada55d05db7a413aa9063de7a8 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 13:51:45 -0400 Subject: [PATCH 052/242] joule-moq-web: a segment ends at a gap; a stream ended as idle reopens on the viewer's return Co-Authored-By: Claude Opus 5.5 --- .../crates/joule-moq-web/web/room/app.js | 11 +++++++---- .../crates/joule-moq-web/web/room/termination.js | 10 +++++++--- .../joule-moq-web/web/room/termination.test.mjs | 6 ++++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/joule-code/joule-code-rs/crates/joule-moq-web/web/room/app.js b/joule-code/joule-code-rs/crates/joule-moq-web/web/room/app.js index 66d7c5859..4a5c504b1 100644 --- a/joule-code/joule-code-rs/crates/joule-moq-web/web/room/app.js +++ b/joule-code/joule-code-rs/crates/joule-moq-web/web/room/app.js @@ -1271,7 +1271,7 @@ const TERM_POLICY = { }; const UNSEEN_REASONS = ["hidden", "occluded", "display_off"]; let termSeed = null; -const attention = { occluded: false, activated: false, waiters: [] }; +const attention = { occluded: false, activated: false, lastActivation: 0, waiters: [] }; const nowUs = () => Math.round((performance.timeOrigin + performance.now()) * 1000); // The viewer's autoplay setting (§4.4): the `autoplay` parameter, else this page's storage. @@ -1322,6 +1322,7 @@ async function startTermination() { attention.activated = true; for (const w of attention.waiters.splice(0)) w(); const t = nowUs(); + attention.lastActivation = t; eachSink((x) => x.observe(term.SIGNAL.interaction, 1, t)); }, { capture: true, passive: true }); } @@ -1383,7 +1384,8 @@ function receipted(st, T, bind, envelope, mediaUs, durUs) { } // §4.3: a stream whose reason is due is terminated; a video stream ended as unseen is -// subscribed again, under a new log, once the page is seen. +// subscribed again, under a new log, once the page is seen, and one ended as idle or +// unattended once the viewer interacts. function terminationTick() { const t = nowUs(); const seen = document.visibilityState !== "hidden" && !attention.occluded; @@ -1393,9 +1395,10 @@ function terminationTick() { if (!st.sink.terminated) { const reason = st.sink.due(t); if (reason) endStream(sub, m, st, reason); - } else if (seen && UNSEEN_REASONS.includes(st.sink.terminatedAs) && !st.reopening && state.phase !== "left") { + } else if (!st.reopening && state.phase !== "left" && ((seen && UNSEEN_REASONS.includes(st.sink.terminatedAs)) + || (["idle", "autoplay_unattended"].includes(st.sink.terminatedAs) && attention.lastActivation > st.sink.terminatedAt))) { st.reopening = true; - log(`${m} of ${sub.principal.slice(0, 8)}: seen again; subscribed under a new log`); + log(`${m} of ${sub.principal.slice(0, 8)}: ${st.sink.terminatedAs === "idle" || st.sink.terminatedAs === "autoplay_unattended" ? "the viewer is back" : "seen again"}; subscribed under a new log`); subscribeMedia(sub, st.tr).catch(fail); } } diff --git a/joule-code/joule-code-rs/crates/joule-moq-web/web/room/termination.js b/joule-code/joule-code-rs/crates/joule-moq-web/web/room/termination.js index fc7edd6d3..c3b794c77 100644 --- a/joule-code/joule-code-rs/crates/joule-moq-web/web/room/termination.js +++ b/joule-code/joule-code-rs/crates/joule-moq-web/web/room/termination.js @@ -144,8 +144,10 @@ export function unseen(st) { } // The segment being gathered, and when it closes (§4.5): the objects over at least `segmentUs` -// of media time, closed when the first object past that arrives, at the media time it starts; -// a change of rendition closes it too. The last one, closed by a termination, ends where its +// of media time, closed when the first object past that arrives; a change of rendition, or a +// gap of more than `segmentUs` between two objects, closes it too. It ends where the next +// object starts, or where its last object ends if that is earlier, so a span is never more +// than `segmentUs` and one object long. The last one, closed by a termination, ends where its // last object does. Spans never overlap: an object at or before the last one gathered, or // before the last segment's end, is not receipted (it is out of order, or repeated). export class Gatherer { @@ -161,7 +163,8 @@ export class Gatherer { const out = []; const p = this.pending; if (p ? mediaUs <= p.last : this.floor != null && mediaUs < this.floor) return out; - if (p && (p.rendition !== rendition || mediaUs - p.media_start >= this.segmentUs)) out.push(this.close(mediaUs)); + const gap = p && mediaUs - p.last > this.segmentUs; + if (p && (p.rendition !== rendition || gap || mediaUs - p.media_start >= this.segmentUs)) out.push(this.close(Math.min(mediaUs, p.last + p.dur))); if (!this.pending) this.pending = { hashes: [], bytes: 0, media_start: mediaUs, last: mediaUs, dur: durUs, rendition }; const q = this.pending; q.hashes.push(hash); @@ -268,6 +271,7 @@ export class SinkStream { const media = this.lastSegment ? this.lastSegment.end : 0; this.note(this.writer.terminate(big(now), reason, big(media))); this.terminatedAs = reason; + this.terminatedAt = now; return { reason, media }; } diff --git a/joule-code/joule-code-rs/crates/joule-moq-web/web/room/termination.test.mjs b/joule-code/joule-code-rs/crates/joule-moq-web/web/room/termination.test.mjs index be11aa181..4455b3f1a 100644 --- a/joule-code/joule-code-rs/crates/joule-moq-web/web/room/termination.test.mjs +++ b/joule-code/joule-code-rs/crates/joule-moq-web/web/room/termination.test.mjs @@ -71,6 +71,12 @@ test("objects gather into segments of at least the span, closed by the next obje const last = g.take(); assert.deepEqual([last.media_start, last.media_end, last.rendition], [2_500_000, 2_600_000, 1]); assert.equal(g.take(), null); + // A gap longer than the span closes the segment where its last object ends. + const h = new Gatherer(2 * S); + h.add(o(0)); + h.add(o(1)); + const gapped = h.add(o(60)); + assert.deepEqual([gapped[0].media_start, gapped[0].media_end, gapped[0].hashes.length], [0, 200_000, 2]); // With nothing gathered, an object before the last segment's end starts no segment. assert.deepEqual(g.add(o(20, 1)), []); assert.equal(g.take(), null, "a segment would overlap the one before it"); -- 2.54.0 (Apple Git-157) From 4dc3b43040582e09b3c8464307a491ba8154ac36 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 13:53:08 -0400 Subject: [PATCH 053/242] sink_meter: no dead-code notes off macOS Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/sink_meter.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wai/wai-rs/src/sink_meter.rs b/wai/wai-rs/src/sink_meter.rs index 96f69b202..13cfaa43c 100644 --- a/wai/wai-rs/src/sink_meter.rs +++ b/wai/wai-rs/src/sink_meter.rs @@ -97,6 +97,7 @@ fn sym(h: *mut c_void, name: &str) -> Option<*mut c_void> { /// The processes a [`TaskEnergy`] reader sums besides its own: the children running now /// with their last reading, and the readings of those that have exited. #[derive(Default)] +#[cfg_attr(not(target_os = "macos"), allow(dead_code))] pub struct Ledger { live: Vec<(i32, u64)>, finished_nj: u64, @@ -104,6 +105,7 @@ pub struct Ledger { /// A ledger shared between the reader and the workers that run children. #[derive(Clone, Default)] +#[cfg_attr(not(target_os = "macos"), allow(dead_code))] pub struct Children(Arc>); #[cfg(target_os = "macos")] -- 2.54.0 (Apple Git-157) From f7d55b28a5459e06c0570c713431edf6cd403a18 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 13:53:23 -0400 Subject: [PATCH 054/242] wai: the refresh rate a page measures, stated exactly; wording Co-Authored-By: Claude Opus 5.5 --- wai/extensions/capability-constraints.md | 5 +++-- wai/extensions/stream-termination.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/wai/extensions/capability-constraints.md b/wai/extensions/capability-constraints.md index 2422f97bb..6d182d92e 100644 --- a/wai/extensions/capability-constraints.md +++ b/wai/extensions/capability-constraints.md @@ -121,8 +121,9 @@ stream is presented on. A page reads them through the interfaces it has: - `hdr`: true when the media query `(video-dynamic-range: high)` or `(dynamic-range: high)` matches (Media Queries Level 5 §6.5, §8.2); - `refresh_milli`: no interface states it, so a page measures it: 1000 times the whole - number of pictures per second nearest to 10� divided by the median interval, in - microseconds, between at least 60 consecutive animation frames; + number of pictures per second nearest to 10� divided by the median of at least 60 intervals, + in microseconds, between consecutive animation frames (of an even count, the upper of the + two middle ones); - `long_mm` and `short_mm`: 0, since a page has no interface for them. Texts read for this section: CSSOM View Module, W3C Working Draft 16 September 2025, §2.1 diff --git a/wai/extensions/stream-termination.md b/wai/extensions/stream-termination.md index c32d32965..eb0ee30a3 100644 --- a/wai/extensions/stream-termination.md +++ b/wai/extensions/stream-termination.md @@ -9,7 +9,7 @@ > ([capability-constraints](capability-constraints.md) §4); it then holds a **receipt for each > segment** the sink received, with the rendition and how it matched the panel and each hop's > joules with their acquisition class ([energy-measurement](energy-measurement.md) §2); the -> **observations** of whether anyone could be watching (the page's visibility, occlusion where +> **observations** of whether anyone could be watching (the document's visibility, occlusion where > the platform reports it, the display's power and the viewer's interaction); and it ends with > a **termination**: the reason (hidden, occluded, display off, idle, autoplayed with no > interaction, stopped by the viewer, or ended), the media time, and the joules of each hop up -- 2.54.0 (Apple Git-157) From 1a2d52c5332b2e848281d95eb829fe1daef906d2 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 13:56:01 -0400 Subject: [PATCH 055/242] wai_sink_table: 2 s clips, so completions outnumber the workers in a window Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/bin/wai_sink_table.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wai/wai-rs/src/bin/wai_sink_table.rs b/wai/wai-rs/src/bin/wai_sink_table.rs index ae1bb6b6a..ef16f55b3 100644 --- a/wai/wai-rs/src/bin/wai_sink_table.rs +++ b/wai/wai-rs/src/bin/wai_sink_table.rs @@ -325,7 +325,7 @@ fn platform_cases(cases: &mut Vec, absent: &mut Vec, tool: Option for (cap, file, enc, sw) in clips { let clip = work_dir.join(file); let c = clip.display().to_string(); - let mut args: Vec<&str> = vec!["-hide_banner", "-loglevel", "error", "-y", "-f", "lavfi", "-i", "testsrc2=size=1920x1080:rate=30", "-t", "10"]; + let mut args: Vec<&str> = vec!["-hide_banner", "-loglevel", "error", "-y", "-f", "lavfi", "-i", "testsrc2=size=1920x1080:rate=30", "-t", "2"]; args.extend_from_slice(enc); args.extend_from_slice(&["-pix_fmt", "yuv420p", &c]); if !run(&args).map(|o| o.status.success()).unwrap_or(false) { @@ -351,7 +351,7 @@ fn platform_cases(cases: &mut Vec, absent: &mut Vec, tool: Option path, decoder: decoder.into(), unit: "frame", - units: 300, + units: 60, wh: Some((1920, 1080)), profiles, item: note, @@ -369,7 +369,7 @@ fn platform_cases(cases: &mut Vec, absent: &mut Vec, tool: Option v }; cases.push(mk("software", "platform:media-library", base(&["-c:v", sw]), cpu.to_vec(), - format!("10 s 1920x1080 30 fps test pattern, {} sha256 {digest}; the tool's software decoder, one thread per process, output MD5 {soft_md5}", enc.join(" ")))); + format!("2 s 1920x1080 30 fps test pattern (60 frames), {} sha256 {digest}; the tool's software decoder, one thread per process, output MD5 {soft_md5}", enc.join(" ")))); let hw_ok = took && hard.as_deref() == Some(soft_md5.as_str()); if hw_ok { let mut profiles = cpu.to_vec(); @@ -378,7 +378,7 @@ fn platform_cases(cases: &mut Vec, absent: &mut Vec, tool: Option format!("the same clip on the platform's hardware decoder, frames kept on the device; output MD5 {soft_md5}, the software path's"))); } else { notes.push(format!("{cap}: the hardware path was not taken or its output differs (took {took}, MD5 {hard:?} vs {soft_md5}): absent")); - absent.push(absent_row(cap, "hardware", "platform:media-framework", cpu.first().copied(), "no_hardware_path", 300, Some((1920, 1080)))); + absent.push(absent_row(cap, "hardware", "platform:media-framework", cpu.first().copied(), "no_hardware_path", 60, Some((1920, 1080)))); } } } -- 2.54.0 (Apple Git-157) From 938f16a1f3a460314c6f46eb1d966b41d39c6d25 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Thu, 22 Oct 2026 13:58:58 -0400 Subject: [PATCH 056/242] sink-energy: registry export, corpus cases for the rules no vector reached Co-Authored-By: Claude Opus 5.5 --- wai/live-conformance/sink-energy/vectors.json | 7856 ++++++++++++++++- wai/registry/capabilities.json | 14 + wai/vocab/v2.ttl | 7 + wai/wai-rs/src/bin/wai_sink_table.rs | 2 +- 4 files changed, 7670 insertions(+), 209 deletions(-) diff --git a/wai/live-conformance/sink-energy/vectors.json b/wai/live-conformance/sink-energy/vectors.json index 2c2238f13..17d2bbf40 100644 --- a/wai/live-conformance/sink-energy/vectors.json +++ b/wai/live-conformance/sink-energy/vectors.json @@ -17,7 +17,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -373,6 +373,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -736,7 +759,7 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" }, "readings": { "wai_sink_readings": 1, @@ -2206,6 +2229,13 @@ } ] }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "rounds": [] + }, { "capability": "wai.video.av1", "path": "hardware", @@ -3684,7 +3714,7 @@ "segments": [], "hops": [], "expect": "accept", - "identity": "8a4050461b7c303aeb9eaee0b02a32c543737b306dfa563eaa62f420f42995c4" + "identity": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9" }, { "name": "table, without its readings", @@ -3694,7 +3724,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -4050,6 +4080,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -4413,14 +4466,14 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" }, "readings": null, "tables": [], "segments": [], "hops": [], "expect": "accept", - "identity": "8a4050461b7c303aeb9eaee0b02a32c543737b306dfa563eaa62f420f42995c4" + "identity": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9" }, { "name": "table, a member missing", @@ -4430,7 +4483,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -4773,6 +4826,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -5148,7 +5224,7 @@ "detail": "proc_pid_rusage V6" } ], - "sig": "fb8397d4f03f15de5e92831ffb6cd603e120515b06a6edd4d7e8394b799160ba69ce8e890482a643c1125f29d9306dca32a02b211172522cb5554004b6015805" + "sig": "8353352f1ccd41569aead636cdef74c970b7499e00b0443d4e28c83cf49311633d27cd02c2360b32a233d7c07a97030a561c485438922c7e9ce773eef42b190e" }, "readings": null, "tables": [], @@ -5165,7 +5241,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -5521,6 +5597,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -5884,7 +5983,7 @@ "energy_calibration_ref": null } ], - "sig": "0c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "05f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" }, "readings": null, "tables": [], @@ -5901,7 +6000,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -6257,6 +6356,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -6617,7 +6739,7 @@ "energy_calibration_ref": null } ], - "sig": "bbc2434d01b3fc611a17c9994f662b7bb59c50d7a3ecac1896db4115d57894d45db80154bb94897ed03f9fe801d441d76da4409402f5daec6fe34620bda7530e" + "sig": "9ded97fce731039ff8d27bf940e0e2901041ea8cf331d471f3df46e3a293823efa21f96f629fabe0d56921da5d127fc54470ed1387258a3c1a84293bf8fcc500" }, "readings": null, "tables": [], @@ -6634,7 +6756,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -6990,6 +7112,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -7353,7 +7498,7 @@ "energy_calibration_ref": null } ], - "sig": "71176d8f8f9c22c3a361ad8aab0fd1133796ecdf61aa2a7b146f1b4799be998613efb340fd255129a08761982abcb47b170b4fd4abbeffed2a1ab08e25bf1502" + "sig": "de01af63cdaec17d97f0ed7d8d1a511fb9852fac04764e4ed208558e1f538df55d9a0716ee247d8ee644addba0ed282b378c60c4f41eb554fd07cee433767601" }, "readings": null, "tables": [], @@ -7370,7 +7515,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -7726,6 +7871,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -8089,7 +8257,7 @@ "energy_calibration_ref": null } ], - "sig": "3d1c82efbd71af3329dcb229140b6171b758689e9738b12c1be0937a7a187ef0fabe30b8007bc7b6a4f2cf955387c405b00a05529524ef59caab3c6543b0c704" + "sig": "819f4ecefc9f9db945450ee6f31365d1caa6cbeb4a069220547de934a2b32d62c59d3a442a0d666cf0ed42560f1c4bec232875fc4190bc09fd9a4da5d5fa160b" }, "readings": null, "tables": [], @@ -8106,7 +8274,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -8462,6 +8630,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -8825,7 +9016,7 @@ "energy_calibration_ref": null } ], - "sig": "d35a7be2099bb4e67408175a15cc34f28cf3b94babc23a432c800a8bc18c026cc379facf276fe0b5474cf74989cabce61834215071b5761f6cb1fdd927e5290b" + "sig": "6dd9f4d69b794c790222e04515130ba1f0e9895af590048a27b358f7c8c7f51e591070dbfbdf79ab74d1da4dbac79a5ffd6dc4fa78dd3b1d2d0c1a6abb03eb02" }, "readings": null, "tables": [], @@ -8842,7 +9033,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -9198,6 +9389,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -9561,7 +9775,7 @@ "energy_calibration_ref": null } ], - "sig": "d0831deb52c75d3d68bea40a244e2110b922074bf6e48f1d34177e63c7673c71c6f275ad0e2b478a105bb29280f4649d0bbe354add02d49eb51ed25fcdf9e00c" + "sig": "a43681914c39f2685002e87c8b1fed4534e885f88c50c0a1ad64625e35965a92e15b47de681aa96e4da0a85e6f1a84bef708315aa9b45900b140e3c4182b6e01" }, "readings": null, "tables": [], @@ -9578,7 +9792,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -9934,6 +10148,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -10297,7 +10534,7 @@ "energy_calibration_ref": null } ], - "sig": "e47a968d3d231d0c7ee4eed3a31b002aa59accc44bf6acf3ea1381292d835e0f4fba92447d579405db720cfdcfcc890067d62f4186ba46850284aed06ebd7002" + "sig": "79ba44b9f1a0d1236236a9d6fcd14829ca9b4acbe71dab26ea520f249512990144199cb43c34123b76d8074086dc0b4119aa262af8a0a93802b8ce34f156840b" }, "readings": null, "tables": [], @@ -10314,7 +10551,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -10670,6 +10907,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -11033,7 +11293,7 @@ "energy_calibration_ref": null } ], - "sig": "b9d3e48275457474f3583f48efa7af05947203658abc2b6e934a199b31cf17d9de153caa648fc87f753fd7c3e4327c6c1dc4faaef1cf7c5f0cab8beaa33cb409" + "sig": "80a78a357777f03d9ac36a6c6a767d68a8b5a0ee21ce687664b46513ad8bd34986d0a72814455b216a750c423512b8b339ef6b15fcd397ff5432e05108fac00b" }, "readings": null, "tables": [], @@ -11050,7 +11310,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -11406,6 +11666,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -11769,7 +12052,7 @@ "energy_calibration_ref": null } ], - "sig": "cf1c4c047313880c7f5b8f1fb47b3f2d330b550465762f7e2cf098627ff4186fb3b686b4473f81632732ecfbab9e70ac735aa31e069ca90573afac416277150f" + "sig": "8f769a614c0e7d208a6b4526e0684d3e13be551aea2a3312f190346f10b617aafa78e2444647c75479b997ac46e95b15dd91d9d2e33ee7bb7553c55a73c9a707" }, "readings": null, "tables": [], @@ -11786,7 +12069,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -12142,6 +12425,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -12505,7 +12811,7 @@ "energy_calibration_ref": null } ], - "sig": "9f5f0901064655fc7d0ab5d9e5c17407ca5bc46b40968ce6ddd67729a10927cb820e058ea517710df99da34915cabaa7ca896828d5543220995b9cd2a8110e0d" + "sig": "443dcdf9af292c35fc4326e9f73fbd66b126594b28f8a0943f606337c81ba19405e5caf08fe5e412f2ab1d2886f74ec26da7215db88ad80a29263b5348ead00a" }, "readings": null, "tables": [], @@ -12522,7 +12828,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -12878,6 +13184,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -13241,7 +13570,7 @@ "energy_calibration_ref": null } ], - "sig": "3abb2b3afa0fbb590698c769baf815a1cb99258dd784c5f060c36f69606c529ad7133eddf7a5725fd9265e14d62454007ab27da0f54bce38479778649eceee0d" + "sig": "d8823767a84ce069eb665895eb14968e5d145c823ca54d8fa347728920d187202ad69f1a1b34dd0b2a7ec9b490d3247db2cadff3c96b40c5ff8a3e9e1a2b690d" }, "readings": null, "tables": [], @@ -13258,7 +13587,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -13614,6 +13943,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -13977,7 +14329,7 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" }, "readings": { "wai_sink_readings": 1, @@ -15447,6 +15799,3720 @@ } ] }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "rounds": [] + }, + { + "capability": "wai.video.av1", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "rounds": [ + { + "idle": [ + [ + 391800000, + 200000 + ], + [ + 447800000, + 200000 + ], + [ + 379200000, + 200000 + ], + [ + 348600000, + 200000 + ], + [ + 391600000, + 200000 + ], + [ + 348800000, + 200000 + ], + [ + 344600000, + 200000 + ], + [ + 402200000, + 200000 + ], + [ + 333800000, + 200000 + ], + [ + 393200000, + 200000 + ] + ], + "busy": [ + [ + 707200000, + 200000 + ], + [ + 730600000, + 200000 + ], + [ + 713000000, + 200000 + ], + [ + 754000000, + 200000 + ], + [ + 608200000, + 200000 + ], + [ + 612200000, + 200000 + ], + [ + 688800000, + 200000 + ], + [ + 706600000, + 200000 + ], + [ + 608200000, + 200000 + ], + [ + 616200000, + 200000 + ], + [ + 758800000, + 200000 + ], + [ + 656600000, + 200000 + ], + [ + 627600000, + 200000 + ], + [ + 751200000, + 200000 + ] + ], + "completions": 3000 + }, + { + "idle": [ + [ + 475000000, + 200000 + ], + [ + 432800000, + 200000 + ], + [ + 451800000, + 200000 + ], + [ + 321200000, + 200000 + ], + [ + 405800000, + 200000 + ], + [ + 425600000, + 200000 + ], + [ + 427600000, + 200000 + ], + [ + 329800000, + 200000 + ], + [ + 361600000, + 200000 + ], + [ + 376200000, + 200000 + ] + ], + "busy": [ + [ + 747000000, + 200000 + ], + [ + 734400000, + 200000 + ], + [ + 686400000, + 200000 + ], + [ + 678000000, + 200000 + ], + [ + 756200000, + 200000 + ], + [ + 716400000, + 200000 + ], + [ + 713600000, + 200000 + ], + [ + 734600000, + 200000 + ], + [ + 752800000, + 200000 + ], + [ + 679400000, + 200000 + ], + [ + 616800000, + 200000 + ], + [ + 607000000, + 200000 + ], + [ + 660800000, + 200000 + ], + [ + 722200000, + 200000 + ] + ], + "completions": 3003 + }, + { + "idle": [ + [ + 453400000, + 200000 + ], + [ + 473800000, + 200000 + ], + [ + 330800000, + 200000 + ], + [ + 409800000, + 200000 + ], + [ + 469200000, + 200000 + ], + [ + 448800000, + 200000 + ], + [ + 437600000, + 200000 + ], + [ + 465800000, + 200000 + ], + [ + 464600000, + 200000 + ], + [ + 329400000, + 200000 + ] + ], + "busy": [ + [ + 735400000, + 200000 + ], + [ + 675000000, + 200000 + ], + [ + 654200000, + 200000 + ], + [ + 720400000, + 200000 + ], + [ + 706400000, + 200000 + ], + [ + 678600000, + 200000 + ], + [ + 722000000, + 200000 + ], + [ + 660200000, + 200000 + ], + [ + 739200000, + 200000 + ], + [ + 603600000, + 200000 + ], + [ + 656000000, + 200000 + ], + [ + 662400000, + 200000 + ], + [ + 717200000, + 200000 + ], + [ + 635200000, + 200000 + ] + ], + "completions": 3006 + }, + { + "idle": [ + [ + 474000000, + 200000 + ], + [ + 410200000, + 200000 + ], + [ + 415400000, + 200000 + ], + [ + 325600000, + 200000 + ], + [ + 402000000, + 200000 + ], + [ + 421200000, + 200000 + ], + [ + 374800000, + 200000 + ], + [ + 441600000, + 200000 + ], + [ + 401600000, + 200000 + ], + [ + 390000000, + 200000 + ] + ], + "busy": [ + [ + 715600000, + 200000 + ], + [ + 702000000, + 200000 + ], + [ + 609400000, + 200000 + ], + [ + 710600000, + 200000 + ], + [ + 618600000, + 200000 + ], + [ + 721400000, + 200000 + ], + [ + 741200000, + 200000 + ], + [ + 647800000, + 200000 + ], + [ + 743800000, + 200000 + ], + [ + 750200000, + 200000 + ], + [ + 693200000, + 200000 + ], + [ + 731000000, + 200000 + ], + [ + 675400000, + 200000 + ], + [ + 671200000, + 200000 + ] + ], + "completions": 3009 + }, + { + "idle": [ + [ + 425000000, + 200000 + ], + [ + 430600000, + 200000 + ], + [ + 406000000, + 200000 + ], + [ + 394600000, + 200000 + ], + [ + 463400000, + 200000 + ], + [ + 348400000, + 200000 + ], + [ + 457000000, + 200000 + ], + [ + 421800000, + 200000 + ], + [ + 405400000, + 200000 + ], + [ + 328600000, + 200000 + ] + ], + "busy": [ + [ + 679400000, + 200000 + ], + [ + 758000000, + 200000 + ], + [ + 714200000, + 200000 + ], + [ + 734400000, + 200000 + ], + [ + 628000000, + 200000 + ], + [ + 729800000, + 200000 + ], + [ + 688800000, + 200000 + ], + [ + 627800000, + 200000 + ], + [ + 644400000, + 200000 + ], + [ + 672400000, + 200000 + ], + [ + 729000000, + 200000 + ], + [ + 715600000, + 200000 + ], + [ + 673000000, + 200000 + ], + [ + 621400000, + 200000 + ] + ], + "completions": 3012 + }, + { + "idle": [ + [ + 443000000, + 200000 + ], + [ + 386200000, + 200000 + ], + [ + 474000000, + 200000 + ], + [ + 328400000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 327000000, + 200000 + ], + [ + 323600000, + 200000 + ], + [ + 455000000, + 200000 + ], + [ + 454600000, + 200000 + ], + [ + 468400000, + 200000 + ] + ], + "busy": [ + [ + 648600000, + 200000 + ], + [ + 716600000, + 200000 + ], + [ + 674600000, + 200000 + ], + [ + 600000000, + 200000 + ], + [ + 737200000, + 200000 + ], + [ + 728200000, + 200000 + ], + [ + 703800000, + 200000 + ], + [ + 639600000, + 200000 + ], + [ + 669400000, + 200000 + ], + [ + 672600000, + 200000 + ], + [ + 725200000, + 200000 + ], + [ + 725200000, + 200000 + ], + [ + 677400000, + 200000 + ], + [ + 614400000, + 200000 + ] + ], + "completions": 3015 + }, + { + "idle": [ + [ + 325600000, + 200000 + ], + [ + 334200000, + 200000 + ], + [ + 446800000, + 200000 + ], + [ + 379200000, + 200000 + ], + [ + 467600000, + 200000 + ], + [ + 420200000, + 200000 + ], + [ + 454600000, + 200000 + ], + [ + 430800000, + 200000 + ], + [ + 409800000, + 200000 + ], + [ + 432400000, + 200000 + ] + ], + "busy": [ + [ + 722000000, + 200000 + ], + [ + 633400000, + 200000 + ], + [ + 713000000, + 200000 + ], + [ + 727400000, + 200000 + ], + [ + 725000000, + 200000 + ], + [ + 620000000, + 200000 + ], + [ + 629200000, + 200000 + ], + [ + 610200000, + 200000 + ], + [ + 656600000, + 200000 + ], + [ + 694000000, + 200000 + ], + [ + 684600000, + 200000 + ], + [ + 723600000, + 200000 + ], + [ + 738200000, + 200000 + ], + [ + 666800000, + 200000 + ] + ], + "completions": 3018 + } + ] + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-gpu", + "rounds": [] + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "rounds": [ + { + "idle": [ + [ + 394200000, + 200000 + ], + [ + 401000000, + 200000 + ], + [ + 394800000, + 200000 + ], + [ + 403600000, + 200000 + ], + [ + 395400000, + 200000 + ], + [ + 399800000, + 200000 + ], + [ + 394200000, + 200000 + ], + [ + 396800000, + 200000 + ], + [ + 397000000, + 200000 + ], + [ + 397600000, + 200000 + ] + ], + "busy": [ + [ + 1002000000, + 200000 + ], + [ + 1001200000, + 200000 + ], + [ + 1001000000, + 200000 + ], + [ + 1000001000, + 200000 + ], + [ + 994200000, + 200000 + ], + [ + 1004000000, + 200000 + ], + [ + 994200000, + 200000 + ], + [ + 1005200000, + 200000 + ], + [ + 998200000, + 200000 + ], + [ + 1002200000, + 200000 + ], + [ + 1004400000, + 200000 + ], + [ + 1001200000, + 200000 + ], + [ + 996400000, + 200000 + ], + [ + 1001800000, + 200000 + ] + ], + "completions": 1000 + }, + { + "idle": [ + [ + 402800000, + 200000 + ], + [ + 399800000, + 200000 + ], + [ + 405200000, + 200000 + ], + [ + 404000000, + 200000 + ], + [ + 401600000, + 200000 + ], + [ + 395800000, + 200000 + ], + [ + 396000000, + 200000 + ], + [ + 403000000, + 200000 + ], + [ + 394000000, + 200000 + ], + [ + 403200000, + 200000 + ] + ], + "busy": [ + [ + 994400000, + 200000 + ], + [ + 1002800000, + 200000 + ], + [ + 1003200000, + 200000 + ], + [ + 999600000, + 200000 + ], + [ + 996200000, + 200000 + ], + [ + 1005800000, + 200000 + ], + [ + 996600000, + 200000 + ], + [ + 1004400000, + 200000 + ], + [ + 1003600000, + 200000 + ], + [ + 1002600000, + 200000 + ], + [ + 998400000, + 200000 + ], + [ + 995600000, + 200000 + ], + [ + 1005800000, + 200000 + ], + [ + 1004600000, + 200000 + ] + ], + "completions": 1003 + }, + { + "idle": [ + [ + 403600000, + 200000 + ], + [ + 395200000, + 200000 + ], + [ + 398400000, + 200000 + ], + [ + 403000000, + 200000 + ], + [ + 404600000, + 200000 + ], + [ + 395200000, + 200000 + ], + [ + 400200000, + 200000 + ], + [ + 404400000, + 200000 + ], + [ + 396000000, + 200000 + ], + [ + 401000000, + 200000 + ] + ], + "busy": [ + [ + 1004400000, + 200000 + ], + [ + 1006000000, + 200000 + ], + [ + 1003800000, + 200000 + ], + [ + 1004600000, + 200000 + ], + [ + 1003000000, + 200000 + ], + [ + 998200000, + 200000 + ], + [ + 997200000, + 200000 + ], + [ + 999600000, + 200000 + ], + [ + 999200000, + 200000 + ], + [ + 996800000, + 200000 + ], + [ + 998400000, + 200000 + ], + [ + 999800000, + 200000 + ], + [ + 998000000, + 200000 + ], + [ + 994200000, + 200000 + ] + ], + "completions": 1006 + }, + { + "idle": [ + [ + 403600000, + 200000 + ], + [ + 406000000, + 200000 + ], + [ + 395800000, + 200000 + ], + [ + 400600000, + 200000 + ], + [ + 402600000, + 200000 + ], + [ + 403800000, + 200000 + ], + [ + 400200000, + 200000 + ], + [ + 396000000, + 200000 + ], + [ + 401000000, + 200000 + ], + [ + 395400000, + 200000 + ] + ], + "busy": [ + [ + 994000000, + 200000 + ], + [ + 996400000, + 200000 + ], + [ + 998000000, + 200000 + ], + [ + 996800000, + 200000 + ], + [ + 998400000, + 200000 + ], + [ + 1003200000, + 200000 + ], + [ + 997000000, + 200000 + ], + [ + 1004200000, + 200000 + ], + [ + 1003000000, + 200000 + ], + [ + 1004200000, + 200000 + ], + [ + 997800000, + 200000 + ], + [ + 1001800000, + 200000 + ], + [ + 1002800000, + 200000 + ], + [ + 1001200000, + 200000 + ] + ], + "completions": 1009 + }, + { + "idle": [ + [ + 397800000, + 200000 + ], + [ + 404200000, + 200000 + ], + [ + 394400000, + 200000 + ], + [ + 405600000, + 200000 + ], + [ + 399200000, + 200000 + ], + [ + 397200000, + 200000 + ], + [ + 405800000, + 200000 + ], + [ + 397800000, + 200000 + ], + [ + 397000000, + 200000 + ], + [ + 395800000, + 200000 + ] + ], + "busy": [ + [ + 1002600000, + 200000 + ], + [ + 1001800000, + 200000 + ], + [ + 999400000, + 200000 + ], + [ + 998400000, + 200000 + ], + [ + 1006000000, + 200000 + ], + [ + 1003200000, + 200000 + ], + [ + 1005800000, + 200000 + ], + [ + 998000000, + 200000 + ], + [ + 1005600000, + 200000 + ], + [ + 994400000, + 200000 + ], + [ + 996600000, + 200000 + ], + [ + 997000000, + 200000 + ], + [ + 999400000, + 200000 + ], + [ + 998600000, + 200000 + ] + ], + "completions": 1012 + }, + { + "idle": [ + [ + 401800000, + 200000 + ], + [ + 403400000, + 200000 + ], + [ + 396400000, + 200000 + ], + [ + 401800000, + 200000 + ], + [ + 395400000, + 200000 + ], + [ + 397600000, + 200000 + ], + [ + 397000000, + 200000 + ], + [ + 403400000, + 200000 + ], + [ + 394600000, + 200000 + ], + [ + 402400000, + 200000 + ] + ], + "busy": [ + [ + 1005400000, + 200000 + ], + [ + 1005400000, + 200000 + ], + [ + 1002800000, + 200000 + ], + [ + 994200000, + 200000 + ], + [ + 1002400000, + 200000 + ], + [ + 1000400000, + 200000 + ], + [ + 1004000000, + 200000 + ], + [ + 997400000, + 200000 + ], + [ + 1000000000, + 200000 + ], + [ + 995400000, + 200000 + ], + [ + 996400000, + 200000 + ], + [ + 1003800000, + 200000 + ], + [ + 995600000, + 200000 + ], + [ + 999600000, + 200000 + ] + ], + "completions": 1015 + }, + { + "idle": [ + [ + 402000000, + 200000 + ], + [ + 399200000, + 200000 + ], + [ + 398400000, + 200000 + ], + [ + 402400000, + 200000 + ], + [ + 402000000, + 200000 + ], + [ + 404400000, + 200000 + ], + [ + 404000000, + 200000 + ], + [ + 405200000, + 200000 + ], + [ + 403000000, + 200000 + ], + [ + 396800000, + 200000 + ] + ], + "busy": [ + [ + 1003200000, + 200000 + ], + [ + 997200000, + 200000 + ], + [ + 1005800000, + 200000 + ], + [ + 997600000, + 200000 + ], + [ + 996800000, + 200000 + ], + [ + 996600000, + 200000 + ], + [ + 1003000000, + 200000 + ], + [ + 999200000, + 200000 + ], + [ + 999600000, + 200000 + ], + [ + 995200000, + 200000 + ], + [ + 1005800000, + 200000 + ], + [ + 1000000000, + 200000 + ], + [ + 1000400000, + 200000 + ], + [ + 1003000000, + 200000 + ] + ], + "completions": 1018 + } + ] + }, + { + "capability": "wai.video.vvc", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": null, + "rounds": [] + } + ] + }, + "tables": [], + "segments": [], + "hops": [], + "expect": "table_readings_mismatch", + "identity": null + }, + { + "name": "table, a reading changed and its digest rebound", + "subject": "table", + "record": { + "wai_sink_table": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "procedure": "wai-sink-table/1", + "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", + "readings_sha256": "b09ba874428370173cf5e3bb124e12c1b738e1dacf46260574d9e43db9a3998a", + "machine": { + "os": "corpus", + "arch": "corpus", + "processor": "synthetic", + "cores": 16, + "memory_gib": 64 + }, + "build": { + "profile": "release", + "toolchain": null, + "target": null + }, + "measured": "2026-10-22", + "rounds": 7, + "sample_us": 200000, + "counters": [ + { + "profile": "macos-gpu", + "status": "frozen", + "detail": "Energy Model/GPU Energy" + }, + { + "profile": "macos-task", + "status": "live", + "detail": "proc_pid_rusage V6" + } + ], + "rows": [ + { + "capability": "wai.audio.flac", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "content-ms", + "units": 10000, + "width": null, + "height": null, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 50 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 53 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 56 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 59 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 62 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 65 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 68 + } + ], + "status": "unmetered", + "reason": "rail_not_live", + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_hardware_path", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2144400, + "sd_uw": 356790, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2652643, + "sd_uw": 391226, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 400 + }, + { + "idle": { + "n": 10, + "mean_uw": 2212000, + "sd_uw": 397991, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2606000, + "sd_uw": 332414, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 403 + }, + { + "idle": { + "n": 10, + "mean_uw": 2066400, + "sd_uw": 307198, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2586286, + "sd_uw": 342938, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 406 + }, + { + "idle": { + "n": 10, + "mean_uw": 1885700, + "sd_uw": 332615, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2577429, + "sd_uw": 398088, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 409 + }, + { + "idle": { + "n": 10, + "mean_uw": 2074500, + "sd_uw": 452434, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2637429, + "sd_uw": 386990, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 412 + }, + { + "idle": { + "n": 10, + "mean_uw": 2040300, + "sd_uw": 384214, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2626143, + "sd_uw": 418958, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 415 + }, + { + "idle": { + "n": 10, + "mean_uw": 1851400, + "sd_uw": 287343, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2489429, + "sd_uw": 369320, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 418 + } + ], + "status": "unmetered", + "reason": "below_attribution_floor", + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 1890800, + "sd_uw": 173618, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3406857, + "sd_uw": 291462, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3000 + }, + { + "idle": { + "n": 10, + "mean_uw": 2003700, + "sd_uw": 258242, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3502000, + "sd_uw": 238914, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3003 + }, + { + "idle": { + "n": 10, + "mean_uw": 2141600, + "sd_uw": 275043, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3416357, + "sd_uw": 204354, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3006 + }, + { + "idle": { + "n": 10, + "mean_uw": 2028200, + "sd_uw": 196893, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3475500, + "sd_uw": 226251, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3009 + }, + { + "idle": { + "n": 10, + "mean_uw": 2040400, + "sd_uw": 214134, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3434357, + "sd_uw": 223672, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3012 + }, + { + "idle": { + "n": 10, + "mean_uw": 2030100, + "sd_uw": 307907, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3404571, + "sd_uw": 219703, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3015 + }, + { + "idle": { + "n": 10, + "mean_uw": 2050600, + "sd_uw": 244479, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3408571, + "sd_uw": 225896, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3018 + } + ], + "status": "relative", + "reason": null, + "median": 4, + "range": [ + 1186, + 1413 + ], + "joules_micro": 1294, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 64810, + "window_us": 2800000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-gpu", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_counter", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 1987200, + "sd_uw": 15719, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5002143, + "sd_uw": 17502, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1000 + }, + { + "idle": { + "n": 10, + "mean_uw": 2002700, + "sd_uw": 19682, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5004857, + "sd_uw": 20184, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1003 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000800, + "sd_uw": 18850, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001143, + "sd_uw": 17629, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1006 + }, + { + "idle": { + "n": 10, + "mean_uw": 2002500, + "sd_uw": 18549, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 4999571, + "sd_uw": 16787, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1009 + }, + { + "idle": { + "n": 10, + "mean_uw": 1997400, + "sd_uw": 20824, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5002429, + "sd_uw": 18559, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1012 + }, + { + "idle": { + "n": 10, + "mean_uw": 1996900, + "sd_uw": 17445, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001000, + "sd_uw": 19415, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1015 + }, + { + "idle": { + "n": 10, + "mean_uw": 2008700, + "sd_uw": 13777, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001214, + "sd_uw": 17215, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1018 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 8199, + 8408 + ], + "joules_micro": 8284, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 7284, + "window_us": 2800000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vvc", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": null, + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } + ], + "sig": "72f80ff03686b7ea8a6934d15afba701536e4cef287750c13f4666069788c977b64d18a73002f6d47e8f943c2571bf3a8120a1ab94dcb1708900e686a860e20e" + }, + "readings": { + "wai_sink_readings": 1, + "rows": [ + { + "capability": "wai.audio.flac", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "rounds": [ + { + "idle": [ + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ] + ], + "busy": [ + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ] + ], + "completions": 50 + }, + { + "idle": [ + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ] + ], + "busy": [ + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ] + ], + "completions": 53 + }, + { + "idle": [ + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ] + ], + "busy": [ + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ] + ], + "completions": 56 + }, + { + "idle": [ + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ] + ], + "busy": [ + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ] + ], + "completions": 59 + }, + { + "idle": [ + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ] + ], + "busy": [ + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ] + ], + "completions": 62 + }, + { + "idle": [ + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ] + ], + "busy": [ + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ] + ], + "completions": 65 + }, + { + "idle": [ + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ], + [ + 400000000, + 200000 + ] + ], + "busy": [ + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ], + [ + 580000000, + 200000 + ] + ], + "completions": 68 + } + ] + }, + { + "capability": "wai.image.png", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "rounds": [] + }, + { + "capability": "wai.image.png", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "rounds": [ + { + "idle": [ + [ + 347600000, + 200000 + ], + [ + 525000000, + 200000 + ], + [ + 387000000, + 200000 + ], + [ + 347400000, + 200000 + ], + [ + 380600000, + 200000 + ], + [ + 458200000, + 200000 + ], + [ + 531400000, + 200000 + ], + [ + 487000000, + 200000 + ], + [ + 367800000, + 200000 + ], + [ + 456800000, + 200000 + ] + ], + "busy": [ + [ + 617600000, + 200000 + ], + [ + 477800000, + 200000 + ], + [ + 397200000, + 200000 + ], + [ + 627600000, + 200000 + ], + [ + 611800000, + 200000 + ], + [ + 525000000, + 200000 + ], + [ + 519400000, + 200000 + ], + [ + 481000000, + 200000 + ], + [ + 523800000, + 200000 + ], + [ + 405000000, + 200000 + ], + [ + 560000000, + 200000 + ], + [ + 612400000, + 200000 + ], + [ + 465800000, + 200000 + ], + [ + 603000000, + 200000 + ] + ], + "completions": 400 + }, + { + "idle": [ + [ + 507200000, + 200000 + ], + [ + 491600000, + 200000 + ], + [ + 367600000, + 200000 + ], + [ + 277600000, + 200000 + ], + [ + 471400000, + 200000 + ], + [ + 490600000, + 200000 + ], + [ + 377800000, + 200000 + ], + [ + 536000000, + 200000 + ], + [ + 426000000, + 200000 + ], + [ + 478200000, + 200000 + ] + ], + "busy": [ + [ + 476000000, + 200000 + ], + [ + 426000000, + 200000 + ], + [ + 511200000, + 200000 + ], + [ + 577600000, + 200000 + ], + [ + 580600000, + 200000 + ], + [ + 390800000, + 200000 + ], + [ + 570400000, + 200000 + ], + [ + 588200000, + 200000 + ], + [ + 497800000, + 200000 + ], + [ + 572800000, + 200000 + ], + [ + 582200000, + 200000 + ], + [ + 434000000, + 200000 + ], + [ + 544600000, + 200000 + ], + [ + 544600000, + 200000 + ] + ], + "completions": 403 + }, + { + "idle": [ + [ + 421400000, + 200000 + ], + [ + 462800000, + 200000 + ], + [ + 515800000, + 200000 + ], + [ + 409200000, + 200000 + ], + [ + 307800000, + 200000 + ], + [ + 366600000, + 200000 + ], + [ + 401000000, + 200000 + ], + [ + 372400000, + 200000 + ], + [ + 390000000, + 200000 + ], + [ + 485800000, + 200000 + ] + ], + "busy": [ + [ + 547400000, + 200000 + ], + [ + 502000000, + 200000 + ], + [ + 399400000, + 200000 + ], + [ + 484600000, + 200000 + ], + [ + 472600000, + 200000 + ], + [ + 481400000, + 200000 + ], + [ + 453800000, + 200000 + ], + [ + 525000000, + 200000 + ], + [ + 527400000, + 200000 + ], + [ + 451400000, + 200000 + ], + [ + 581400000, + 200000 + ], + [ + 645000000, + 200000 + ], + [ + 627600000, + 200000 + ], + [ + 542600000, + 200000 + ] + ], + "completions": 406 + }, + { + "idle": [ + [ + 378400000, + 200000 + ], + [ + 437200000, + 200000 + ], + [ + 335800000, + 200000 + ], + [ + 268400000, + 200000 + ], + [ + 489000000, + 200000 + ], + [ + 441400000, + 200000 + ], + [ + 363800000, + 200000 + ], + [ + 300600000, + 200000 + ], + [ + 379800000, + 200000 + ], + [ + 377000000, + 200000 + ] + ], + "busy": [ + [ + 454800000, + 200000 + ], + [ + 474200000, + 200000 + ], + [ + 619600000, + 200000 + ], + [ + 424000000, + 200000 + ], + [ + 554600000, + 200000 + ], + [ + 464400000, + 200000 + ], + [ + 569000000, + 200000 + ], + [ + 624400000, + 200000 + ], + [ + 614800000, + 200000 + ], + [ + 440800000, + 200000 + ], + [ + 427400000, + 200000 + ], + [ + 526200000, + 200000 + ], + [ + 599600000, + 200000 + ], + [ + 423000000, + 200000 + ] + ], + "completions": 409 + }, + { + "idle": [ + [ + 466200000, + 200000 + ], + [ + 477400000, + 200000 + ], + [ + 331800000, + 200000 + ], + [ + 274400000, + 200000 + ], + [ + 472200000, + 200000 + ], + [ + 497200000, + 200000 + ], + [ + 285400000, + 200000 + ], + [ + 489600000, + 200000 + ], + [ + 365000000, + 200000 + ], + [ + 489800000, + 200000 + ] + ], + "busy": [ + [ + 496400000, + 200000 + ], + [ + 588600000, + 200000 + ], + [ + 586000000, + 200000 + ], + [ + 584400000, + 200000 + ], + [ + 631200000, + 200000 + ], + [ + 505400000, + 200000 + ], + [ + 419000000, + 200000 + ], + [ + 430600000, + 200000 + ], + [ + 583400000, + 200000 + ], + [ + 588600000, + 200000 + ], + [ + 516600000, + 200000 + ], + [ + 398800000, + 200000 + ], + [ + 455200000, + 200000 + ], + [ + 600600000, + 200000 + ] + ], + "completions": 412 + }, + { + "idle": [ + [ + 414800000, + 200000 + ], + [ + 393600000, + 200000 + ], + [ + 277000000, + 200000 + ], + [ + 485000000, + 200000 + ], + [ + 382800000, + 200000 + ], + [ + 318800000, + 200000 + ], + [ + 489200000, + 200000 + ], + [ + 467200000, + 200000 + ], + [ + 353800000, + 200000 + ], + [ + 498400000, + 200000 + ] + ], + "busy": [ + [ + 449400000, + 200000 + ], + [ + 612800000, + 200000 + ], + [ + 630600000, + 200000 + ], + [ + 460600000, + 200000 + ], + [ + 637600000, + 200000 + ], + [ + 616200000, + 200000 + ], + [ + 499800000, + 200000 + ], + [ + 444400000, + 200000 + ], + [ + 513400000, + 200000 + ], + [ + 622400000, + 200000 + ], + [ + 408800000, + 200000 + ], + [ + 540800000, + 200000 + ], + [ + 425600000, + 200000 + ], + [ + 490800000, + 200000 + ] + ], + "completions": 415 + }, + { + "idle": [ + [ + 406200000, + 200000 + ], + [ + 266600000, + 200000 + ], + [ + 354400000, + 200000 + ], + [ + 431800000, + 200000 + ], + [ + 390600000, + 200000 + ], + [ + 322200000, + 200000 + ], + [ + 430400000, + 200000 + ], + [ + 426800000, + 200000 + ], + [ + 367600000, + 200000 + ], + [ + 306200000, + 200000 + ] + ], + "busy": [ + [ + 444000000, + 200000 + ], + [ + 581000000, + 200000 + ], + [ + 417200000, + 200000 + ], + [ + 476000000, + 200000 + ], + [ + 494600000, + 200000 + ], + [ + 561400000, + 200000 + ], + [ + 513400000, + 200000 + ], + [ + 436800000, + 200000 + ], + [ + 650400000, + 200000 + ], + [ + 437200000, + 200000 + ], + [ + 498000000, + 200000 + ], + [ + 388200000, + 200000 + ], + [ + 490200000, + 200000 + ], + [ + 582000000, + 200000 + ] + ], + "completions": 418 + } + ] + }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "rounds": [] + }, { "capability": "wai.video.av1", "path": "hardware", @@ -17017,8 +21083,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "8a4050461b7c303aeb9eaee0b02a32c543737b306dfa563eaa62f420f42995c4", - "row": 5, + "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -17029,7 +21095,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "b6b9e97c26097344c26236620635dfdb8969a1fcc29c5035be01f4e674c860b9ec9835bedaf6519c7aaef9139da3ec1508f7ddc82c3e7200a429d4bb2c736e0c" + "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" }, "readings": null, "tables": [ @@ -17038,7 +21104,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -17394,6 +21460,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -17757,13 +21846,13 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" } ], "segments": [], "hops": [], "expect": "accept", - "identity": "af4753cdc24d6168da0ca24bc0483a56175fa93970af4d0cd9fbcd2e0cc7a9f9" + "identity": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07" }, { "name": "segment, from a table not held", @@ -17792,8 +21881,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "8a4050461b7c303aeb9eaee0b02a32c543737b306dfa563eaa62f420f42995c4", - "row": 5, + "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -17804,14 +21893,14 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "b6b9e97c26097344c26236620635dfdb8969a1fcc29c5035be01f4e674c860b9ec9835bedaf6519c7aaef9139da3ec1508f7ddc82c3e7200a429d4bb2c736e0c" + "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" }, "readings": null, "tables": [], "segments": [], "hops": [], "expect": "accept", - "identity": "af4753cdc24d6168da0ca24bc0483a56175fa93970af4d0cd9fbcd2e0cc7a9f9" + "identity": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07" }, { "name": "segment, from another key's table", @@ -17840,8 +21929,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "f6afdc2d8b166ec78f92cc091c0a99798295385d6209331f02ec46c5ccd6663f", - "row": 5, + "id": "bd74e55dbd7822aec58d2097175e05c974e5d6f93c9b048c3350f94c346dfaff", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -17852,7 +21941,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "9104cd7a478c5134d5e4659c801e265b70efb264994097d24955336ec010e0c20cec62694e790161b5b85ef08029ce61e273291b45740da778225bd335dfdf01" + "sig": "c173f9db9a7efd77482a1367d4096f6f94ddf08251306b45775e893f2679f1296005289ea126b66285a83c7c02c97ce7839367309b869c9205df3babf64b1308" }, "readings": null, "tables": [ @@ -17861,7 +21950,7 @@ "signer": "849e713d6bb5b2daf8790b7d6ed3779d08d3a71fa30fe14b230a5539ff54e6a9", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -18217,6 +22306,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -18580,13 +22692,13 @@ "energy_calibration_ref": null } ], - "sig": "7a01ed90093ae27c33717df9f0dc41f7b5e76df9cf2b665763d938f10cd56847f94fdea03808d4b06474d7e743aae78c17d630fad9ac364b4ca9778c46ca2809" + "sig": "80d62d05c16342e453409ec2b4cd034c8863029734bf8f1e3fcbdf2c1a969aba26b518f1e6239e427d810d75f612972f78982cb150f45e13ba1bbeb3f825d508" } ], "segments": [], "hops": [], "expect": "accept", - "identity": "600185da5f261f29bccaf76694ed5d1df94dce2cc56d23368fafd7ce930d860c" + "identity": "83e0d157261d6a7272d72dbba17a9c319ab7938615a2562f6eebed9222147624" }, { "name": "segment, estimated", @@ -18646,7 +22758,7 @@ "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segment": 2, - "prev": "af4753cdc24d6168da0ca24bc0483a56175fa93970af4d0cd9fbcd2e0cc7a9f9", + "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", "capability": "wai.video.av1", "path": "software", @@ -18669,14 +22781,14 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "994c3a44fe1b93f79eaf61f564e4ad6bff639d40942c0650624fcb8f2ac04fc81989a447588ea024eaa274793437b2c8874e55582635e13bd3f6b2009889d105" + "sig": "117485aa23543d0f3f6889adb8ed04262700e16edcab1fc70bc7fde3c9bc2c7dd58712375b5a52716d3ae0c7d626263a8e875dd8b767a3a8d994b887da43e309" }, "readings": null, "tables": [], "segments": [], "hops": [], "expect": "accept", - "identity": "a0723c48caf8cf42fdccc6cd859000a22cec0ffcbdff18acd2dcaa910f5f124f" + "identity": "9ca9c2eff1d8b74b8cf7f08758f611900f78387c091eb54e5ce4717378716ed0" }, { "name": "segment, measured but ModelBased", @@ -19080,8 +23192,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "8a4050461b7c303aeb9eaee0b02a32c543737b306dfa563eaa62f420f42995c4", - "row": 5, + "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", + "row": 6, "per_completion_uj": 8344, "units_per_completion": 60 }, @@ -19092,7 +23204,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "0fd74fec1ba5d0b116aae147111516e7c7c72f84a3e02edacf2dedadb986ddfbd3d79f5aadabb6eb1bb4c85aa9fcbb0d61a0f6e41a81bbc9a9176de25a05ad0a" + "sig": "efb75168fb71a06d57ffb08a7fda617be40910c38a74d02c9e155dad03a6712a8046dd569d4d5439da410658708cc1a76806b38b5774f72bb13d231fc8850005" }, "readings": null, "tables": [ @@ -19101,7 +23213,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -19457,6 +23569,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -19820,7 +23955,7 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" } ], "segments": [], @@ -19878,16 +24013,16 @@ "identity": null }, { - "name": "segment, a figure beside no class", + "name": "segment, segment 0 naming a previous receipt", "subject": "segment", "record": { "wai_sink_segment": 1, "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", - "segment": 2, - "prev": "af4753cdc24d6168da0ca24bc0483a56175fa93970af4d0cd9fbcd2e0cc7a9f9", - "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", + "segment": 0, + "prev": "4d33f4ffbf8693946535d25bc70f1079c3a50190cb56b2595b09df746cdd1a36", + "object": "904fcef85345a6ab4cc7388f8a9d6a52700713dd5f0afab36deb9b859b0d5bf2", "capability": "wai.video.av1", "path": "software", "decoder": "builtin:wai@0.0.0-corpus", @@ -19896,32 +24031,91 @@ "width": 1280, "height": 720, "media_us": 2000000, - "method": "unmetered", - "profile": null, - "fabrics": [], - "coverage": null, - "bracket": null, + "method": "measured", + "profile": "macos-task", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 40000, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 10, + "mean_uw": 2900000, + "sd_uw": 60000, + "live_intervals": 10, + "longest_live_run": 10 + }, + "window_us": 2000000, + "sample_us": 200000 + }, "table": null, "estimator": null, - "reason": "not_reported", - "joules_micro": 5, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, + "reason": null, + "joules_micro": 1800000, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 25338, + "window_us": 2000000 + }, + "energy_counter_filtering": "Unknown", "energy_calibration_ref": null, - "sig": "50a6236bfa6a2ca4134c20c95d2d520197dc8c4d928482e55e26d11902fe65a4ad2f2cfb8acf60a010ffa40160c8469ba07b7438dd3334b6a1adb7b34e3f640b" + "sig": "91d40465839f3dfbd7292e917c310116f7fded1c6d86ae32b4c88642424e515c401b17f22a096208f688fd7373ed1339843c6fe74f5c78ac43fee81f7d8f270e" }, "readings": null, "tables": [], "segments": [], "hops": [], - "expect": "unmetered_as_zero", + "expect": "record_malformed", "identity": null }, { - "name": "chain, three segments", - "subject": "chain", - "record": null, + "name": "segment, from a held table that is refused", + "subject": "segment", + "record": { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", + "segment": 1, + "prev": "2299cf3c7fb5fc13ac56cad34b0f656487dbfdde2f30ad1e9f6ed7a754eb238a", + "object": "2ec96fd7e1b71588a9b60b1cdf1dc4bc7aea8f2ab33eb3cffd9d987b1ef87698", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "table", + "profile": "macos-task", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": null, + "table": { + "id": "233a73330dc33fa4f9431cbe2ec77f17fa468b968bd17dd8a612c2a299198a58", + "row": 6, + "per_completion_uj": 8284, + "units_per_completion": 60 + }, + "estimator": null, + "reason": null, + "joules_micro": 6627, + "energy_provenance": "ModelBased", + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null, + "sig": "e12fc4bc7e37b55d8150c6a40706a488adee32786387b69f6aac462910dda2b38b69efe46f95b6ca10bfc5faead45f69ac435b78c21ac2251f25b8eb75753e0e" + }, "readings": null, "tables": [ { @@ -19929,7 +24123,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -19943,7 +24137,7 @@ "target": null }, "measured": "2026-10-22", - "rounds": 7, + "rounds": 8, "sample_us": 200000, "counters": [ { @@ -20285,6 +24479,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -20648,7 +24865,857 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "6dd9f4d69b794c790222e04515130ba1f0e9895af590048a27b358f7c8c7f51e591070dbfbdf79ab74d1da4dbac79a5ffd6dc4fa78dd3b1d2d0c1a6abb03eb02" + } + ], + "segments": [], + "hops": [], + "expect": "segment_table_mismatch", + "identity": null + }, + { + "name": "segment, from a refused table not held", + "subject": "segment", + "record": { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", + "segment": 1, + "prev": "2299cf3c7fb5fc13ac56cad34b0f656487dbfdde2f30ad1e9f6ed7a754eb238a", + "object": "2ec96fd7e1b71588a9b60b1cdf1dc4bc7aea8f2ab33eb3cffd9d987b1ef87698", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "table", + "profile": "macos-task", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": null, + "table": { + "id": "233a73330dc33fa4f9431cbe2ec77f17fa468b968bd17dd8a612c2a299198a58", + "row": 6, + "per_completion_uj": 8284, + "units_per_completion": 60 + }, + "estimator": null, + "reason": null, + "joules_micro": 6627, + "energy_provenance": "ModelBased", + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null, + "sig": "e12fc4bc7e37b55d8150c6a40706a488adee32786387b69f6aac462910dda2b38b69efe46f95b6ca10bfc5faead45f69ac435b78c21ac2251f25b8eb75753e0e" + }, + "readings": null, + "tables": [], + "segments": [], + "hops": [], + "expect": "accept", + "identity": "6f4195d2ac02f657e6dd493fdb4029f45f967c25157affa2bbf16cd0c8719724" + }, + { + "name": "segment, a figure beside no class", + "subject": "segment", + "record": { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", + "segment": 2, + "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", + "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "unmetered", + "profile": null, + "fabrics": [], + "coverage": null, + "bracket": null, + "table": null, + "estimator": null, + "reason": "not_reported", + "joules_micro": 5, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null, + "sig": "50b751a9f013c04ab53e1b73e8e209f352a724ef61845abf84942697293bd12fd60839df30f77b874c5c9035355fe3399643c3173e436237d3f854d2c30e9e03" + }, + "readings": null, + "tables": [], + "segments": [], + "hops": [], + "expect": "unmetered_as_zero", + "identity": null + }, + { + "name": "chain, three segments", + "subject": "chain", + "record": null, + "readings": null, + "tables": [ + { + "wai_sink_table": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "procedure": "wai-sink-table/1", + "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", + "machine": { + "os": "corpus", + "arch": "corpus", + "processor": "synthetic", + "cores": 16, + "memory_gib": 64 + }, + "build": { + "profile": "release", + "toolchain": null, + "target": null + }, + "measured": "2026-10-22", + "rounds": 7, + "sample_us": 200000, + "counters": [ + { + "profile": "macos-gpu", + "status": "frozen", + "detail": "Energy Model/GPU Energy" + }, + { + "profile": "macos-task", + "status": "live", + "detail": "proc_pid_rusage V6" + } + ], + "rows": [ + { + "capability": "wai.audio.flac", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "content-ms", + "units": 10000, + "width": null, + "height": null, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 50 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 53 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 56 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 59 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 62 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 65 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 68 + } + ], + "status": "unmetered", + "reason": "rail_not_live", + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_hardware_path", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2144400, + "sd_uw": 356790, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2652643, + "sd_uw": 391226, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 400 + }, + { + "idle": { + "n": 10, + "mean_uw": 2212000, + "sd_uw": 397991, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2606000, + "sd_uw": 332414, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 403 + }, + { + "idle": { + "n": 10, + "mean_uw": 2066400, + "sd_uw": 307198, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2586286, + "sd_uw": 342938, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 406 + }, + { + "idle": { + "n": 10, + "mean_uw": 1885700, + "sd_uw": 332615, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2577429, + "sd_uw": 398088, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 409 + }, + { + "idle": { + "n": 10, + "mean_uw": 2074500, + "sd_uw": 452434, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2637429, + "sd_uw": 386990, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 412 + }, + { + "idle": { + "n": 10, + "mean_uw": 2040300, + "sd_uw": 384214, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2626143, + "sd_uw": 418958, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 415 + }, + { + "idle": { + "n": 10, + "mean_uw": 1851400, + "sd_uw": 287343, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2489429, + "sd_uw": 369320, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 418 + } + ], + "status": "unmetered", + "reason": "below_attribution_floor", + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 1890800, + "sd_uw": 173618, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3406857, + "sd_uw": 291462, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3000 + }, + { + "idle": { + "n": 10, + "mean_uw": 2003700, + "sd_uw": 258242, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3502000, + "sd_uw": 238914, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3003 + }, + { + "idle": { + "n": 10, + "mean_uw": 2141600, + "sd_uw": 275043, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3416357, + "sd_uw": 204354, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3006 + }, + { + "idle": { + "n": 10, + "mean_uw": 2028200, + "sd_uw": 196893, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3475500, + "sd_uw": 226251, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3009 + }, + { + "idle": { + "n": 10, + "mean_uw": 2040400, + "sd_uw": 214134, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3434357, + "sd_uw": 223672, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3012 + }, + { + "idle": { + "n": 10, + "mean_uw": 2030100, + "sd_uw": 307907, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3404571, + "sd_uw": 219703, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3015 + }, + { + "idle": { + "n": 10, + "mean_uw": 2050600, + "sd_uw": 244479, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3408571, + "sd_uw": 225896, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3018 + } + ], + "status": "relative", + "reason": null, + "median": 4, + "range": [ + 1186, + 1413 + ], + "joules_micro": 1294, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 64810, + "window_us": 2800000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-gpu", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_counter", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 1987200, + "sd_uw": 15719, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5002143, + "sd_uw": 17502, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1000 + }, + { + "idle": { + "n": 10, + "mean_uw": 2002700, + "sd_uw": 19682, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5004857, + "sd_uw": 20184, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1003 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000800, + "sd_uw": 18850, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001143, + "sd_uw": 17629, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1006 + }, + { + "idle": { + "n": 10, + "mean_uw": 2002500, + "sd_uw": 18549, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 4999571, + "sd_uw": 16787, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1009 + }, + { + "idle": { + "n": 10, + "mean_uw": 1997400, + "sd_uw": 20824, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5002429, + "sd_uw": 18559, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1012 + }, + { + "idle": { + "n": 10, + "mean_uw": 1996900, + "sd_uw": 17445, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001000, + "sd_uw": 19415, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1015 + }, + { + "idle": { + "n": 10, + "mean_uw": 2008700, + "sd_uw": 13777, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001214, + "sd_uw": 17215, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1018 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 8199, + 8408 + ], + "joules_micro": 8284, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 7284, + "window_us": 2800000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vvc", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": null, + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } + ], + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" } ], "segments": [ @@ -20729,8 +25796,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "8a4050461b7c303aeb9eaee0b02a32c543737b306dfa563eaa62f420f42995c4", - "row": 5, + "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -20741,7 +25808,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "b6b9e97c26097344c26236620635dfdb8969a1fcc29c5035be01f4e674c860b9ec9835bedaf6519c7aaef9139da3ec1508f7ddc82c3e7200a429d4bb2c736e0c" + "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" }, { "wai_sink_segment": 1, @@ -20749,7 +25816,7 @@ "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segment": 2, - "prev": "af4753cdc24d6168da0ca24bc0483a56175fa93970af4d0cd9fbcd2e0cc7a9f9", + "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", "capability": "wai.video.av1", "path": "software", @@ -20772,12 +25839,12 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "994c3a44fe1b93f79eaf61f564e4ad6bff639d40942c0650624fcb8f2ac04fc81989a447588ea024eaa274793437b2c8874e55582635e13bd3f6b2009889d105" + "sig": "117485aa23543d0f3f6889adb8ed04262700e16edcab1fc70bc7fde3c9bc2c7dd58712375b5a52716d3ae0c7d626263a8e875dd8b767a3a8d994b887da43e309" } ], "hops": [], "expect": "accept", - "identity": "a0723c48caf8cf42fdccc6cd859000a22cec0ffcbdff18acd2dcaa910f5f124f" + "identity": "9ca9c2eff1d8b74b8cf7f08758f611900f78387c091eb54e5ce4717378716ed0" }, { "name": "chain, a segment missing", @@ -20790,7 +25857,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -21146,6 +26213,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -21509,7 +26599,7 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" } ], "segments": [ @@ -21613,12 +26703,12 @@ { "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segments": 3, - "last": "a0723c48caf8cf42fdccc6cd859000a22cec0ffcbdff18acd2dcaa910f5f124f" + "last": "9ca9c2eff1d8b74b8cf7f08758f611900f78387c091eb54e5ce4717378716ed0" }, { "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", "segments": 2, - "last": "600185da5f261f29bccaf76694ed5d1df94dce2cc56d23368fafd7ce930d860c" + "last": "83e0d157261d6a7272d72dbba17a9c319ab7938615a2562f6eebed9222147624" } ], "media_us": 10000000, @@ -21685,7 +26775,7 @@ "energy_calibration_ref": null } }, - "sig": "70b9b6acc728ac4292554813c8a40b2d106070a3c52f95fb522f5f3ea57e2e3eabcdaf0fe7dabef8adde84796e52c32bf84ecf2a02287c348f390ab2e3045707" + "sig": "90e6034baeea1de600f605ec51da42281c7b62c9c382d08d3001f8f19baa4e94e7e61721d1a8df5e10553cfff42afdc89332fbaab20bfe9e336da9069d212002" }, "readings": null, "tables": [ @@ -21694,7 +26784,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -22050,6 +27140,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -22413,14 +27526,14 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" }, { "wai_sink_table": 1, "signer": "849e713d6bb5b2daf8790b7d6ed3779d08d3a71fa30fe14b230a5539ff54e6a9", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -22776,6 +27889,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -23139,7 +28275,7 @@ "energy_calibration_ref": null } ], - "sig": "7a01ed90093ae27c33717df9f0dc41f7b5e76df9cf2b665763d938f10cd56847f94fdea03808d4b06474d7e743aae78c17d630fad9ac364b4ca9778c46ca2809" + "sig": "80d62d05c16342e453409ec2b4cd034c8863029734bf8f1e3fcbdf2c1a969aba26b518f1e6239e427d810d75f612972f78982cb150f45e13ba1bbeb3f825d508" } ], "segments": [ @@ -23220,8 +28356,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "8a4050461b7c303aeb9eaee0b02a32c543737b306dfa563eaa62f420f42995c4", - "row": 5, + "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -23232,7 +28368,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "b6b9e97c26097344c26236620635dfdb8969a1fcc29c5035be01f4e674c860b9ec9835bedaf6519c7aaef9139da3ec1508f7ddc82c3e7200a429d4bb2c736e0c" + "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" }, { "wai_sink_segment": 1, @@ -23240,7 +28376,7 @@ "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segment": 2, - "prev": "af4753cdc24d6168da0ca24bc0483a56175fa93970af4d0cd9fbcd2e0cc7a9f9", + "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", "capability": "wai.video.av1", "path": "software", @@ -23263,7 +28399,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "994c3a44fe1b93f79eaf61f564e4ad6bff639d40942c0650624fcb8f2ac04fc81989a447588ea024eaa274793437b2c8874e55582635e13bd3f6b2009889d105" + "sig": "117485aa23543d0f3f6889adb8ed04262700e16edcab1fc70bc7fde3c9bc2c7dd58712375b5a52716d3ae0c7d626263a8e875dd8b767a3a8d994b887da43e309" }, { "wai_sink_segment": 1, @@ -23328,8 +28464,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "f6afdc2d8b166ec78f92cc091c0a99798295385d6209331f02ec46c5ccd6663f", - "row": 5, + "id": "bd74e55dbd7822aec58d2097175e05c974e5d6f93c9b048c3350f94c346dfaff", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -23340,14 +28476,14 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "9104cd7a478c5134d5e4659c801e265b70efb264994097d24955336ec010e0c20cec62694e790161b5b85ef08029ce61e273291b45740da778225bd335dfdf01" + "sig": "c173f9db9a7efd77482a1367d4096f6f94ddf08251306b45775e893f2679f1296005289ea126b66285a83c7c02c97ce7839367309b869c9205df3babf64b1308" } ], "hops": [ "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f" ], "expect": "accept", - "identity": "e33e87600f0538eb5ead08d161be7ff4bf2b6145dc4d0aa51bd4dc4335d93605" + "identity": "8f4df2220d026f31915f6964d0d3af327e97378f90deb250d63bc22723fd3d69" }, { "name": "session, the relay's whole figure", @@ -23501,12 +28637,12 @@ { "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segments": 3, - "last": "a0723c48caf8cf42fdccc6cd859000a22cec0ffcbdff18acd2dcaa910f5f124f" + "last": "9ca9c2eff1d8b74b8cf7f08758f611900f78387c091eb54e5ce4717378716ed0" }, { "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", "segments": 2, - "last": "600185da5f261f29bccaf76694ed5d1df94dce2cc56d23368fafd7ce930d860c" + "last": "83e0d157261d6a7272d72dbba17a9c319ab7938615a2562f6eebed9222147624" } ], "media_us": 10000000, @@ -23573,7 +28709,7 @@ "energy_calibration_ref": null } }, - "sig": "70b9b6acc728ac4292554813c8a40b2d106070a3c52f95fb522f5f3ea57e2e3eabcdaf0fe7dabef8adde84796e52c32bf84ecf2a02287c348f390ab2e3045707" + "sig": "90e6034baeea1de600f605ec51da42281c7b62c9c382d08d3001f8f19baa4e94e7e61721d1a8df5e10553cfff42afdc89332fbaab20bfe9e336da9069d212002" }, "readings": null, "tables": [ @@ -23582,7 +28718,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -23938,6 +29074,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -24301,14 +29460,14 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" }, { "wai_sink_table": 1, "signer": "849e713d6bb5b2daf8790b7d6ed3779d08d3a71fa30fe14b230a5539ff54e6a9", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -24664,6 +29823,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -25027,7 +30209,7 @@ "energy_calibration_ref": null } ], - "sig": "7a01ed90093ae27c33717df9f0dc41f7b5e76df9cf2b665763d938f10cd56847f94fdea03808d4b06474d7e743aae78c17d630fad9ac364b4ca9778c46ca2809" + "sig": "80d62d05c16342e453409ec2b4cd034c8863029734bf8f1e3fcbdf2c1a969aba26b518f1e6239e427d810d75f612972f78982cb150f45e13ba1bbeb3f825d508" } ], "segments": [ @@ -25108,8 +30290,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "8a4050461b7c303aeb9eaee0b02a32c543737b306dfa563eaa62f420f42995c4", - "row": 5, + "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -25120,7 +30302,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "b6b9e97c26097344c26236620635dfdb8969a1fcc29c5035be01f4e674c860b9ec9835bedaf6519c7aaef9139da3ec1508f7ddc82c3e7200a429d4bb2c736e0c" + "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" }, { "wai_sink_segment": 1, @@ -25128,7 +30310,7 @@ "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segment": 2, - "prev": "af4753cdc24d6168da0ca24bc0483a56175fa93970af4d0cd9fbcd2e0cc7a9f9", + "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", "capability": "wai.video.av1", "path": "software", @@ -25151,7 +30333,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "994c3a44fe1b93f79eaf61f564e4ad6bff639d40942c0650624fcb8f2ac04fc81989a447588ea024eaa274793437b2c8874e55582635e13bd3f6b2009889d105" + "sig": "117485aa23543d0f3f6889adb8ed04262700e16edcab1fc70bc7fde3c9bc2c7dd58712375b5a52716d3ae0c7d626263a8e875dd8b767a3a8d994b887da43e309" }, { "wai_sink_segment": 1, @@ -25216,8 +30398,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "f6afdc2d8b166ec78f92cc091c0a99798295385d6209331f02ec46c5ccd6663f", - "row": 5, + "id": "bd74e55dbd7822aec58d2097175e05c974e5d6f93c9b048c3350f94c346dfaff", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -25228,12 +30410,1994 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "9104cd7a478c5134d5e4659c801e265b70efb264994097d24955336ec010e0c20cec62694e790161b5b85ef08029ce61e273291b45740da778225bd335dfdf01" + "sig": "c173f9db9a7efd77482a1367d4096f6f94ddf08251306b45775e893f2679f1296005289ea126b66285a83c7c02c97ce7839367309b869c9205df3babf64b1308" } ], "hops": [], "expect": "accept", - "identity": "e33e87600f0538eb5ead08d161be7ff4bf2b6145dc4d0aa51bd4dc4335d93605" + "identity": "8f4df2220d026f31915f6964d0d3af327e97378f90deb250d63bc22723fd3d69" + }, + { + "name": "session, an origin's share of another kind of record", + "subject": "session", + "record": { + "wai_sink_session": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "streams": [ + { + "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", + "segments": 3, + "last": "9ca9c2eff1d8b74b8cf7f08758f611900f78387c091eb54e5ce4717378716ed0" + }, + { + "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", + "segments": 2, + "last": "83e0d157261d6a7272d72dbba17a9c319ab7938615a2562f6eebed9222147624" + } + ], + "media_us": 10000000, + "presented_us": 120000000, + "display": { + "method": "estimator-display/1", + "panel_class": "backlit", + "on_mode_mw": 30000, + "reference_nits": 250, + "nits": 200, + "basis": "https://label.example/display/model-x", + "relative_ppm": 300000 + }, + "split": { + "origin": { + "status": "figure", + "reason": null, + "coverage": "Partial", + "source": { + "kind": "other", + "record": "668c00a7d6976901a17f6c9d8ef661fc90ba9daf85e37c203e880c53045f108a", + "hop_joules": 9000000, + "hop_class": "ModelBased", + "share_num": 1, + "share_den": 3 + }, + "joules_micro": 3000000, + "energy_provenance": "Estimator", + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + "relay": { + "status": "figure", + "reason": null, + "coverage": "Partial", + "source": { + "kind": "live-metering-hop", + "record": "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f", + "hop_joules": 50000000, + "hop_class": "OnChipCounter", + "share_num": 120000000, + "share_den": 3600000000 + }, + "joules_micro": 1666667, + "energy_provenance": "Estimator", + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + "sink_decode": { + "status": "figure", + "reason": null, + "coverage": "Partial", + "source": null, + "joules_micro": 2338254, + "energy_provenance": "Estimator", + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + "display": { + "status": "figure", + "reason": null, + "coverage": "Whole", + "source": null, + "joules_micro": 2880000000, + "energy_provenance": "Estimator", + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } + }, + "sig": "37fc46d8dd4daf63c314f23940994e766d88c593f5beb9bda3b25dfc3b95e7d4976471753c8596b176fbd008938fafbf8e82717c0784e2b9efd9faba68996f0d" + }, + "readings": null, + "tables": [ + { + "wai_sink_table": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "procedure": "wai-sink-table/1", + "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", + "machine": { + "os": "corpus", + "arch": "corpus", + "processor": "synthetic", + "cores": 16, + "memory_gib": 64 + }, + "build": { + "profile": "release", + "toolchain": null, + "target": null + }, + "measured": "2026-10-22", + "rounds": 7, + "sample_us": 200000, + "counters": [ + { + "profile": "macos-gpu", + "status": "frozen", + "detail": "Energy Model/GPU Energy" + }, + { + "profile": "macos-task", + "status": "live", + "detail": "proc_pid_rusage V6" + } + ], + "rows": [ + { + "capability": "wai.audio.flac", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "content-ms", + "units": 10000, + "width": null, + "height": null, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 50 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 53 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 56 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 59 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 62 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 65 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 68 + } + ], + "status": "unmetered", + "reason": "rail_not_live", + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_hardware_path", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2144400, + "sd_uw": 356790, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2652643, + "sd_uw": 391226, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 400 + }, + { + "idle": { + "n": 10, + "mean_uw": 2212000, + "sd_uw": 397991, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2606000, + "sd_uw": 332414, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 403 + }, + { + "idle": { + "n": 10, + "mean_uw": 2066400, + "sd_uw": 307198, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2586286, + "sd_uw": 342938, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 406 + }, + { + "idle": { + "n": 10, + "mean_uw": 1885700, + "sd_uw": 332615, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2577429, + "sd_uw": 398088, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 409 + }, + { + "idle": { + "n": 10, + "mean_uw": 2074500, + "sd_uw": 452434, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2637429, + "sd_uw": 386990, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 412 + }, + { + "idle": { + "n": 10, + "mean_uw": 2040300, + "sd_uw": 384214, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2626143, + "sd_uw": 418958, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 415 + }, + { + "idle": { + "n": 10, + "mean_uw": 1851400, + "sd_uw": 287343, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2489429, + "sd_uw": 369320, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 418 + } + ], + "status": "unmetered", + "reason": "below_attribution_floor", + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 1890800, + "sd_uw": 173618, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3406857, + "sd_uw": 291462, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3000 + }, + { + "idle": { + "n": 10, + "mean_uw": 2003700, + "sd_uw": 258242, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3502000, + "sd_uw": 238914, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3003 + }, + { + "idle": { + "n": 10, + "mean_uw": 2141600, + "sd_uw": 275043, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3416357, + "sd_uw": 204354, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3006 + }, + { + "idle": { + "n": 10, + "mean_uw": 2028200, + "sd_uw": 196893, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3475500, + "sd_uw": 226251, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3009 + }, + { + "idle": { + "n": 10, + "mean_uw": 2040400, + "sd_uw": 214134, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3434357, + "sd_uw": 223672, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3012 + }, + { + "idle": { + "n": 10, + "mean_uw": 2030100, + "sd_uw": 307907, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3404571, + "sd_uw": 219703, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3015 + }, + { + "idle": { + "n": 10, + "mean_uw": 2050600, + "sd_uw": 244479, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3408571, + "sd_uw": 225896, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3018 + } + ], + "status": "relative", + "reason": null, + "median": 4, + "range": [ + 1186, + 1413 + ], + "joules_micro": 1294, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 64810, + "window_us": 2800000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-gpu", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_counter", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 1987200, + "sd_uw": 15719, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5002143, + "sd_uw": 17502, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1000 + }, + { + "idle": { + "n": 10, + "mean_uw": 2002700, + "sd_uw": 19682, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5004857, + "sd_uw": 20184, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1003 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000800, + "sd_uw": 18850, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001143, + "sd_uw": 17629, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1006 + }, + { + "idle": { + "n": 10, + "mean_uw": 2002500, + "sd_uw": 18549, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 4999571, + "sd_uw": 16787, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1009 + }, + { + "idle": { + "n": 10, + "mean_uw": 1997400, + "sd_uw": 20824, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5002429, + "sd_uw": 18559, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1012 + }, + { + "idle": { + "n": 10, + "mean_uw": 1996900, + "sd_uw": 17445, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001000, + "sd_uw": 19415, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1015 + }, + { + "idle": { + "n": 10, + "mean_uw": 2008700, + "sd_uw": 13777, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001214, + "sd_uw": 17215, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1018 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 8199, + 8408 + ], + "joules_micro": 8284, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 7284, + "window_us": 2800000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vvc", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": null, + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } + ], + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" + }, + { + "wai_sink_table": 1, + "signer": "849e713d6bb5b2daf8790b7d6ed3779d08d3a71fa30fe14b230a5539ff54e6a9", + "procedure": "wai-sink-table/1", + "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", + "machine": { + "os": "corpus", + "arch": "corpus", + "processor": "synthetic", + "cores": 16, + "memory_gib": 64 + }, + "build": { + "profile": "release", + "toolchain": null, + "target": null + }, + "measured": "2026-10-22", + "rounds": 7, + "sample_us": 200000, + "counters": [ + { + "profile": "macos-gpu", + "status": "frozen", + "detail": "Energy Model/GPU Energy" + }, + { + "profile": "macos-task", + "status": "live", + "detail": "proc_pid_rusage V6" + } + ], + "rows": [ + { + "capability": "wai.audio.flac", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "content-ms", + "units": 10000, + "width": null, + "height": null, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 50 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 53 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 56 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 59 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 62 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 65 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 68 + } + ], + "status": "unmetered", + "reason": "rail_not_live", + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_hardware_path", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2144400, + "sd_uw": 356790, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2652643, + "sd_uw": 391226, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 400 + }, + { + "idle": { + "n": 10, + "mean_uw": 2212000, + "sd_uw": 397991, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2606000, + "sd_uw": 332414, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 403 + }, + { + "idle": { + "n": 10, + "mean_uw": 2066400, + "sd_uw": 307198, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2586286, + "sd_uw": 342938, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 406 + }, + { + "idle": { + "n": 10, + "mean_uw": 1885700, + "sd_uw": 332615, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2577429, + "sd_uw": 398088, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 409 + }, + { + "idle": { + "n": 10, + "mean_uw": 2074500, + "sd_uw": 452434, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2637429, + "sd_uw": 386990, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 412 + }, + { + "idle": { + "n": 10, + "mean_uw": 2040300, + "sd_uw": 384214, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2626143, + "sd_uw": 418958, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 415 + }, + { + "idle": { + "n": 10, + "mean_uw": 1851400, + "sd_uw": 287343, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2489429, + "sd_uw": 369320, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 418 + } + ], + "status": "unmetered", + "reason": "below_attribution_floor", + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 1890800, + "sd_uw": 173618, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3406857, + "sd_uw": 291462, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3000 + }, + { + "idle": { + "n": 10, + "mean_uw": 2003700, + "sd_uw": 258242, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3502000, + "sd_uw": 238914, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3003 + }, + { + "idle": { + "n": 10, + "mean_uw": 2141600, + "sd_uw": 275043, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3416357, + "sd_uw": 204354, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3006 + }, + { + "idle": { + "n": 10, + "mean_uw": 2028200, + "sd_uw": 196893, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3475500, + "sd_uw": 226251, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3009 + }, + { + "idle": { + "n": 10, + "mean_uw": 2040400, + "sd_uw": 214134, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3434357, + "sd_uw": 223672, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3012 + }, + { + "idle": { + "n": 10, + "mean_uw": 2030100, + "sd_uw": 307907, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3404571, + "sd_uw": 219703, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3015 + }, + { + "idle": { + "n": 10, + "mean_uw": 2050600, + "sd_uw": 244479, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3408571, + "sd_uw": 225896, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3018 + } + ], + "status": "relative", + "reason": null, + "median": 4, + "range": [ + 1186, + 1413 + ], + "joules_micro": 1294, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 64810, + "window_us": 2800000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-gpu", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_counter", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 1987200, + "sd_uw": 15719, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5002143, + "sd_uw": 17502, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1000 + }, + { + "idle": { + "n": 10, + "mean_uw": 2002700, + "sd_uw": 19682, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5004857, + "sd_uw": 20184, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1003 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000800, + "sd_uw": 18850, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001143, + "sd_uw": 17629, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1006 + }, + { + "idle": { + "n": 10, + "mean_uw": 2002500, + "sd_uw": 18549, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 4999571, + "sd_uw": 16787, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1009 + }, + { + "idle": { + "n": 10, + "mean_uw": 1997400, + "sd_uw": 20824, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5002429, + "sd_uw": 18559, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1012 + }, + { + "idle": { + "n": 10, + "mean_uw": 1996900, + "sd_uw": 17445, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001000, + "sd_uw": 19415, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1015 + }, + { + "idle": { + "n": 10, + "mean_uw": 2008700, + "sd_uw": 13777, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001214, + "sd_uw": 17215, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1018 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 8199, + 8408 + ], + "joules_micro": 8284, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 7284, + "window_us": 2800000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vvc", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": null, + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } + ], + "sig": "80d62d05c16342e453409ec2b4cd034c8863029734bf8f1e3fcbdf2c1a969aba26b518f1e6239e427d810d75f612972f78982cb150f45e13ba1bbeb3f825d508" + } + ], + "segments": [ + { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", + "segment": 0, + "prev": null, + "object": "904fcef85345a6ab4cc7388f8a9d6a52700713dd5f0afab36deb9b859b0d5bf2", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "measured", + "profile": "macos-task", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 40000, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 10, + "mean_uw": 2900000, + "sd_uw": 60000, + "live_intervals": 10, + "longest_live_run": 10 + }, + "window_us": 2000000, + "sample_us": 200000 + }, + "table": null, + "estimator": null, + "reason": null, + "joules_micro": 1800000, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 25338, + "window_us": 2000000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null, + "sig": "3b05fcb31aaea9e0574691dd6ccaab9cf4754581b14f6d89e14f417d92217f294bf175ba14189ddcdfa003e400b8f4e9252cefc0478cd4ad178c5c4cec84de0e" + }, + { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", + "segment": 1, + "prev": "2299cf3c7fb5fc13ac56cad34b0f656487dbfdde2f30ad1e9f6ed7a754eb238a", + "object": "2ec96fd7e1b71588a9b60b1cdf1dc4bc7aea8f2ab33eb3cffd9d987b1ef87698", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "table", + "profile": "macos-task", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": null, + "table": { + "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", + "row": 6, + "per_completion_uj": 8284, + "units_per_completion": 60 + }, + "estimator": null, + "reason": null, + "joules_micro": 6627, + "energy_provenance": "ModelBased", + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null, + "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" + }, + { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", + "segment": 2, + "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", + "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "unmetered", + "profile": null, + "fabrics": [], + "coverage": null, + "bracket": null, + "table": null, + "estimator": null, + "reason": "not_reported", + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null, + "sig": "117485aa23543d0f3f6889adb8ed04262700e16edcab1fc70bc7fde3c9bc2c7dd58712375b5a52716d3ae0c7d626263a8e875dd8b767a3a8d994b887da43e309" + }, + { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", + "segment": 0, + "prev": null, + "object": "46a5ce125bbfa822b69b37a2d86527fba30be66ae6d4d464e36a14a9a3c0190d", + "capability": "wai.audio.opus", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "content-ms", + "units": 2000, + "width": null, + "height": null, + "media_us": 2000000, + "method": "estimator", + "profile": "estimator-cpu-time/1", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": null, + "table": null, + "estimator": { + "method": "estimator-cpu-time/1", + "cpu_us": 150000, + "active_mw": 3500, + "basis": "https://power.example/device-class/phone-a", + "relative_ppm": 400000 + }, + "reason": null, + "joules_micro": 525000, + "energy_provenance": "Estimator", + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null, + "sig": "c72bf30af2dfe67c3fb20d934f48e4b3e34fab7fe89ff796a952f41f7efde4dde8fa9f6a90737edbb56d42cfbe76b5f93dcc8aaa42585ebad97db30ac270b702" + }, + { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", + "segment": 1, + "prev": "350fa7be2744dc21b7cc5e4cf37fb9462a311266fb3c015a4ab00e454e8280a4", + "object": "ba2aed82898351ea19b5244df36506e731296164a2a7b7bcc52c60128a144071", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "table", + "profile": "macos-task", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": null, + "table": { + "id": "bd74e55dbd7822aec58d2097175e05c974e5d6f93c9b048c3350f94c346dfaff", + "row": 6, + "per_completion_uj": 8284, + "units_per_completion": 60 + }, + "estimator": null, + "reason": null, + "joules_micro": 6627, + "energy_provenance": "Estimator", + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null, + "sig": "c173f9db9a7efd77482a1367d4096f6f94ddf08251306b45775e893f2679f1296005289ea126b66285a83c7c02c97ce7839367309b869c9205df3babf64b1308" + } + ], + "hops": [ + "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f" + ], + "expect": "accept", + "identity": "ae7a442e00d3202b0703152660984f16a32164d119b5a22787cd85af6c8083d6" + }, + { + "name": "session, decode read from a calibrated instrument", + "subject": "session", + "record": { + "wai_sink_session": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "7f460c5a79ebd2a029d1ccfeeb68347624ac84361f307d40cdd6846cf63d6794", + "streams": [ + { + "stream": "2c2bb3d85938f7fce4e94723828b46992e7f9ded50c6a8127621820b8edfba33", + "segments": 2, + "last": "0391a86fb0cecd4a62c6e870f1f35360ac2ddb6f3fc0e67de5669e683cd6c4be" + } + ], + "media_us": 4000000, + "presented_us": 5000000, + "display": null, + "split": { + "origin": { + "status": "absent", + "reason": "not_reported", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + "relay": { + "status": "absent", + "reason": "not_reported", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + "sink_decode": { + "status": "figure", + "reason": null, + "coverage": "Partial", + "source": null, + "joules_micro": 6200000, + "energy_provenance": "CalibratedInstrument", + "energy_uncertainty": { + "relative_ppm": 38391, + "window_us": 3000000 + }, + "energy_counter_filtering": null, + "energy_calibration_ref": "https://calibration.example/certificate/7" + }, + "display": { + "status": "absent", + "reason": "display_undeclared", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } + }, + "sig": "5da8db3082051bdb0f489d0aff38f6476c5e798ded30622b125cbb8d4b13d058c21a0107ff063ea46817784e55466a1bdd4371c06a6f98bb19c7f4a8f2b4b10f" + }, + "readings": null, + "tables": [], + "segments": [ + { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "7f460c5a79ebd2a029d1ccfeeb68347624ac84361f307d40cdd6846cf63d6794", + "stream": "2c2bb3d85938f7fce4e94723828b46992e7f9ded50c6a8127621820b8edfba33", + "segment": 0, + "prev": null, + "object": "7e573147e6cd74a681dde835d1d840f1254ef6356bde89bcedf2854e8cdec96d", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "measured", + "profile": "macos-task", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 40000, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 10, + "mean_uw": 3000000, + "sd_uw": 60000, + "live_intervals": 10, + "longest_live_run": 10 + }, + "window_us": 2000000, + "sample_us": 200000 + }, + "table": null, + "estimator": null, + "reason": null, + "joules_micro": 2000000, + "energy_provenance": "CalibratedInstrument", + "energy_uncertainty": { + "relative_ppm": 42804, + "window_us": 2000000 + }, + "energy_counter_filtering": null, + "energy_calibration_ref": "https://calibration.example/certificate/7", + "sig": "325d871eb9f1bb195cb8ab485c196c783d3c955d17d0cefeb9174e7a0499c81ac200d65c0f1c030ac0a6cc9b711d3de5d82d510fcac905d5663cdf7efd7c7c0e" + }, + { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "7f460c5a79ebd2a029d1ccfeeb68347624ac84361f307d40cdd6846cf63d6794", + "stream": "2c2bb3d85938f7fce4e94723828b46992e7f9ded50c6a8127621820b8edfba33", + "segment": 1, + "prev": "96ce495a408f723e642ef73e6bca179ef0482feb0db8befcf08e6c98ce409df0", + "object": "0d1192fc56ca4212d18ebb9fda62f3b476340fca937e754fcc47c64e2b3587b3", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "measured", + "profile": "macos-task", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 40000, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 10, + "mean_uw": 3400000, + "sd_uw": 60000, + "live_intervals": 10, + "longest_live_run": 10 + }, + "window_us": 3000000, + "sample_us": 200000 + }, + "table": null, + "estimator": null, + "reason": null, + "joules_micro": 4200000, + "energy_provenance": "CalibratedInstrument", + "energy_uncertainty": { + "relative_ppm": 36289, + "window_us": 3000000 + }, + "energy_counter_filtering": null, + "energy_calibration_ref": "https://calibration.example/certificate/7", + "sig": "87b79cadfc84c3815a18ccd39cba7559fa106012aaa30ee209138fd770472b183bca75426d3034b21c923beca7f4a81d0d4eaf93890b2b61a8a0065718169901" + } + ], + "hops": [], + "expect": "accept", + "identity": "f6acf6af93977838762e6ccad5ddae985e61bfcb1de81e2ca1f5c8e0fbca1554" }, { "name": "session, an origin absent as unmetered", @@ -25246,12 +32410,12 @@ { "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segments": 3, - "last": "a0723c48caf8cf42fdccc6cd859000a22cec0ffcbdff18acd2dcaa910f5f124f" + "last": "9ca9c2eff1d8b74b8cf7f08758f611900f78387c091eb54e5ce4717378716ed0" }, { "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", "segments": 2, - "last": "600185da5f261f29bccaf76694ed5d1df94dce2cc56d23368fafd7ce930d860c" + "last": "83e0d157261d6a7272d72dbba17a9c319ab7938615a2562f6eebed9222147624" } ], "media_us": 10000000, @@ -25318,7 +32482,7 @@ "energy_calibration_ref": null } }, - "sig": "f99f5c67201f0da1b569fdb43e9f81c10b5107b5c7d62874945c4c187aca87954b920146931cf34ec161d7c373af19246bd753646ed787dc9c83ff3a1e335c0a" + "sig": "6c2be43891d04f1f9d0957834d9c0f0331c939c40a778ad72a116434a5e5f9d492815283f86abbd81c0dc8e2197c6ce0559f8c6bae12c9e9d28907e456d4f90c" }, "readings": null, "tables": [ @@ -25327,7 +32491,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -25683,6 +32847,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -26046,14 +33233,14 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" }, { "wai_sink_table": 1, "signer": "849e713d6bb5b2daf8790b7d6ed3779d08d3a71fa30fe14b230a5539ff54e6a9", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -26409,6 +33596,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -26772,7 +33982,7 @@ "energy_calibration_ref": null } ], - "sig": "7a01ed90093ae27c33717df9f0dc41f7b5e76df9cf2b665763d938f10cd56847f94fdea03808d4b06474d7e743aae78c17d630fad9ac364b4ca9778c46ca2809" + "sig": "80d62d05c16342e453409ec2b4cd034c8863029734bf8f1e3fcbdf2c1a969aba26b518f1e6239e427d810d75f612972f78982cb150f45e13ba1bbeb3f825d508" } ], "segments": [ @@ -26853,8 +34063,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "8a4050461b7c303aeb9eaee0b02a32c543737b306dfa563eaa62f420f42995c4", - "row": 5, + "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -26865,7 +34075,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "b6b9e97c26097344c26236620635dfdb8969a1fcc29c5035be01f4e674c860b9ec9835bedaf6519c7aaef9139da3ec1508f7ddc82c3e7200a429d4bb2c736e0c" + "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" }, { "wai_sink_segment": 1, @@ -26873,7 +34083,7 @@ "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segment": 2, - "prev": "af4753cdc24d6168da0ca24bc0483a56175fa93970af4d0cd9fbcd2e0cc7a9f9", + "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", "capability": "wai.video.av1", "path": "software", @@ -26896,7 +34106,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "994c3a44fe1b93f79eaf61f564e4ad6bff639d40942c0650624fcb8f2ac04fc81989a447588ea024eaa274793437b2c8874e55582635e13bd3f6b2009889d105" + "sig": "117485aa23543d0f3f6889adb8ed04262700e16edcab1fc70bc7fde3c9bc2c7dd58712375b5a52716d3ae0c7d626263a8e875dd8b767a3a8d994b887da43e309" }, { "wai_sink_segment": 1, @@ -26961,8 +34171,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "f6afdc2d8b166ec78f92cc091c0a99798295385d6209331f02ec46c5ccd6663f", - "row": 5, + "id": "bd74e55dbd7822aec58d2097175e05c974e5d6f93c9b048c3350f94c346dfaff", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -26973,7 +34183,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "9104cd7a478c5134d5e4659c801e265b70efb264994097d24955336ec010e0c20cec62694e790161b5b85ef08029ce61e273291b45740da778225bd335dfdf01" + "sig": "c173f9db9a7efd77482a1367d4096f6f94ddf08251306b45775e893f2679f1296005289ea126b66285a83c7c02c97ce7839367309b869c9205df3babf64b1308" } ], "hops": [ @@ -26993,12 +34203,12 @@ { "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segments": 3, - "last": "a0723c48caf8cf42fdccc6cd859000a22cec0ffcbdff18acd2dcaa910f5f124f" + "last": "9ca9c2eff1d8b74b8cf7f08758f611900f78387c091eb54e5ce4717378716ed0" }, { "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", "segments": 2, - "last": "600185da5f261f29bccaf76694ed5d1df94dce2cc56d23368fafd7ce930d860c" + "last": "83e0d157261d6a7272d72dbba17a9c319ab7938615a2562f6eebed9222147624" } ], "media_us": 10000000, @@ -27065,7 +34275,7 @@ "energy_calibration_ref": null } }, - "sig": "50876119dd867c5c7a11f5aba5d55ff1159bdab8ff359d32a8f2a6ece54db7c252402eaf0ca8b03bbad39b2612fd935825e7383c52df82d8d81c804737b78c0a" + "sig": "ffd90245a5e10a0ef75fd46729641b5b3ab1df59942205e004e1696ee5af6a8ade8ff6d6577bfcf87129ba3287552e90e06f2dba73f4941f190958cbaf535904" }, "readings": null, "tables": [ @@ -27074,7 +34284,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -27430,6 +34640,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -27793,14 +35026,14 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" }, { "wai_sink_table": 1, "signer": "849e713d6bb5b2daf8790b7d6ed3779d08d3a71fa30fe14b230a5539ff54e6a9", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -28156,6 +35389,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -28519,7 +35775,7 @@ "energy_calibration_ref": null } ], - "sig": "7a01ed90093ae27c33717df9f0dc41f7b5e76df9cf2b665763d938f10cd56847f94fdea03808d4b06474d7e743aae78c17d630fad9ac364b4ca9778c46ca2809" + "sig": "80d62d05c16342e453409ec2b4cd034c8863029734bf8f1e3fcbdf2c1a969aba26b518f1e6239e427d810d75f612972f78982cb150f45e13ba1bbeb3f825d508" } ], "segments": [ @@ -28600,8 +35856,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "8a4050461b7c303aeb9eaee0b02a32c543737b306dfa563eaa62f420f42995c4", - "row": 5, + "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -28612,7 +35868,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "b6b9e97c26097344c26236620635dfdb8969a1fcc29c5035be01f4e674c860b9ec9835bedaf6519c7aaef9139da3ec1508f7ddc82c3e7200a429d4bb2c736e0c" + "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" }, { "wai_sink_segment": 1, @@ -28620,7 +35876,7 @@ "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segment": 2, - "prev": "af4753cdc24d6168da0ca24bc0483a56175fa93970af4d0cd9fbcd2e0cc7a9f9", + "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", "capability": "wai.video.av1", "path": "software", @@ -28643,7 +35899,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "994c3a44fe1b93f79eaf61f564e4ad6bff639d40942c0650624fcb8f2ac04fc81989a447588ea024eaa274793437b2c8874e55582635e13bd3f6b2009889d105" + "sig": "117485aa23543d0f3f6889adb8ed04262700e16edcab1fc70bc7fde3c9bc2c7dd58712375b5a52716d3ae0c7d626263a8e875dd8b767a3a8d994b887da43e309" }, { "wai_sink_segment": 1, @@ -28708,8 +35964,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "f6afdc2d8b166ec78f92cc091c0a99798295385d6209331f02ec46c5ccd6663f", - "row": 5, + "id": "bd74e55dbd7822aec58d2097175e05c974e5d6f93c9b048c3350f94c346dfaff", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -28720,7 +35976,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "9104cd7a478c5134d5e4659c801e265b70efb264994097d24955336ec010e0c20cec62694e790161b5b85ef08029ce61e273291b45740da778225bd335dfdf01" + "sig": "c173f9db9a7efd77482a1367d4096f6f94ddf08251306b45775e893f2679f1296005289ea126b66285a83c7c02c97ce7839367309b869c9205df3babf64b1308" } ], "hops": [ @@ -28740,12 +35996,12 @@ { "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segments": 3, - "last": "a0723c48caf8cf42fdccc6cd859000a22cec0ffcbdff18acd2dcaa910f5f124f" + "last": "9ca9c2eff1d8b74b8cf7f08758f611900f78387c091eb54e5ce4717378716ed0" }, { "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", "segments": 2, - "last": "600185da5f261f29bccaf76694ed5d1df94dce2cc56d23368fafd7ce930d860c" + "last": "83e0d157261d6a7272d72dbba17a9c319ab7938615a2562f6eebed9222147624" } ], "media_us": 10000000, @@ -28812,7 +36068,7 @@ "energy_calibration_ref": null } }, - "sig": "7b15e741084e06b46fe2ee1ec116fc3b94efe2a1bc709b089fbd5ee31a2b0b7413e6e84cfef434cd0474ae967eb676bfe4c70d10e1269ab711d341bcf3c8800b" + "sig": "ebb8b1f2a19c2926260c1ef3554a98d40169f2dc53937d17b862802eb26516174940035b37f1b9b2921320fccabbb89061211080978e63bb1e5dbd028e7e540b" }, "readings": null, "tables": [ @@ -28821,7 +36077,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -29177,6 +36433,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -29540,14 +36819,14 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" }, { "wai_sink_table": 1, "signer": "849e713d6bb5b2daf8790b7d6ed3779d08d3a71fa30fe14b230a5539ff54e6a9", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -29903,6 +37182,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -30266,7 +37568,7 @@ "energy_calibration_ref": null } ], - "sig": "7a01ed90093ae27c33717df9f0dc41f7b5e76df9cf2b665763d938f10cd56847f94fdea03808d4b06474d7e743aae78c17d630fad9ac364b4ca9778c46ca2809" + "sig": "80d62d05c16342e453409ec2b4cd034c8863029734bf8f1e3fcbdf2c1a969aba26b518f1e6239e427d810d75f612972f78982cb150f45e13ba1bbeb3f825d508" } ], "segments": [ @@ -30347,8 +37649,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "8a4050461b7c303aeb9eaee0b02a32c543737b306dfa563eaa62f420f42995c4", - "row": 5, + "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -30359,7 +37661,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "b6b9e97c26097344c26236620635dfdb8969a1fcc29c5035be01f4e674c860b9ec9835bedaf6519c7aaef9139da3ec1508f7ddc82c3e7200a429d4bb2c736e0c" + "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" }, { "wai_sink_segment": 1, @@ -30367,7 +37669,7 @@ "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segment": 2, - "prev": "af4753cdc24d6168da0ca24bc0483a56175fa93970af4d0cd9fbcd2e0cc7a9f9", + "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", "capability": "wai.video.av1", "path": "software", @@ -30390,7 +37692,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "994c3a44fe1b93f79eaf61f564e4ad6bff639d40942c0650624fcb8f2ac04fc81989a447588ea024eaa274793437b2c8874e55582635e13bd3f6b2009889d105" + "sig": "117485aa23543d0f3f6889adb8ed04262700e16edcab1fc70bc7fde3c9bc2c7dd58712375b5a52716d3ae0c7d626263a8e875dd8b767a3a8d994b887da43e309" }, { "wai_sink_segment": 1, @@ -30455,8 +37757,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "f6afdc2d8b166ec78f92cc091c0a99798295385d6209331f02ec46c5ccd6663f", - "row": 5, + "id": "bd74e55dbd7822aec58d2097175e05c974e5d6f93c9b048c3350f94c346dfaff", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -30467,7 +37769,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "9104cd7a478c5134d5e4659c801e265b70efb264994097d24955336ec010e0c20cec62694e790161b5b85ef08029ce61e273291b45740da778225bd335dfdf01" + "sig": "c173f9db9a7efd77482a1367d4096f6f94ddf08251306b45775e893f2679f1296005289ea126b66285a83c7c02c97ce7839367309b869c9205df3babf64b1308" } ], "hops": [ @@ -30487,12 +37789,12 @@ { "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segments": 3, - "last": "a0723c48caf8cf42fdccc6cd859000a22cec0ffcbdff18acd2dcaa910f5f124f" + "last": "9ca9c2eff1d8b74b8cf7f08758f611900f78387c091eb54e5ce4717378716ed0" }, { "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", "segments": 2, - "last": "600185da5f261f29bccaf76694ed5d1df94dce2cc56d23368fafd7ce930d860c" + "last": "83e0d157261d6a7272d72dbba17a9c319ab7938615a2562f6eebed9222147624" } ], "media_us": 10000000, @@ -30559,7 +37861,7 @@ "energy_calibration_ref": null } }, - "sig": "3070bdcadc6e8a7c3ecbfc11d78c2470f4556127df1ea969daa68064f97921566f20de28457abfa94f00d6b0659d29665017e6796f6c2e7b1ac61d3735546903" + "sig": "747f2f517732cccb55ae9cffaafae9eeeba26808ec0b1bd5569a3b7c9c30b7ab3b2ef269435df3a1192eb48e5574746861eb44cc15cd1bd1bb53857dc414b006" }, "readings": null, "tables": [ @@ -30568,7 +37870,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -30924,6 +38226,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -31287,14 +38612,14 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" }, { "wai_sink_table": 1, "signer": "849e713d6bb5b2daf8790b7d6ed3779d08d3a71fa30fe14b230a5539ff54e6a9", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -31650,6 +38975,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -32013,7 +39361,7 @@ "energy_calibration_ref": null } ], - "sig": "7a01ed90093ae27c33717df9f0dc41f7b5e76df9cf2b665763d938f10cd56847f94fdea03808d4b06474d7e743aae78c17d630fad9ac364b4ca9778c46ca2809" + "sig": "80d62d05c16342e453409ec2b4cd034c8863029734bf8f1e3fcbdf2c1a969aba26b518f1e6239e427d810d75f612972f78982cb150f45e13ba1bbeb3f825d508" } ], "segments": [ @@ -32094,8 +39442,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "8a4050461b7c303aeb9eaee0b02a32c543737b306dfa563eaa62f420f42995c4", - "row": 5, + "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -32106,7 +39454,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "b6b9e97c26097344c26236620635dfdb8969a1fcc29c5035be01f4e674c860b9ec9835bedaf6519c7aaef9139da3ec1508f7ddc82c3e7200a429d4bb2c736e0c" + "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" }, { "wai_sink_segment": 1, @@ -32114,7 +39462,7 @@ "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segment": 2, - "prev": "af4753cdc24d6168da0ca24bc0483a56175fa93970af4d0cd9fbcd2e0cc7a9f9", + "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", "capability": "wai.video.av1", "path": "software", @@ -32137,7 +39485,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "994c3a44fe1b93f79eaf61f564e4ad6bff639d40942c0650624fcb8f2ac04fc81989a447588ea024eaa274793437b2c8874e55582635e13bd3f6b2009889d105" + "sig": "117485aa23543d0f3f6889adb8ed04262700e16edcab1fc70bc7fde3c9bc2c7dd58712375b5a52716d3ae0c7d626263a8e875dd8b767a3a8d994b887da43e309" }, { "wai_sink_segment": 1, @@ -32202,8 +39550,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "f6afdc2d8b166ec78f92cc091c0a99798295385d6209331f02ec46c5ccd6663f", - "row": 5, + "id": "bd74e55dbd7822aec58d2097175e05c974e5d6f93c9b048c3350f94c346dfaff", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -32214,7 +39562,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "9104cd7a478c5134d5e4659c801e265b70efb264994097d24955336ec010e0c20cec62694e790161b5b85ef08029ce61e273291b45740da778225bd335dfdf01" + "sig": "c173f9db9a7efd77482a1367d4096f6f94ddf08251306b45775e893f2679f1296005289ea126b66285a83c7c02c97ce7839367309b869c9205df3babf64b1308" } ], "hops": [ @@ -32234,12 +39582,12 @@ { "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segments": 3, - "last": "a0723c48caf8cf42fdccc6cd859000a22cec0ffcbdff18acd2dcaa910f5f124f" + "last": "9ca9c2eff1d8b74b8cf7f08758f611900f78387c091eb54e5ce4717378716ed0" }, { "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", "segments": 2, - "last": "600185da5f261f29bccaf76694ed5d1df94dce2cc56d23368fafd7ce930d860c" + "last": "83e0d157261d6a7272d72dbba17a9c319ab7938615a2562f6eebed9222147624" } ], "media_us": 10000000, @@ -32306,7 +39654,7 @@ "energy_calibration_ref": null } }, - "sig": "37fef6f149e183a250d3ce291f5a848728ba1d74a19b1eef32fab29057fbc9c53dd10c2bb2f30cad44182256482a1f113d4836e6da0231a6ee50f73eb8b0d205" + "sig": "e4023904722a8592403b015d9173879893abc5ab602ff07dd2fddafe5b1307c865bbb066bf450493cab198fca0a6ad7f5eaa695d5c7c49b2e307451ccbae3303" }, "readings": null, "tables": [ @@ -32315,7 +39663,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -32671,6 +40019,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -33034,14 +40405,14 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" }, { "wai_sink_table": 1, "signer": "849e713d6bb5b2daf8790b7d6ed3779d08d3a71fa30fe14b230a5539ff54e6a9", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -33397,6 +40768,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -33760,7 +41154,7 @@ "energy_calibration_ref": null } ], - "sig": "7a01ed90093ae27c33717df9f0dc41f7b5e76df9cf2b665763d938f10cd56847f94fdea03808d4b06474d7e743aae78c17d630fad9ac364b4ca9778c46ca2809" + "sig": "80d62d05c16342e453409ec2b4cd034c8863029734bf8f1e3fcbdf2c1a969aba26b518f1e6239e427d810d75f612972f78982cb150f45e13ba1bbeb3f825d508" } ], "segments": [ @@ -33841,8 +41235,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "8a4050461b7c303aeb9eaee0b02a32c543737b306dfa563eaa62f420f42995c4", - "row": 5, + "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -33853,7 +41247,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "b6b9e97c26097344c26236620635dfdb8969a1fcc29c5035be01f4e674c860b9ec9835bedaf6519c7aaef9139da3ec1508f7ddc82c3e7200a429d4bb2c736e0c" + "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" }, { "wai_sink_segment": 1, @@ -33861,7 +41255,7 @@ "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segment": 2, - "prev": "af4753cdc24d6168da0ca24bc0483a56175fa93970af4d0cd9fbcd2e0cc7a9f9", + "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", "capability": "wai.video.av1", "path": "software", @@ -33884,7 +41278,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "994c3a44fe1b93f79eaf61f564e4ad6bff639d40942c0650624fcb8f2ac04fc81989a447588ea024eaa274793437b2c8874e55582635e13bd3f6b2009889d105" + "sig": "117485aa23543d0f3f6889adb8ed04262700e16edcab1fc70bc7fde3c9bc2c7dd58712375b5a52716d3ae0c7d626263a8e875dd8b767a3a8d994b887da43e309" }, { "wai_sink_segment": 1, @@ -33949,8 +41343,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "f6afdc2d8b166ec78f92cc091c0a99798295385d6209331f02ec46c5ccd6663f", - "row": 5, + "id": "bd74e55dbd7822aec58d2097175e05c974e5d6f93c9b048c3350f94c346dfaff", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -33961,7 +41355,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "9104cd7a478c5134d5e4659c801e265b70efb264994097d24955336ec010e0c20cec62694e790161b5b85ef08029ce61e273291b45740da778225bd335dfdf01" + "sig": "c173f9db9a7efd77482a1367d4096f6f94ddf08251306b45775e893f2679f1296005289ea126b66285a83c7c02c97ce7839367309b869c9205df3babf64b1308" } ], "hops": [ @@ -33981,12 +41375,12 @@ { "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segments": 3, - "last": "a0723c48caf8cf42fdccc6cd859000a22cec0ffcbdff18acd2dcaa910f5f124f" + "last": "9ca9c2eff1d8b74b8cf7f08758f611900f78387c091eb54e5ce4717378716ed0" }, { "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", "segments": 2, - "last": "600185da5f261f29bccaf76694ed5d1df94dce2cc56d23368fafd7ce930d860c" + "last": "83e0d157261d6a7272d72dbba17a9c319ab7938615a2562f6eebed9222147624" } ], "media_us": 10000001, @@ -34053,7 +41447,7 @@ "energy_calibration_ref": null } }, - "sig": "43abe310740b591b62e2431b304443337437bd4b582e9e2dbf546d33d97a3fb2e3d5b93cdf516c806984b279b93dd64ba6738ca0bb57e6881d80a921f4ea530d" + "sig": "f652522dea94b7c9df974393e58e3feed5af7dee7274eacadf6a3ea2517d38c87c0728b8b0acfe0b99b4914685d18beac5b11b3d01fc5acee29545e48c37f201" }, "readings": null, "tables": [ @@ -34062,7 +41456,7 @@ "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -34418,6 +41812,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -34781,14 +42198,14 @@ "energy_calibration_ref": null } ], - "sig": "1c1e010cd57eddee4dd8b5605f3d843e9694f2af520607059189ce12f6f5a2f9734a7df57b7ea47df28371e715211fc5f65b6fad5b03101c22cdf0ce3dbbb909" + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" }, { "wai_sink_table": 1, "signer": "849e713d6bb5b2daf8790b7d6ed3779d08d3a71fa30fe14b230a5539ff54e6a9", "procedure": "wai-sink-table/1", "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "b377f9f2c7ab770f72c2b7ea29972879ddec2a6b92b11053a041e3ed5f99ccb8", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", "machine": { "os": "corpus", "arch": "corpus", @@ -35144,6 +42561,29 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, { "capability": "wai.video.av1", "path": "hardware", @@ -35507,7 +42947,7 @@ "energy_calibration_ref": null } ], - "sig": "7a01ed90093ae27c33717df9f0dc41f7b5e76df9cf2b665763d938f10cd56847f94fdea03808d4b06474d7e743aae78c17d630fad9ac364b4ca9778c46ca2809" + "sig": "80d62d05c16342e453409ec2b4cd034c8863029734bf8f1e3fcbdf2c1a969aba26b518f1e6239e427d810d75f612972f78982cb150f45e13ba1bbeb3f825d508" } ], "segments": [ @@ -35588,8 +43028,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "8a4050461b7c303aeb9eaee0b02a32c543737b306dfa563eaa62f420f42995c4", - "row": 5, + "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -35600,7 +43040,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "b6b9e97c26097344c26236620635dfdb8969a1fcc29c5035be01f4e674c860b9ec9835bedaf6519c7aaef9139da3ec1508f7ddc82c3e7200a429d4bb2c736e0c" + "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" }, { "wai_sink_segment": 1, @@ -35608,7 +43048,7 @@ "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", "segment": 2, - "prev": "af4753cdc24d6168da0ca24bc0483a56175fa93970af4d0cd9fbcd2e0cc7a9f9", + "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", "capability": "wai.video.av1", "path": "software", @@ -35631,7 +43071,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "994c3a44fe1b93f79eaf61f564e4ad6bff639d40942c0650624fcb8f2ac04fc81989a447588ea024eaa274793437b2c8874e55582635e13bd3f6b2009889d105" + "sig": "117485aa23543d0f3f6889adb8ed04262700e16edcab1fc70bc7fde3c9bc2c7dd58712375b5a52716d3ae0c7d626263a8e875dd8b767a3a8d994b887da43e309" }, { "wai_sink_segment": 1, @@ -35696,8 +43136,8 @@ "coverage": "Partial", "bracket": null, "table": { - "id": "f6afdc2d8b166ec78f92cc091c0a99798295385d6209331f02ec46c5ccd6663f", - "row": 5, + "id": "bd74e55dbd7822aec58d2097175e05c974e5d6f93c9b048c3350f94c346dfaff", + "row": 6, "per_completion_uj": 8284, "units_per_completion": 60 }, @@ -35708,7 +43148,7 @@ "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null, - "sig": "9104cd7a478c5134d5e4659c801e265b70efb264994097d24955336ec010e0c20cec62694e790161b5b85ef08029ce61e273291b45740da778225bd335dfdf01" + "sig": "c173f9db9a7efd77482a1367d4096f6f94ddf08251306b45775e893f2679f1296005289ea126b66285a83c7c02c97ce7839367309b869c9205df3babf64b1308" } ], "hops": [ diff --git a/wai/registry/capabilities.json b/wai/registry/capabilities.json index a16da3ba0..fbccbcdc0 100644 --- a/wai/registry/capabilities.json +++ b/wai/registry/capabilities.json @@ -1448,6 +1448,20 @@ "edition": null, "registered": "2026-06-13" }, + { + "capability": "wai.sink.energy", + "kind": "record", + "status": "registered", + "media": null, + "role": null, + "tier": null, + "licence": null, + "replicate": null, + "sets": [], + "spec_section": "§5 Interactive, temporal & computed content", + "edition": null, + "registered": "2026-10-22" + }, { "capability": "wai.splat.4d", "kind": "payload", diff --git a/wai/vocab/v2.ttl b/wai/vocab/v2.ttl index 30762872b..949e9bc3f 100644 --- a/wai/vocab/v2.ttl +++ b/wai/vocab/v2.ttl @@ -1091,6 +1091,13 @@ wai:wai.semantic.perco2 a wai:Capability ; wai:specSection "§5 Neural capabilities" ; wai:registeredOn "2026-06-13"^^xsd:date . +wai:wai.sink.energy a wai:Capability ; + rdfs:label "wai.sink.energy" ; + wai:kind wai:kind-record ; + wai:status wai:status-registered ; + wai:specSection "§5 Interactive, temporal & computed content" ; + wai:registeredOn "2026-10-22"^^xsd:date . + wai:wai.splat.4d a wai:Capability ; rdfs:label "wai.splat.4d" ; wai:kind wai:kind-payload ; diff --git a/wai/wai-rs/src/bin/wai_sink_table.rs b/wai/wai-rs/src/bin/wai_sink_table.rs index ef16f55b3..faa64d84a 100644 --- a/wai/wai-rs/src/bin/wai_sink_table.rs +++ b/wai/wai-rs/src/bin/wai_sink_table.rs @@ -269,7 +269,7 @@ fn codec_cases(cases: &mut Vec, cpu: &[&'static str]) { std::hint::black_box(video::av1_decode(&p).unwrap()); })); let sr = 48_000u32; - let pcm: Vec = (0..sr as usize * 10).map(|i| ((i as f32 * 0.0577).sin() * 0.4 + (i as f32 * 0.0131).sin() * 0.3)).collect(); + let pcm: Vec = (0..sr as usize * 10).map(|i| (i as f32 * 0.0577).sin() * 0.4 + (i as f32 * 0.0131).sin() * 0.3).collect(); let opus = audio::opus_encode(&pcm, sr, 96_000).unwrap(); audio::opus_decode(&opus).unwrap(); let p = opus.clone(); -- 2.54.0 (Apple Git-157) From 761876d3855e55744a67c76f9f2aba0b3da8521d Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 19:56:42 -0400 Subject: [PATCH 057/242] sink-energy: the measured decode tables of the macOS and the 32-core Linux reference sinks Co-Authored-By: Claude Opus 5.5 --- .../tables/linux-x86_64-32-core/procedure.txt | 18 + .../tables/linux-x86_64-32-core/readings.json | 1 + .../tables/linux-x86_64-32-core/table.json | 2529 +++++++++ .../tables/macos-arm64/procedure.txt | 35 + .../tables/macos-arm64/readings.json | 1 + .../sink-energy/tables/macos-arm64/table.json | 5031 +++++++++++++++++ 6 files changed, 7615 insertions(+) create mode 100644 wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/procedure.txt create mode 100644 wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/readings.json create mode 100644 wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/table.json create mode 100644 wai/live-conformance/sink-energy/tables/macos-arm64/procedure.txt create mode 100644 wai/live-conformance/sink-energy/tables/macos-arm64/readings.json create mode 100644 wai/live-conformance/sink-energy/tables/macos-arm64/table.json diff --git a/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/procedure.txt b/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/procedure.txt new file mode 100644 index 000000000..642dd4356 --- /dev/null +++ b/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/procedure.txt @@ -0,0 +1,18 @@ +wai-sink-table/1 (extensions/sink-energy.md §4.4), as this run carried it out. + +command: /home/dcharlot/ena-target/release/wai_sink_table run --out /home/dcharlot/ena-out --rounds 7 +sampling interval: 200000 us; idle bracket 10 intervals; busy bracket 14 intervals; rounds 7; workers 8, released together from a start barrier +order: every case once per round, the order rotated by one each round; 2 s of settling before each idle bracket +build: release rustc 1.99.0 (b940084d7 2026-09-28) x86_64-unknown-linux-gnu +machine: linux 7.0.0-38-generic, x86_64 processor, 32 logical cores; release build; load averages at the end Some(("5.49", "3.89", "3.12")) +survey: + linux-nvml no-counter: libnvidia-ml: no device + linux-rapl live: powercap energy_uj: /sys/class/powercap/intel-rapl:0 +work items (a completion is one decode of the whole item; its output was checked once before the rounds): + wai.video.int_motion software [builtin:wai@0.3.0] 8 frame per completion at 96x96: tests/vectors/int_video/video_real_bitstream.json sha256 8e05b13cf7a3f4910f41feb8f12efc8bb32c44a80dbfdaa2b312c850db042b75 (8 frames 96x96); decodes + wai.neural.int_hyper software [builtin:wai@0.3.0] 1 image per completion at 16x16: tests/vectors/int_hyper tiny conditional codec, seeded random weights (16x16), bitstream sha256 11c4637ce1038584a47d7bbc2d0515d48518292189e42070c6ad029b0947efbb; decodes + wai.neural.int_synth software [builtin:wai@0.3.0] 1 image per completion at 128x128: neural-conformance/vectors/photo latent.bin sha256 dd430da21fb066174f02dde9fd45902f9388327fbfccc489efa530ac0f65a5e0 (128x128); decodes + wai.audio.int_codec software [builtin:wai@0.3.0] 512 content-ms per completion: tests/vectors/int_audio (8192 samples at 16000 Hz), bitstream sha256 4cba3bb4edf1d08a5cb529187dec566d440ed120c2efe1369dcbf4712e2a1840; output hash e5ff66ad1b86117c as committed + wai.world.model software [builtin:wai@0.3.0] 9 frame per completion at 64x64: tests/vectors/int_world (9 frames 64x64); clip hash 2072048504126404994 +note: platform media tool: none on this sink; no hardware decode path is reachable from the procedure +rows absent for no_decoder: the registered payload capabilities this build (neural_int, video_int, audio_int, world_int) does not decode; no_work_item: those it decodes and this procedure has no item for diff --git a/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/readings.json b/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/readings.json new file mode 100644 index 000000000..cc1309470 --- /dev/null +++ b/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/readings.json @@ -0,0 +1 @@ +{"rows":[{"capability":"wai.audio.aac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.ac4","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.avs3","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.flac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.iamf","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.int_codec","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[20373534000,200115],[20477472000,200082],[20358216000,200088],[20405255000,200088],[20474405000,200085],[20368256000,200082],[20313402000,200087],[20080703000,200106],[20265828000,200088],[20060868000,200086],[20215202000,200085],[20372909000,200084],[20212883000,200090],[20244070000,200101]],"completions":5935,"idle":[[1348365000,201134],[1086278000,200827],[850557000,200678],[763236000,200680],[1584421000,200690],[1702030000,200242],[870530000,200717],[483266000,200608],[1221678000,200448],[1152803000,200383]]},{"busy":[[20081160000,200106],[20331179000,200094],[20382735000,200084],[20125393000,200085],[20317736000,200082],[20053651000,200078],[20230582000,200084],[20179376000,200086],[20116452000,200107],[20135907000,200087],[20222190000,200095],[20062927000,200094],[20791100000,200097],[20179484000,200102]],"completions":5872,"idle":[[1305398000,201158],[863038000,201364],[1746538000,201023],[1373632000,201319],[1118869000,200407],[882156000,201042],[564653000,200846],[1797530000,201128],[1479751000,200588],[1085775000,200675]]},{"busy":[[20650894000,200102],[20422238000,200075],[20326448000,200074],[20302127000,200076],[20321535000,200080],[20177454000,200115],[20371490000,200083],[20204018000,200084],[20189004000,200096],[20035356000,200038],[19961584000,200076],[20161204000,200080],[20204323000,200082],[20222328000,200075]],"completions":5911,"idle":[[1221433000,201107],[1048332000,201303],[1282816000,201301],[1142397000,200192],[1276454000,201167],[886703000,201145],[1074041000,201229],[1268596000,201330],[447243000,201116],[1452241000,201152]]},{"busy":[[20819068000,200125],[20446727000,200091],[19830838000,200090],[19963201000,200099],[20283253000,200090],[19771835000,200085],[20188211000,200086],[20258886000,200087],[19978688000,200084],[19982105000,200082],[19813398000,200079],[19975514000,200080],[19713306000,200086],[20106275000,200082]],"completions":5859,"idle":[[1762284000,200885],[863237000,200836],[788930000,200696],[1094838000,200689],[988199000,200218],[1721133000,200235],[1148211000,200683],[1217863000,200680],[924039000,200852],[0,200685]]},{"busy":[[21679757000,200128],[21594372000,200124],[21504702000,200144],[21679848000,200147],[21589307000,200118],[21672234000,200136],[21752431000,200139],[21178394000,200123],[21456074000,200137],[21314358000,200149],[21378289000,200157],[21544311000,200158],[21348185000,200148],[21576124000,200336]],"completions":5816,"idle":[[4958056000,200159],[4670337000,200154],[4765561000,200192],[4374759000,200166],[4558419000,200157],[4781369000,200166],[4635792000,200163],[4666675000,200164],[4692094000,200159],[4507961000,200141]]},{"busy":[[21996390000,200131],[22168638000,200122],[22188855000,200135],[22228297000,200113],[21669122000,200146],[21666436000,200134],[21672448000,200126],[28171730000,203516],[30893758000,201042],[25118986000,200117],[28584124000,200905],[30690490000,200108],[32321037000,205843],[30371186000,200131]],"completions":4885,"idle":[[5054380000,200125],[5337630000,200121],[5132425000,200134],[5335051000,200119],[5077389000,200117],[5073179000,200120],[5327773000,200130],[5154167000,200157],[5399440000,200155],[5252018000,200165]]},{"busy":[[20285893000,200104],[20250585000,200086],[20022753000,200091],[20337434000,200082],[20155864000,200107],[20364318000,200090],[20196527000,200712],[20097196000,200120],[20161265000,200082],[20207101000,200103],[20113477000,200122],[20186090000,200099],[20147349000,200089],[20207284000,200101]],"completions":5913,"idle":[[1012643000,200641],[1095951000,200640],[1379522000,200834],[1291269000,200744],[1066534000,200692],[1224730000,200704],[1067388000,200699],[1149553000,200705],[1161256000,200651],[1309701000,200792]]}]},{"capability":"wai.audio.ivas","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.mpegh","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.opus","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.opus.packet","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.scene","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.score","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.avatar.pose","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.feed.state","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.film.linear","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.film.reel","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.generate.spectral","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.haptic.mihs","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.haptic.signal","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.avif","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jp2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jpeg","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jpegai","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jxl","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.png","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.mesh.draco","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.mesh.glb","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.bmshj2018","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.cmic","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dcvc_fm","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dcvc_rt","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.elic","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.encodec32","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.glc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.int_hyper","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[19480926000,200514],[18994699000,200502],[19107853000,200507],[18975917000,200559],[18945004000,200496],[18868729000,200527],[18969417000,200496],[19127887000,200525],[19058508000,200517],[18841540000,200493],[18963848000,200496],[18866547000,200486],[19193817000,200215],[18905882000,200780]],"completions":336528,"idle":[[922285000,201189],[885254000,201300],[1699344000,201099],[1143145000,200842],[1103794000,200720],[919005000,201068],[985438000,201326],[1739961000,200559],[785238000,201171],[1025536000,201301]]},{"busy":[[19162919000,200111],[18999322000,200069],[18831683000,200066],[18755058000,200064],[18900023000,200068],[18777502000,200067],[19168519000,200068],[19162430000,200070],[19240567000,200068],[19188797000,200067],[19128848000,200065],[19141832000,200065],[18690470000,200081],[18722955000,200080]],"completions":335253,"idle":[[1022377000,200564],[429315000,200676],[1218549000,200686],[1117817000,200217],[1097218000,200674],[828753000,200366],[1125674000,200795],[975917000,200718],[135140000,200681],[2152828000,200803]]},{"busy":[[19212522000,200122],[18779013000,200068],[18684367000,200066],[18773748000,200066],[18954830000,200068],[18918654000,200069],[18744102000,200062],[18717446000,200064],[18762229000,200064],[18882248000,200072],[18670574000,200069],[18686106000,200068],[18718057000,200066],[18793660000,200066]],"completions":334491,"idle":[[1114994000,200678],[859620000,200876],[1081869000,200413],[920896000,200723],[1330315000,200767],[1243420000,200681],[877579000,200681],[858598000,200734],[1051185000,200345],[1910790000,200675]]},{"busy":[[19980855000,200093],[19905876000,200078],[20340211000,200079],[19570994000,200067],[18693644000,200079],[18749458000,200082],[18825046000,200082],[18736869000,200078],[19012018000,200037],[18917707000,200076],[18631529000,200070],[18826953000,200085],[18703409000,200087],[18848039000,200069]],"completions":332432,"idle":[[4489911000,200528],[820254000,200681],[1373190000,200681],[1409351000,201026],[3478275000,200151],[3557662000,200121],[3337001000,200125],[3036662000,200072],[3130316000,200165],[3510469000,200104]]},{"busy":[[20175699000,200150],[20486108000,200105],[21106971000,200077],[21252762000,200077],[21217057000,200075],[21175007000,200069],[21162892000,200108],[21222077000,200104],[21311185000,200093],[21266859000,200102],[21533311000,200116],[21717062000,200135],[21217042000,200126],[20975234000,200115]],"completions":331496,"idle":[[4724151000,200188],[4788617000,200196],[4962756000,200160],[4638142000,200153],[4678728000,200155],[4655903000,200149],[4493786000,200191],[5015198000,200149],[4786221000,200170],[4722137000,200175]]},{"busy":[[19249661000,200138],[19126956000,200075],[18929151000,200069],[19055457000,200070],[18985926000,200071],[19155442000,200076],[20779519000,200140],[21247360000,200090],[21325726000,200083],[21243516000,200077],[21280866000,200088],[21335780000,200072],[21321438000,200078],[21422735000,200075]],"completions":332948,"idle":[[1302240000,200710],[869492000,200800],[1277339000,200691],[1560573000,200697],[986979000,200679],[782247000,200691],[1292612000,200276],[1153047000,200719],[1439547000,200806],[916227000,200430]]},{"busy":[[19359839000,200131],[18705499000,200083],[18637571000,200067],[18654537000,200068],[18753212000,200073],[19028038000,200067],[18950848000,200066],[18935498000,200065],[18938001000,200063],[18949215000,200069],[19063483000,200069],[18931790000,200065],[18931196000,200066],[18945950000,200066]],"completions":334142,"idle":[[954068000,200496],[906935000,200343],[743431000,200693],[1380162000,200731],[1466828000,200256],[881699000,200693],[1075445000,200687],[930784000,200761],[329145000,200677],[2084731000,200244]]}]},{"capability":"wai.neural.int_mlicpp","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[]},{"capability":"wai.neural.int_synth","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[25628924000,200128],[26936946000,200124],[26336178000,200133],[26951273000,200368],[27304100000,200131],[26321774000,200133],[26754125000,200256],[27418351000,200128],[27040609000,200138],[27293419000,200130],[26801715000,200127],[26888303000,200129],[26990395000,200125],[26745993000,200126]],"completions":762,"idle":[[1029701000,201366],[795415000,201113],[1246411000,201371],[1160630000,200201],[1292383000,201353],[855287000,201159],[732430000,201141],[1049476000,201131],[1207671000,201348],[1603722000,201273]]},{"busy":[[25662277000,200141],[27247493000,200170],[27089556000,200143],[26799747000,200137],[27027456000,200156],[26786533000,200132],[27069401000,200128],[26140143000,200149],[26585509000,200121],[26911068000,200128],[25908145000,200148],[26784397000,200143],[26733221000,200126],[26845109000,200121]],"completions":765,"idle":[[1420550000,200675],[934141000,200214],[1407428000,200686],[1191192000,200870],[953595000,200913],[1376271000,200262],[1032784000,200772],[1279673000,200725],[882034000,200705],[872041000,200683]]},{"busy":[[25495035000,200129],[27467162000,200142],[27321051000,200146],[26913983000,200147],[27087542000,200181],[27106692000,200132],[26947810000,200122],[26834839000,200239],[26919568000,200178],[27248682000,200137],[26733511000,200125],[27150162000,200121],[27012992000,200138],[27275491000,200266]],"completions":772,"idle":[[862458000,200777],[1363623000,200649],[1350241000,200649],[938321000,200878],[955914000,200719],[1231870000,200440],[1323220000,200485],[1366109000,200882],[603790000,200696],[1440950000,200896]]},{"busy":[[24383565000,200133],[26278396000,200139],[26915020000,200146],[26231493000,200135],[26955988000,200135],[26928310000,200217],[26690042000,200264],[27092745000,200164],[26827394000,200131],[27193296000,200128],[27211880000,200122],[27272317000,200210],[26606992000,200186],[26931240000,200124]],"completions":763,"idle":[[446999000,200720],[1427248000,200687],[1167176000,200692],[455162000,200686],[1420794000,200726],[1599389000,200131],[4997850000,200211],[1509809000,200496],[1139559000,200316],[1076117000,200633]]},{"busy":[[26316724000,200306],[27052693000,200129],[26922802000,200256],[27266748000,200223],[26971872000,200126],[27739349000,200135],[27160888000,200160],[27379413000,200302],[27891105000,200137],[27078327000,200146],[26619793000,200137],[27075474000,200138],[27430771000,200648],[27244868000,200131]],"completions":754,"idle":[[4731598000,200160],[5132684000,200152],[4616308000,200176],[4787533000,200194],[4469877000,200180],[4576622000,200180],[4784695000,200153],[4759321000,200152],[4993135000,200148],[4888892000,200164]]},{"busy":[[26252823000,200141],[27835276000,200150],[28458032000,200161],[27782056000,200266],[28127437000,200126],[27989473000,200265],[28232229000,200159],[27375735000,200561],[26587691000,200210],[27232768000,200150],[28406017000,200423],[27881539000,200136],[27464644000,200145],[28334533000,200157]],"completions":759,"idle":[[5335600000,200164],[5137170000,200162],[5094921000,200200],[5186652000,200164],[5148919000,200159],[5449944000,200107],[5307312000,200103],[5354719000,200106],[5206457000,200140],[5167564000,200108]]},{"busy":[[25442212000,200121],[27103487000,200152],[26938472000,200275],[26709419000,200130],[27218517000,200136],[26601103000,200137],[26904477000,200137],[26747915000,200143],[26817186000,200140],[27638539000,200142],[25829643000,200148],[26788272000,200181],[27297539000,200137],[26521730000,200148]],"completions":764,"idle":[[1063803000,200707],[1727297000,200706],[1029366000,200239],[839007000,200409],[782338000,200684],[1193054000,200259],[1533780000,200678],[912215000,200831],[684473000,200321],[1252743000,200448]]}]},{"capability":"wai.neural.mbt2018_mean","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mimi","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mlicpp","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mlicv2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.snac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.video_bmshj2018","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.wavtokenizer","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.pc.gpcc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.pc.vpcc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.prior.nnc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.prior.set","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.quantum.circuit","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.quantum.circuit2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.semantic.clip","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.semantic.perco2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.4d","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.int_codec","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[]},{"capability":"wai.splat.sog","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.spz","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.tensor.nnc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.ternary.tt","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.text.xz","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.text.zstd","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.av1","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.av1.lossless","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.av2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.avc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.avs3","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.evc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.evc.baseline","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.hevc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.int_hyper","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[]},{"capability":"wai.video.int_motion","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[19862834000,200103],[20043717000,200230],[19496459000,200517],[19172241000,200486],[19269634000,200502],[19204573000,200101],[19353034000,200139],[19194564000,200493],[19129137000,200494],[19061392000,200491],[19147646000,200497],[19228910000,200511],[19249065000,200499],[19097386000,200491]],"completions":11605,"idle":[[1455110000,200525],[803181000,201122],[955883000,201113],[936322000,200685],[781790000,201150],[1273677000,200733],[1392368000,200868],[1226973000,200880],[971843000,200972],[660946000,201217]]},{"busy":[[19806745000,200119],[19134860000,200083],[19007837000,200061],[19022591000,200077],[19039313000,200064],[19109425000,200067],[19056143000,200062],[18975734000,200078],[18992212000,200062],[19033242000,200064],[19175537000,200067],[19139025000,200065],[18979792000,200066],[18997812000,200060]],"completions":11545,"idle":[[1717501000,200500],[955929000,200498],[965511000,200705],[987956000,200495],[1114200000,200502],[1459763000,200952],[949201000,200216],[1110950000,200684],[1001901000,201181],[1528684000,200691]]},{"busy":[[19741242000,200112],[19471024000,200066],[19355673000,200066],[19246976000,200072],[19392811000,200074],[19286722000,200065],[19260555000,200066],[19335060000,200069],[19310051000,200066],[19264965000,200067],[19256130000,200084],[19215589000,200076],[20480219000,200086],[18914366000,200084]],"completions":11542,"idle":[[539142000,200715],[1315941000,200686],[1430011000,200903],[831774000,200783],[1091695000,200689],[1223249000,200672],[847445000,200696],[1249309000,200687],[1360236000,200783],[925108000,200499]]},{"busy":[[19490279000,200159],[19537518000,200081],[19321038000,200071],[19267329000,200070],[19300561000,200066],[19243039000,200067],[19227018000,200068],[19264171000,200073],[19406009000,200068],[19363913000,200070],[19263316000,200066],[19376882000,200067],[19470108000,200067],[19409321000,200066]],"completions":11534,"idle":[[1012902000,200731],[1026375000,200758],[943723000,200169],[850557000,200844],[1332588000,200689],[1181991000,200687],[1087148000,200685],[1215910000,200689],[1185623000,200906],[1519163000,200718]]},{"busy":[[29299662000,200139],[28968259000,200133],[29829497000,200085],[28303606000,200100],[25041292000,200113],[27365360000,200082],[26169820000,200082],[25462032000,200088],[24146059000,200087],[22363864000,200100],[22620611000,200108],[22795650000,200105],[24560376000,200080],[24633934000,200088]],"completions":11163,"idle":[[1676015000,200651],[1153673000,200761],[1263484000,200674],[3426718000,200098],[4025213000,200125],[5832523000,200224],[19218122000,200120],[23864672000,200147],[23665448000,200183],[22742034000,200132]]},{"busy":[[21129935000,200126],[21299634000,200083],[21236039000,200074],[21156132000,200068],[21356699000,200099],[21310513000,200100],[21332515000,200077],[21198550000,200068],[21257216000,200074],[21616818000,200115],[21710058000,200091],[21098687000,200066],[21247573000,200065],[21206652000,200082]],"completions":11467,"idle":[[4812495000,200164],[4984102000,200159],[4712495000,200166],[4889182000,200174],[4811854000,200182],[5098735000,200145],[5160149000,200133],[4897055000,200132],[4960605000,200145],[4877036000,200149]]},{"busy":[[19472869000,200072],[19167710000,200069],[18869004000,200066],[18948728000,200063],[18927381000,200082],[18889633000,200064],[19091648000,200067],[19377233000,200075],[20389571000,200080],[18764700000,200066],[18794865000,200063],[18999186000,200066],[18798573000,200064],[18894882000,200083]],"completions":11525,"idle":[[1209395000,200708],[1609337000,200710],[671871000,200475],[1411563000,200367],[1076986000,200882],[749900000,200748],[1528577000,200546],[1246213000,200757],[676768000,200711],[1916253000,200687]]}]},{"capability":"wai.video.j2k","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.vp8","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.vp9","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.vvc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.live","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.model","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[21038249000,200109],[20944717000,200112],[20848699000,200080],[20582524000,200085],[20722973000,200069],[21028011000,200101],[20833593000,200095],[20764277000,200096],[21065164000,200074],[20973861000,200100],[21087883000,200081],[20996198000,200077],[20969115000,200071],[21235078000,200080]],"completions":1318,"idle":[[1542218000,200688],[1120914000,200692],[1673619000,200350],[821506000,200679],[482428000,200725],[1878504000,200628],[920652000,200684],[1408649000,200712],[884354000,200678],[882233000,200661]]},{"busy":[[20883503000,200122],[20894656000,200099],[20816810000,200085],[20696241000,200073],[20920198000,200077],[20577839000,200082],[20676879000,200096],[20663894000,200074],[20838934000,200085],[20970198000,200096],[20920183000,200090],[20993299000,200092],[20976271000,200093],[20842306000,200071]],"completions":1306,"idle":[[936719000,200740],[1040870000,200733],[779425000,200713],[1161606000,200165],[1371939000,200234],[749808000,200658],[1218382000,200646],[962597000,200673],[794682000,200649],[1779876000,200647]]},{"busy":[[20254095000,200083],[20909639000,200075],[20886326000,200077],[20276478000,200075],[20442623000,200074],[20682188000,200073],[20973540000,200085],[20633485000,200075],[20919634000,200083],[20708447000,200072],[20505013000,200076],[20780572000,200074],[20660171000,200084],[20607912000,200077]],"completions":1301,"idle":[[1300378000,200890],[680110000,201223],[1526579000,201306],[947369000,201253],[1075307000,201194],[1393590000,201139],[843706000,200978],[1565318000,200271],[1046882000,200647],[1302606000,200684]]},{"busy":[[21424811000,200130],[21117529000,200083],[20722409000,200082],[20845129000,200076],[20600115000,200078],[20641587000,200084],[20362656000,200066],[20526709000,200101],[20382018000,200093],[20739315000,200078],[20961211000,200084],[21547256000,200100],[20547399000,200087],[20270971000,200107]],"completions":1298,"idle":[[1071279000,201112],[1064551000,200838],[1632667000,200711],[932340000,200732],[947323000,200680],[958432000,200687],[1014855000,200687],[1474884000,200689],[838427000,200682],[0,200687]]},{"busy":[[20895297000,200109],[20969512000,200090],[20553975000,200075],[20934068000,200076],[20773721000,200072],[20458781000,200071],[20552908000,200070],[20678023000,200097],[20659347000,200091],[20588642000,200075],[20714306000,200072],[20660934000,200071],[20473215000,200077],[20581760000,200073]],"completions":1299,"idle":[[2062729000,200600],[1072820000,201205],[405756000,200994],[757316000,201302],[2319063000,201131],[1706668000,201232],[1160753000,201311],[542559000,201284],[925627000,201158],[1425356000,201245]]},{"busy":[[20911898000,200104],[20825461000,200085],[20813209000,200091],[20868946000,200080],[20876698000,200094],[20909014000,200075],[20640778000,200083],[20835424000,200078],[20649293000,200072],[20431026000,201383],[20559926000,200092],[20571339000,200071],[20482218000,200067],[20506813000,200088]],"completions":1299,"idle":[[1077596000,200977],[943326000,200932],[909621000,200373],[1448686000,200771],[1484573000,200649],[1203963000,200687],[1231183000,200641],[1155077000,200185],[1679204000,200662],[597564000,200668]]},{"busy":[[20848775000,200080],[20964019000,200083],[20765146000,200102],[20671539000,200094],[20954162000,200092],[20891589000,200094],[20800530000,200080],[20455561000,200067],[20747127000,200087],[20706357000,200037],[20689833000,200066],[20572941000,200073],[20783731000,200070],[20681746000,200085]],"completions":1311,"idle":[[979106000,200693],[1036323000,200428],[1348609000,200692],[606506000,200818],[1570200000,200498],[891251000,200489],[1014077000,200687],[1195510000,200826],[1154817000,200594],[1280009000,200218]]}]},{"capability":"wai.world.replay","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.scene","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]}],"wai_sink_readings":1} \ No newline at end of file diff --git a/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/table.json b/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/table.json new file mode 100644 index 000000000..25721b19f --- /dev/null +++ b/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/table.json @@ -0,0 +1,2529 @@ +{ + "wai_sink_table": 1, + "signer": "5f8823017ac5f259575e1c43369a132cd44159e64c3a6dcdda69bb6b3b782c96", + "procedure": "wai-sink-table/1", + "procedure_sha256": "5543efbdc3a44ad3cfb5e1a6f23fdc0f66487ca9e4d5b01a5ba89b4a8c3bda1c", + "readings_sha256": "24ed273b1b042c696809062639eb0cecfdb9b00d18f0707450c2ff92e5d820a4", + "machine": { + "os": "linux 7.0.0-38-generic", + "arch": "x86_64", + "processor": "x86_64 processor, 32 logical cores", + "cores": 32, + "memory_gib": 122 + }, + "build": { + "profile": "release", + "toolchain": "rustc 1.99.0 (b940084d7 2026-09-28)", + "target": "x86_64-unknown-linux-gnu" + }, + "measured": "2026-10-22", + "rounds": 7, + "sample_us": 200000, + "counters": [ + { + "profile": "linux-nvml", + "status": "no-counter", + "detail": "libnvidia-ml: no device" + }, + { + "profile": "linux-rapl", + "status": "live", + "detail": "powercap energy_uj: /sys/class/powercap/intel-rapl:0" + } + ], + "rows": [ + { + "capability": "wai.audio.aac", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.ac4", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.avs3", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.flac", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.iamf", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.int_codec", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "content-ms", + "units": 512, + "width": null, + "height": null, + "fabrics": [ + "package" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 5514308, + "sd_uw": 1885172, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 101462312, + "sd_uw": 645969, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801267, + "completions": 5935 + }, + { + "idle": { + "n": 10, + "mean_uw": 6079399, + "sd_uw": 1967228, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 101100129, + "sd_uw": 955385, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801281, + "completions": 5872 + }, + { + "idle": { + "n": 10, + "mean_uw": 5519585, + "sd_uw": 1393623, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 101226765, + "sd_uw": 839652, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801136, + "completions": 5911 + }, + { + "idle": { + "n": 10, + "mean_uw": 5237916, + "sd_uw": 2477984, + "live_intervals": 9, + "longest_live_run": 9 + }, + "busy": { + "n": 14, + "mean_uw": 100359320, + "sd_uw": 1497245, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801246, + "completions": 5859 + }, + { + "idle": { + "n": 10, + "mean_uw": 23286625, + "sd_uw": 800439, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 107513521, + "sd_uw": 824599, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2802144, + "completions": 5816 + }, + { + "idle": { + "n": 10, + "mean_uw": 26054217, + "sd_uw": 650712, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 127820691, + "sd_uw": 20377626, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2812569, + "completions": 4885 + }, + { + "idle": { + "n": 10, + "mean_uw": 5858319, + "sd_uw": 603372, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 100904550, + "sd_uw": 472314, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801988, + "completions": 5913 + } + ], + "status": "figure", + "reason": null, + "median": 1, + "range": [ + 40553, + 58545 + ], + "joules_micro": 45299, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 114657, + "window_us": 2801281 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.ivas", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.mpegh", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.opus", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.opus.packet", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.scene", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.score", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.avatar.pose", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.feed.state", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.film.linear", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.film.reel", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.generate.spectral", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.haptic.mihs", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.haptic.signal", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.avif", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.jp2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.jpeg", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.jpegai", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.jxl", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.mesh.draco", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.mesh.glb", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.bmshj2018", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.cmic", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.dac", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.dcvc_fm", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.dcvc_rt", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.elic", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.encodec32", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.glc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_hyper", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "image", + "units": 1, + "width": 16, + "height": 16, + "fabrics": [ + "package" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 5576187, + "sd_uw": 1658807, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 94866512, + "sd_uw": 857431, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2807113, + "completions": 336528 + }, + { + "idle": { + "n": 10, + "mean_uw": 5035838, + "sd_uw": 2634610, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 94919695, + "sd_uw": 1009752, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801009, + "completions": 335253 + }, + { + "idle": { + "n": 10, + "mean_uw": 5606274, + "sd_uw": 1597893, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 94001564, + "sd_uw": 723204, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800990, + "completions": 334491 + }, + { + "idle": { + "n": 10, + "mean_uw": 14052138, + "sd_uw": 5945798, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 95586085, + "sd_uw": 2849612, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801062, + "completions": 332432 + }, + { + "idle": { + "n": 10, + "mean_uw": 23712849, + "sd_uw": 767096, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 105595035, + "sd_uw": 1938641, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801452, + "completions": 331496 + }, + { + "idle": { + "n": 10, + "mean_uw": 5771363, + "sd_uw": 1293526, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 101549066, + "sd_uw": 5604118, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801202, + "completions": 332948 + }, + { + "idle": { + "n": 10, + "mean_uw": 5362971, + "sd_uw": 2374919, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 94531545, + "sd_uw": 927650, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801018, + "completions": 334142 + } + ], + "status": "figure", + "reason": null, + "median": 0, + "range": [ + 687, + 806 + ], + "joules_micro": 745, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 46111, + "window_us": 2807113 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_synth", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "image", + "units": 1, + "width": 128, + "height": 128, + "fabrics": [ + "package" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 5455096, + "sd_uw": 1306145, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 133971609, + "sd_uw": 2333543, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2802176, + "completions": 762 + }, + { + "idle": { + "n": 10, + "mean_uw": 5656585, + "sd_uw": 1121368, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 133332495, + "sd_uw": 2324338, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801943, + "completions": 765 + }, + { + "idle": { + "n": 10, + "mean_uw": 5698216, + "sd_uw": 1406491, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 134720541, + "sd_uw": 2340209, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2802203, + "completions": 772 + }, + { + "idle": { + "n": 10, + "mean_uw": 7603826, + "sd_uw": 6422845, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 133293067, + "sd_uw": 3658548, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2802234, + "completions": 763 + }, + { + "idle": { + "n": 10, + "mean_uw": 23850591, + "sd_uw": 984244, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 135624127, + "sd_uw": 2021214, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2802974, + "completions": 754 + }, + { + "idle": { + "n": 10, + "mean_uw": 26176193, + "sd_uw": 579893, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 138406443, + "sd_uw": 3331892, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2803050, + "completions": 759 + }, + { + "idle": { + "n": 10, + "mean_uw": 5494125, + "sd_uw": 1657567, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 133669280, + "sd_uw": 2813073, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2802127, + "completions": 764 + } + ], + "status": "figure", + "reason": null, + "median": 1, + "range": [ + 412303, + 470138 + ], + "joules_micro": 465202, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 35889, + "window_us": 2801943 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.mbt2018_mean", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.mimi", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.mlicpp", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.mlicv2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.snac", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.video_bmshj2018", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.wavtokenizer", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.pc.gpcc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.pc.vpcc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.prior.nnc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.prior.set", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.quantum.circuit", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.quantum.circuit2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.semantic.clip", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.semantic.perco2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.splat.4d", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.splat.int_codec", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.splat.sog", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.splat.spz", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.tensor.nnc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.ternary.tt", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.text.xz", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.text.zstd", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1.lossless", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.avc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.avs3", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.evc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.evc.baseline", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.hevc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.int_hyper", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.int_motion", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "frame", + "units": 8, + "width": 96, + "height": 96, + "fabrics": [ + "package" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 5206053, + "sd_uw": 1367502, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 96420380, + "sd_uw": 1500176, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2805554, + "completions": 11605 + }, + { + "idle": { + "n": 10, + "mean_uw": 5876724, + "sd_uw": 1405013, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 95491096, + "sd_uw": 1052863, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800995, + "completions": 11545 + }, + { + "idle": { + "n": 10, + "mean_uw": 5387577, + "sd_uw": 1425772, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 96938996, + "sd_uw": 1787157, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801053, + "completions": 11542 + }, + { + "idle": { + "n": 10, + "mean_uw": 5658379, + "sd_uw": 973746, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 96727857, + "sd_uw": 491223, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801059, + "completions": 11534 + }, + { + "idle": { + "n": 10, + "mean_uw": 53388722, + "sd_uw": 51113657, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 129064224, + "sd_uw": 12747575, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801390, + "completions": 11163 + }, + { + "idle": { + "n": 10, + "mean_uw": 24582848, + "sd_uw": 677840, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 106439462, + "sd_uw": 862506, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801188, + "completions": 11467 + }, + { + "idle": { + "n": 10, + "mean_uw": 6028693, + "sd_uw": 2105055, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 95461548, + "sd_uw": 2138011, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800980, + "completions": 11525 + } + ], + "status": "figure", + "reason": null, + "median": 1, + "range": [ + 18984, + 22210 + ], + "joules_micro": 21734, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 42849, + "window_us": 2800995 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.j2k", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vp8", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vp9", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vvc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.world.live", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.world.model", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "frame", + "units": 9, + "width": 64, + "height": 64, + "fabrics": [ + "package" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 5789285, + "sd_uw": 2214244, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 104629158, + "sd_uw": 845488, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801230, + "completions": 1318 + }, + { + "idle": { + "n": 10, + "mean_uw": 5382701, + "sd_uw": 1593445, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 104122349, + "sd_uw": 653095, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801235, + "completions": 1306 + }, + { + "idle": { + "n": 10, + "mean_uw": 5813918, + "sd_uw": 1476122, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 103260101, + "sd_uw": 1145401, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801083, + "completions": 1301 + }, + { + "idle": { + "n": 10, + "mean_uw": 4948666, + "sd_uw": 2145987, + "live_intervals": 9, + "longest_live_run": 9 + }, + "busy": { + "n": 14, + "mean_uw": 103771197, + "sd_uw": 1916403, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801249, + "completions": 1298 + }, + { + "idle": { + "n": 10, + "mean_uw": 6155246, + "sd_uw": 3166611, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 103349564, + "sd_uw": 813677, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801119, + "completions": 1299 + }, + { + "idle": { + "n": 10, + "mean_uw": 5846235, + "sd_uw": 1572520, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 103439219, + "sd_uw": 961481, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2802463, + "completions": 1299 + }, + { + "idle": { + "n": 10, + "mean_uw": 5522209, + "sd_uw": 1330991, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 103720668, + "sd_uw": 697517, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801110, + "completions": 1311 + } + ], + "status": "figure", + "reason": null, + "median": 0, + "range": [ + 208943, + 212616 + ], + "joules_micro": 209435, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 7647, + "window_us": 2801230 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.world.replay", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.world.scene", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } + ], + "sig": "d5cf3549f288ef3f3e6de7e4829b6d8e4083a34e97dd67fb670e46aa5fba668502c5da505e9b896852ef1438a0fb96680b65c75fade139aecdb0690ec5254b09" +} diff --git a/wai/live-conformance/sink-energy/tables/macos-arm64/procedure.txt b/wai/live-conformance/sink-energy/tables/macos-arm64/procedure.txt new file mode 100644 index 000000000..436eb577b --- /dev/null +++ b/wai/live-conformance/sink-energy/tables/macos-arm64/procedure.txt @@ -0,0 +1,35 @@ +wai-sink-table/1 (extensions/sink-energy.md §4.4), as this run carried it out. + +command: /Volumes/macos_4TB_external_1/wai-build/targets/_wt-energy-sink/release/wai_sink_table run --out ../live-conformance/sink-energy/tables/macos-arm64 --rounds 7 +sampling interval: 200000 us; idle bracket 10 intervals; busy bracket 14 intervals; rounds 7; workers 8, released together from a start barrier +order: every case once per round, the order rotated by one each round; 2 s of settling before each idle bracket +build: release rustc 1.98.0 (88d9e12ae 2026-08-18) aarch64-apple-darwin +machine: macos 27.0.1, aarch64 processor, 32 logical cores (24 Performance + 8 Efficiency); release build; load averages at the end Some(("22.55", "45.16", "67.69")) +survey: + macos-gpu frozen: IOReport Energy Model / GPU Energy + macos-task live: proc_pid_rusage RUSAGE_INFO_V6: ri_energy_nj + ri_billed_energy - ri_serviced_energy, this process and its children +work items (a completion is one decode of the whole item; its output was checked once before the rounds): + wai.video.int_motion software [builtin:wai@0.3.0] 8 frame per completion at 96x96: tests/vectors/int_video/video_real_bitstream.json sha256 8e05b13cf7a3f4910f41feb8f12efc8bb32c44a80dbfdaa2b312c850db042b75 (8 frames 96x96); decodes + wai.neural.int_hyper software [builtin:wai@0.3.0] 1 image per completion at 16x16: tests/vectors/int_hyper tiny conditional codec, seeded random weights (16x16), bitstream sha256 11c4637ce1038584a47d7bbc2d0515d48518292189e42070c6ad029b0947efbb; decodes + wai.neural.int_synth software [builtin:wai@0.3.0] 1 image per completion at 128x128: neural-conformance/vectors/photo latent.bin sha256 dd430da21fb066174f02dde9fd45902f9388327fbfccc489efa530ac0f65a5e0 (128x128); decodes + wai.audio.int_codec software [builtin:wai@0.3.0] 512 content-ms per completion: tests/vectors/int_audio (8192 samples at 16000 Hz), bitstream sha256 4cba3bb4edf1d08a5cb529187dec566d440ed120c2efe1369dcbf4712e2a1840; output hash e5ff66ad1b86117c as committed + wai.world.model software [builtin:wai@0.3.0] 9 frame per completion at 64x64: tests/vectors/int_world (9 frames 64x64); clip hash 2072048504126404994 + wai.image.png software [builtin:wai@0.3.0] 1 image per completion at 1920x1080: a 1920x1080 picture from seed 1, encoded once; decodes to its source, payload sha256 71e54ccf081952abb8951c41b2385f77239a68004c4ce2d71993a55d9e24370b + wai.image.jpeg software [builtin:wai@0.3.0] 1 image per completion at 1920x1080: the same picture at quality 85; decodes, payload sha256 f8f39a5b8ebca54cb349bb0d3fd5d154abbd935cf1e24f8129f4744c2e51bf98 + wai.image.avif software [builtin:wai@0.3.0] 1 image per completion at 1280x720: a 1280x720 picture from seed 2 at quality 60; decodes, payload sha256 65b8f6728a751bec333caab31dbbe03a0200b4d14f6c2135b6cb9fced33543a0 + wai.image.jxl software [builtin:wai@0.3.0] 1 image per completion at 1280x720: the 1280x720 picture at distance 2.0; decodes, payload sha256 021e172d10983472562df4506f83d50d6dff3b9b7cf14512f94cdbd6b20a5b91 + wai.video.av1 software [builtin:wai@0.3.0] 30 frame per completion at 1280x720: 30 frames 1280x720 from seeds 10-39 at quality 60; decodes, payload sha256 3d6ac29bf1dc6c6a71103f4881e8b96a66c4c3cdcf2b101cf60c78150bf6d72f + wai.audio.opus software [builtin:wai@0.3.0] 10000 content-ms per completion: 10 s of two tones, mono 48 kHz, at 96 kbit/s; decodes, payload sha256 733bf012f596978fef695583a5a312849bb6a27b33e2bae63b852f9a9ccf4bfd + wai.audio.flac software [builtin:wai@0.3.0] 10000 content-ms per completion: the same 10 s; decodes, payload sha256 ff2af19c6bb6215055117b2c86686a1113451d00fcdd25b13537331c19ef7407 + wai.text.zstd software [builtin:wai@0.3.0] 4194304 byte per completion: 4 MiB of words from seed 7 at level 19; decodes to its source, payload sha256 2553ced638440a380e42ab15404673253d9a0e6f5f4c486e35aef312e656b149 + wai.text.xz software [builtin:wai@0.3.0] 4194304 byte per completion: the same 4 MiB at level 6; decodes to its source, payload sha256 a58c78ae580c453cae300e73cc73194f84c048c0b5a0e984c0552ac10ae1daa9 + wai.video.avc software [platform:media-library] 60 frame per completion at 1920x1080: 2 s 1920x1080 30 fps test pattern (60 frames), -c:v libx264 -preset medium -crf 23 sha256 29a813955d3a7b30fe9c5ad08f43da0652c21626fe05c59a9051a29247fe0229; the tool's software decoder, one thread per process, output MD5 MD5=82bdbdb4291a71ae59f18ef1ccb1abf0 + wai.video.avc hardware [platform:media-framework] 60 frame per completion at 1920x1080: the same clip on the platform's hardware decoder, frames kept on the device; output MD5 MD5=82bdbdb4291a71ae59f18ef1ccb1abf0, the software path's + wai.video.hevc software [platform:media-library] 60 frame per completion at 1920x1080: 2 s 1920x1080 30 fps test pattern (60 frames), -c:v libx265 -preset fast -crf 26 -tag:v hvc1 sha256 8d4e6c9a0efa78453cc4905cf3a2f7b96089c8f076af27e7950c8030285bc23c; the tool's software decoder, one thread per process, output MD5 MD5=beb40263ca65da630ced7bacaa961f52 + wai.video.hevc hardware [platform:media-framework] 60 frame per completion at 1920x1080: the same clip on the platform's hardware decoder, frames kept on the device; output MD5 MD5=beb40263ca65da630ced7bacaa961f52, the software path's + wai.video.av1 software [platform:media-library] 60 frame per completion at 1920x1080: 2 s 1920x1080 30 fps test pattern (60 frames), -c:v libsvtav1 -preset 8 -crf 35 sha256 f76b1979437ee3f347345d3fc06b24750855d029b76722962f4508dd3ce0b54b; the tool's software decoder, one thread per process, output MD5 MD5=75e3c4deef532a2ae51cac3854e12e2a + wai.video.av1 hardware [platform:media-framework] 60 frame per completion at 1920x1080: the same clip on the platform's hardware decoder, frames kept on the device; output MD5 MD5=75e3c4deef532a2ae51cac3854e12e2a, the software path's + wai.video.vp9 software [platform:media-library] 60 frame per completion at 1920x1080: 2 s 1920x1080 30 fps test pattern (60 frames), -c:v libvpx-vp9 -deadline realtime -cpu-used 8 -b:v 6M sha256 d9af343406d25aff46a95d928deac273e8f88af824bafad2bd8931dc094d6b6f; the tool's software decoder, one thread per process, output MD5 MD5=fced24d8b9466647848c258a6e3c3a77 + wai.video.vp9 hardware [platform:media-framework] 60 frame per completion at 1920x1080: the same clip on the platform's hardware decoder, frames kept on the device; output MD5 MD5=fced24d8b9466647848c258a6e3c3a77, the software path's +note: platform media tool: /opt/homebrew/bin/ffmpeg (sha256 7f6028378534aa10a2dfa07d807ddcc0d18c869bf5c2f64c89efcc18ee88c4ee), ffmpeg version 9.0.2 Copyright (c) 2000-2026 the FFmpeg developers +rows absent for no_decoder: the registered payload capabilities this build (codecs, neural_int, video_int, audio_int, world_int) does not decode; no_work_item: those it decodes and this procedure has no item for diff --git a/wai/live-conformance/sink-energy/tables/macos-arm64/readings.json b/wai/live-conformance/sink-energy/tables/macos-arm64/readings.json new file mode 100644 index 000000000..8c457b22d --- /dev/null +++ b/wai/live-conformance/sink-energy/tables/macos-arm64/readings.json @@ -0,0 +1 @@ +{"rows":[{"capability":"wai.audio.aac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.ac4","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.avs3","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.flac","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[{"busy":[[2691128803,210268],[2509725708,214454],[2551296432,208655],[3481541115,210218],[3734849541,216871],[3171962358,212353],[3450053311,212853],[3445718940,204938],[2894381857,220792],[3066270795,223853],[3026821270,220839],[2795866568,209146],[3221460957,209124],[3021787417,201613]],"completions":2831,"idle":[[46896,210070],[10896,202036],[10615,208365],[9473,200769],[9972,201131],[12703,208059],[9447,207575],[11451,210079],[10872,210080],[10448,200346]]},{"busy":[[4256342036,216890],[1168620753,210645],[2419755992,210389],[1944236614,212434],[2654655761,206504],[2199662958,220337],[2419668766,212401],[2436727855,225308],[2926445646,213003],[2189611129,210049],[1147200931,217399],[2389102301,223531],[2999886959,214059],[2531705092,218177]],"completions":2242,"idle":[[50729,201059],[9786,205914],[9063,202402],[10000,208826],[9247,207831],[9669,210065],[9907,204614],[9870,207699],[9661,203587],[46744,205557]]},{"busy":[[3248162300,204594],[2694018572,214655],[2555229985,214369],[2901660271,204115],[2820847838,210702],[3151584050,220791],[2909614208,204644],[2747366717,201413],[2842674846,206640],[2365991937,210668],[1803145930,206254],[2838210269,210939],[3115327802,209038],[2838349901,205989]],"completions":2576,"idle":[[48263,204119],[10060,206615],[9059,202021],[10439,204353],[7245,206284],[9174,208092],[9212,205582],[8124,205768],[12009,200930],[9592,203642]]},{"busy":[[4430926962,203542],[4575767167,203042],[3676074826,208562],[3877943450,208060],[3992614812,207099],[4172526461,208707],[4213277304,204710],[4593231342,215198],[4474870905,213783],[5162187635,220020],[4745120424,204783],[4605984573,209127],[4737924519,204684],[4552913126,205376]],"completions":4065,"idle":[[46604,207768],[8870,205718],[8251,201630],[9921,208014],[12141,209859],[5875,206036],[9043,202457],[11562,204747],[11400,204041],[8921,204928]]},{"busy":[[4987325178,203568],[6191758813,209334],[5972654877,210045],[5844458645,201165],[6066666220,208425],[5251052013,200713],[5078898294,202435],[5748337721,202368],[6098566681,209088],[5871379401,202452],[5911746622,201381],[5975736947,210056],[5385139978,210627],[5078716474,212446]],"completions":4871,"idle":[[54738,204180],[7417,210003],[8386,208467],[7830,206024],[9050,200810],[7905,203567],[9192,210054],[8385,210049],[7526,201530],[8897,210055]]},{"busy":[[4785396247,211555],[5299615172,210202],[5132447239,200571],[5295940196,200364],[5036600159,200267],[5116836219,208387],[5082498657,201324],[5273897966,205763],[4994833357,206081],[5567025654,202874],[6140831060,207252],[6066128507,201745],[6308514693,210031],[5910108381,210078]],"completions":4806,"idle":[[44605,206277],[9276,204506],[10424,210065],[8873,200183],[10475,209635],[10963,203839],[9659,203194],[9770,204792],[11114,208213],[12098,205732]]},{"busy":[[4714399776,200966],[5919253997,200789],[5954268994,203029],[6156415225,210041],[6225522956,210035],[5973632891,203932],[5595301184,210050],[5830010775,210049],[6228252980,210037],[6090748600,203250],[5951101383,203198],[5973903165,201496],[6087664870,210046],[5940324349,200513]],"completions":4987,"idle":[[18064,200949],[6111,210078],[3402,202165],[4590,206769],[7757,200855],[3323,210053],[7154,210060],[3054,204590],[3717,200873],[42491,202511]]}]},{"capability":"wai.audio.iamf","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.int_codec","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[{"busy":[[1573383031,217999],[483508205,222291],[515741753,222230],[440103158,207959],[438036436,219196],[345868044,210190],[659948910,216746],[615754788,235028],[387409376,203933],[331535642,210058],[393245180,218898],[356538563,207375],[372481515,233977],[310453266,203393]],"completions":1338,"idle":[[46959,210050],[8113,210082],[11799,202455],[10053,204918],[8785,206196],[13019,203330],[9777,209333],[11329,202394],[8339,203762],[10554,210071]]},{"busy":[[1524966373,204050],[648138153,207560],[455842744,207826],[607331681,211414],[648313993,222365],[566072773,221230],[516063250,209008],[565015927,206857],[711291314,209664],[653317588,207502],[636875385,221942],[759907702,214963],[672081829,209285],[572364056,212603]],"completions":1431,"idle":[[46186,200896],[10322,208119],[9398,201093],[44414,207922],[10548,210072],[10595,205633],[11376,204736],[9072,201823],[9767,204303],[9728,202500]]},{"busy":[[1941510413,209724],[895747068,206607],[835880018,201329],[1050735550,207774],[1431501362,212074],[1033497580,204768],[1133184205,210133],[1393632230,211318],[1088438138,204101],[1221024716,200770],[987338815,203469],[918160663,210281],[1061090040,208027],[938518404,215985]],"completions":2217,"idle":[[46922,203008],[9537,204817],[9566,204575],[9267,201839],[8979,205085],[9855,201594],[10718,201639],[8513,211250],[10235,205807],[8355,205938]]},{"busy":[[2792749113,210590],[2537939366,208494],[2581972008,204960],[2008242848,201523],[2426656455,209968],[2133388265,213553],[2661052632,205790],[2468645573,212414],[2585391293,208389],[2224120190,208812],[2280028531,207118],[2782450489,212016],[2535054275,213791],[2397210025,204148]],"completions":4816,"idle":[[36991,200258],[9488,208490],[9323,203701],[9710,205401],[9197,204398],[9305,201028],[9388,206266],[8760,201638],[8476,205660],[14481,206816]]},{"busy":[[2340409681,211443],[2694626300,207782],[3343384285,212156],[2790934390,214411],[1678153149,210055],[3181225553,211155],[3924800311,210194],[7120753887,210057],[7404466435,210056],[7605399294,210046],[7877338841,209199],[7914558168,210036],[7697816480,206584],[7783959094,206317]],"completions":9927,"idle":[[16642,210063],[2888,200087],[2999,210069],[3351,210065],[3567,210048],[45829,210055],[4484,210051],[9067,210067],[2894,209463],[5164,200631]]},{"busy":[[2506527732,205846],[1031309449,210845],[913864729,203203],[881691294,207564],[752207607,208206],[859112504,208980],[678581883,211467],[764426298,205863],[898532611,210050],[964627022,205940],[1206637495,210667],[1945633030,210108],[2505393530,202119],[2093994187,208209]],"completions":2669,"idle":[[46472,203192],[12293,202754],[9723,201568],[9758,204376],[10222,210061],[10652,200454],[10506,200695],[44427,210063],[10765,201658],[16865,206426]]},{"busy":[[6164132285,207285],[6545756236,206657],[7580828819,210039],[7761688647,210049],[7577558363,210043],[6913647303,203602],[5904145811,205596],[7892444906,210040],[7789379202,210052],[7654277182,210066],[7671041229,210068],[7199854325,210058],[5757777769,207552],[6106144135,200971]],"completions":12727,"idle":[[43869,206991],[3175,210070],[2846,210053],[4112,210064],[2750,210062],[2824,210068],[2887,210059],[2509,207114],[3454,207142],[3531,210080]]}]},{"capability":"wai.audio.ivas","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.mpegh","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.opus","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[{"busy":[[3135976517,200705],[3276402809,200593],[4025702151,211991],[3989334000,207741],[4596026123,221967],[3890224760,210087],[3457128259,215638],[2610589577,214778],[2755123922,214527],[3477621394,220417],[3639475505,225925],[2522607072,217524],[2796941780,226607],[3140533920,211123]],"completions":1262,"idle":[[42795,203349],[9941,209146],[16300,200343],[6915,200713],[8339,205404],[11497,210106],[9823,210109],[9338,200632],[10153,200263],[9322,210076]]},{"busy":[[3194955006,207831],[2291280082,215068],[1835667053,210252],[1921660059,215220],[1984249852,216423],[2884249002,205428],[2544411753,220382],[2416275581,203207],[2176286051,206965],[2056756425,201211],[2236523632,202357],[2182876669,217918],[2440492862,214675],[2469726092,207670]],"completions":898,"idle":[[29815,208450],[12248,208713],[8956,200562],[8938,202994],[9910,201583],[9779,209306],[10612,208295],[8660,200477],[9211,210055],[9458,202068]]},{"busy":[[3054771729,213800],[2106620596,209737],[1821209767,209523],[2711387144,214900],[2667133850,213857],[2256777951,204902],[2227394730,210051],[1810923509,204825],[2345779574,205342],[2423503857,235071],[2065513301,200634],[2293715457,215477],[2483959915,205963],[2685142751,213327]],"completions":891,"idle":[[50706,209251],[9911,200058],[38931,207039],[9144,208931],[10225,206286],[10032,209516],[9687,205752],[13451,205210],[10097,203449],[9615,208868]]},{"busy":[[3993072181,208234],[4408994384,204954],[4132598679,204399],[4429658205,213280],[5057587933,212028],[4699993138,211155],[5172888830,200318],[5301922467,202932],[5239249910,210528],[5435997826,210042],[5204339140,203636],[5405168553,205230],[4922446472,211321],[5576693693,211392]],"completions":1813,"idle":[[43101,205312],[9033,204141],[10803,205641],[10829,206095],[8656,203592],[8404,202045],[8263,210065],[9233,207753],[9117,210035],[8648,207641]]},{"busy":[[5499808223,210054],[6091834389,206084],[6014918312,202881],[5934210361,210033],[6050667252,209109],[5966950939,205211],[5594189436,200725],[6062687226,203635],[6163116317,210032],[6174536540,209174],[6084657279,208644],[6158160769,209250],[6068304163,206160],[6259336848,210021]],"completions":2066,"idle":[[49791,205728],[10879,210063],[8798,206839],[8561,207850],[9349,200190],[9707,210061],[9807,210069],[9006,200646],[8948,200932],[10162,201035]]},{"busy":[[4188989555,213369],[4863744595,208002],[4515398398,207293],[4297541779,208028],[5050767382,205577],[4114067136,210410],[4153220362,205173],[4542608681,207371],[4777577540,215397],[3951192868,212144],[4455050595,201020],[4817218658,210450],[5517558976,212257],[4913619015,210484]],"completions":1713,"idle":[[42424,205207],[9442,202206],[8510,201763],[9377,203351],[10965,202698],[10348,205203],[10144,210070],[10471,208117],[9774,201068],[9971,209173]]},{"busy":[[4789737025,200307],[6152000765,210041],[5943218315,201379],[6233703128,210032],[6099968797,209145],[6191972655,210035],[6303387412,210032],[6213885012,208688],[6301595555,210059],[6188128169,210036],[6266885870,210028],[6256939806,210080],[6272758279,210047],[6217974175,210042]],"completions":2108,"idle":[[43196,210066],[3015,206948],[2924,200253],[40403,207650],[3246,210064],[3427,201386],[3110,210080],[3132,201240],[3315,203455],[3264,208730]]}]},{"capability":"wai.audio.opus.packet","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.scene","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.score","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.avatar.pose","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.feed.state","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.film.linear","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.film.reel","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.generate.spectral","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.haptic.mihs","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.haptic.signal","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.avif","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[{"busy":[[3712764168,214735],[3293629524,216515],[3834693879,218695],[3712823067,216411],[3125056986,208245],[3840764686,211672],[3841785385,207516],[4796443663,217031],[4450223252,221419],[3694225834,209996],[3539722390,209970],[4927592974,222277],[3370876686,200152],[4027203100,217205]],"completions":696,"idle":[[50161,210070],[11260,201113],[13090,210080],[10386,208235],[11590,210064],[10187,210083],[12440,210080],[11637,210095],[12398,210174],[9892,209678]]},{"busy":[[3120676543,210401],[2253428450,227311],[2357888434,223682],[2335659655,215166],[2041230522,207132],[2163832299,203994],[2088895310,209983],[2465265936,213636],[2325475276,210603],[2331806168,210184],[2398640222,206853],[2198859342,217035],[1976104644,203916],[2336136463,207876]],"completions":428,"idle":[[47767,203137],[50882,202019],[10061,209414],[9949,207569],[9589,203359],[11335,201920],[10342,200713],[9459,206035],[9602,200528],[9844,200142]]},{"busy":[[2833914295,217358],[2858469397,211446],[2274110243,211335],[1978935170,207451],[2638167039,210812],[1690184300,213342],[1888601824,218420],[1943466873,208767],[1570422969,208487],[2735543830,210092],[2124558506,213727],[2435892107,206563],[1889657554,200348],[2329317989,210410]],"completions":411,"idle":[[78819,210835],[8824,201339],[8584,200842],[8622,206105],[10334,207758],[10500,211053],[10368,204147],[9300,206071],[9688,203701],[14584,203145]]},{"busy":[[4208555594,206008],[5153686906,203538],[4927734231,203845],[5166792014,206043],[5198026478,210053],[5203689731,210060],[5133350506,207853],[5073202058,206426],[5079832476,200242],[3722558578,203054],[3125559089,211621],[4715290292,204700],[4870681982,202610],[5155266840,201942]],"completions":869,"idle":[[49456,205431],[9426,208558],[8440,209319],[8812,207682],[8468,208693],[9620,210062],[9109,209936],[8467,203492],[8659,204500],[9203,201407]]},{"busy":[[5059977988,202894],[6284158784,210027],[5901431603,203177],[6080256288,209677],[6492462243,210057],[6509095218,208018],[6388593791,200154],[6606753820,210039],[6173489551,209833],[5951767971,202137],[6667490807,210053],[6426331283,208680],[6424909529,201132],[6471511488,206695]],"completions":1066,"idle":[[41384,209771],[9088,202006],[11623,210064],[9614,201616],[8904,205648],[10557,202412],[8450,207582],[9262,210054],[10050,210059],[9679,210060]]},{"busy":[[5392261918,204193],[6557136575,200153],[6706210131,201862],[6987027961,210067],[6390164137,203156],[6938442887,210035],[6730178925,202059],[6921976011,210034],[6783288965,205867],[6957257312,210036],[6890307821,210036],[6593921932,201073],[6649323268,200620],[6951558861,210036]],"completions":1117,"idle":[[22278,205505],[3193,209115],[8776,209932],[8656,210069],[3163,210053],[8332,203199],[3227,210075],[2954,207785],[3030,200884],[3264,208443]]},{"busy":[[5430808150,210053],[6538339555,202616],[6444667211,210075],[6239237270,200996],[6657116236,204807],[6485092949,200918],[6572974044,205774],[6463551339,201560],[6542067326,210050],[6370661875,207773],[6060302428,209057],[5974170533,206315],[6531395569,210028],[7096521774,210031]],"completions":1071,"idle":[[47916,207700],[8241,200180],[8643,207949],[9507,200979],[8622,200379],[9314,203983],[45308,210053],[8960,210064],[4715,204714],[8651,210080]]}]},{"capability":"wai.image.jp2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jpeg","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[{"busy":[[3316531015,220085],[4166045312,218409],[4007188629,218556],[3681318324,209522],[4216497164,218320],[3658714112,210040],[4039531618,214233],[3194144694,212853],[3925982694,213663],[3253720206,220076],[3138980073,225290],[3311249382,227943],[3301477145,230253],[3220355902,211729]],"completions":1425,"idle":[[43095,207002],[11599,207067],[9112,208012],[11850,210078],[8035,206114],[10177,200729],[10206,200771],[9715,200199],[9091,202058],[9317,201548]]},{"busy":[[2500979778,214233],[2288852142,212504],[2514766499,210473],[2248584083,202579],[1828712839,209194],[2516037159,207664],[2261881125,215512],[2911016897,226150],[2434653523,203450],[2589410100,210470],[2686097870,215798],[2170350761,204542],[2309978148,222114],[2514642732,210603]],"completions":969,"idle":[[46142,207167],[8600,205682],[9161,208948],[10189,204460],[10766,200493],[10485,202853],[8952,204850],[10825,210073],[10145,201726],[9748,210079]]},{"busy":[[3067009720,202345],[2490028233,215882],[2576051307,210133],[2785804431,228734],[2417567658,203112],[2336464208,207335],[2123961091,214168],[1543004759,206895],[1956971215,212582],[1673561499,208616],[2647626326,210663],[2818821457,215998],[2919047164,205896],[2329879085,203196]],"completions":949,"idle":[[24136,208639],[9639,207102],[11745,210077],[8228,201337],[9478,207013],[10892,203144],[10634,206335],[10307,210080],[10269,203789],[9091,205558]]},{"busy":[[5165048023,215292],[4408300766,209842],[4692508409,206270],[5236806880,210249],[4619211364,202656],[4969347726,207815],[5224753834,204186],[5119376742,203759],[4582562867,212962],[4815302104,211577],[4936231733,211924],[4012850162,202521],[4522551278,201673],[4515884062,203405]],"completions":1853,"idle":[[32200,210062],[8626,208215],[8129,201704],[7868,203905],[8143,204924],[8942,204091],[8812,210063],[9513,205722],[8379,209306],[8904,201976]]},{"busy":[[4341383689,201115],[5620535645,210087],[5362091987,201014],[5429785999,206546],[5547831141,207362],[6102795740,205344],[6183834876,201017],[6082617932,201892],[6302795632,207616],[6032908329,201408],[5812910947,200596],[6182122927,203517],[6166570184,203403],[6409101095,208478]],"completions":2164,"idle":[[50402,200199],[9837,200235],[8828,200348],[8690,210057],[9067,210055],[9358,208091],[10982,210064],[9938,209229],[9901,202364],[10056,209474]]},{"busy":[[5553617332,210035],[6904353921,210034],[7015030908,206655],[7069494346,210037],[6879684636,201364],[7224132809,210047],[7233882787,210037],[7022104784,210037],[7269184181,210036],[6920845955,210041],[6920900585,202339],[7231395805,210037],[7233994923,210052],[7060145688,210047]],"completions":2647,"idle":[[40050,202579],[3505,210056],[7202,210052],[7203,210051],[7443,210049],[4664,205118],[6027,210078],[3278,210080],[3480,208542],[3677,205643]]},{"busy":[[3680001038,204958],[4268444392,227011],[3764056503,211728],[4660602262,208482],[5005689336,203229],[4302276012,212878],[4154197120,204042],[4002999335,201489],[4642505025,201638],[5241693324,205821],[6435059599,210035],[6671927711,210053],[5681112133,200110],[6402184251,206150]],"completions":1909,"idle":[[64254,200378],[9301,205068],[9532,202072],[9272,200152],[10768,210060],[9558,207706],[10511,207569],[10096,202127],[10321,203409],[9736,205374]]}]},{"capability":"wai.image.jpegai","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jxl","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[{"busy":[[6566143970,213719],[5419517003,209060],[6330550633,213685],[6492164843,212606],[6285574267,214097],[5668030019,210884],[5491205019,211330],[5775887255,213727],[4812632175,201544],[5384554292,223366],[5805917921,221119],[5524006381,211498],[6038936903,212218],[6363513855,213956]],"completions":1133,"idle":[[45074,207053],[10999,200316],[9774,205980],[10371,202248],[9864,210085],[11224,208206],[9325,202837],[10004,206687],[9482,210105],[8998,205298]]},{"busy":[[4063486454,213800],[3440533573,209975],[3411271285,200273],[3434216109,207137],[2977530867,212502],[3319950397,200191],[4135836259,221914],[3809398718,210505],[2847202840,204524],[3866573995,226787],[2885692124,218076],[3407454698,214265],[2815947507,201827],[3495049354,218518]],"completions":673,"idle":[[43973,205253],[10039,206732],[10206,207507],[9475,204228],[9783,201906],[11168,203186],[9672,200169],[9024,202627],[10841,204996],[11267,210077]]},{"busy":[[3308777496,208839],[1708635727,219821],[3193158925,207483],[2853658833,211813],[3362535214,203108],[3370360352,204882],[1588679623,201214],[1244360445,208682],[1515939561,209031],[1357761587,214586],[3001520569,207409],[2622245418,206162],[3975107357,212982],[2281191946,201610]],"completions":499,"idle":[[229937,208196],[11068,210076],[10758,203831],[12526,207358],[11305,210078],[11775,210070],[10160,209589],[8620,209262],[9728,202831],[11504,210067]]},{"busy":[[5294770016,206048],[6307261828,207322],[4675234608,217140],[3937462143,209302],[5755204362,215165],[6241118169,203218],[6607400954,217472],[6646726265,208651],[5769519886,206572],[6058035094,213926],[5335884769,209524],[5212248646,208319],[6219654570,202650],[5307958057,208528]],"completions":1103,"idle":[[48475,210077],[9121,200807],[9591,200124],[42734,204830],[9208,206803],[10073,200899],[8382,206191],[8905,210053],[8924,209082],[8926,205333]]},{"busy":[[6731637096,210112],[7997822101,201340],[7045820126,210052],[7640015997,208868],[8402877037,200701],[8956944790,210047],[8678014251,205054],[8103604221,207770],[7050915820,201663],[7063088198,202536],[7280895056,208865],[6942012991,211698],[6486233630,202945],[6913604472,203778]],"completions":1417,"idle":[[47633,203239],[8547,208052],[8811,208999],[8973,201863],[10285,200191],[9942,210065],[9618,200348],[9453,205852],[8788,203998],[10010,202952]]},{"busy":[[6209299711,202804],[8169524497,204077],[7152284275,201924],[8063326564,210053],[8126120117,204706],[7582310837,207809],[7519819562,200154],[8409926974,200984],[8826662373,203653],[8684984125,201247],[9072252101,210032],[8997572624,210048],[8586014925,200490],[8987255260,210044]],"completions":1512,"idle":[[43236,203199],[8419,200598],[9124,200688],[8725,202622],[8563,200634],[8399,200132],[7715,205756],[8737,202851],[7921,207377],[9884,200445]]},{"busy":[[7206688298,201787],[8669002800,200449],[9045842564,210040],[8948873728,210039],[9112740076,209263],[8541104086,201927],[9078495188,210038],[8617845723,201679],[8904106142,210029],[9056361845,209847],[9067259629,210039],[8852980837,207300],[8827704616,201458],[8631377745,200889]],"completions":1621,"idle":[[41767,206958],[7943,202233],[40759,210058],[8913,201119],[8044,203890],[8811,201649],[8066,208891],[8095,210059],[8996,200106],[8582,204678]]}]},{"capability":"wai.image.png","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[{"busy":[[5325217757,215268],[2009447872,219650],[1935029611,213292],[2031227542,212659],[2572951410,232439],[2189632285,231622],[2982423574,225161],[2520084820,241065],[2702478560,215847],[2399628525,222299],[3298503473,225633],[3066949416,211059],[2985930155,217611],[2796158569,222040]],"completions":986,"idle":[[43066,210077],[12613,210083],[11283,202080],[9940,201225],[9643,210093],[11467,204808],[9903,210090],[12101,200401],[11461,204305],[10768,201690]]},{"busy":[[2176242927,218427],[1636752026,247998],[1680445037,206209],[1772416495,201802],[1856270089,213938],[1594217160,218541],[1564531393,220302],[1639017109,214008],[1502220046,207293],[1618526117,200562],[1928902876,202662],[2240061241,209059],[2149632598,219802],[2041452811,215347]],"completions":682,"idle":[[47933,210064],[10360,206027],[10091,209715],[9902,200923],[9163,200326],[10867,210052],[8883,202948],[8459,200585],[12666,202486],[10950,204001]]},{"busy":[[2501210332,215285],[1455333752,203283],[1054470173,205776],[1178350638,211141],[1228706673,239037],[2096743642,203592],[2402936718,206474],[2232934935,206194],[1629762525,227191],[1283998988,215893],[887264864,225849],[785561332,210034],[1710863395,208187],[2374221122,208138]],"completions":585,"idle":[[46189,202768],[8689,203444],[9083,202390],[10020,205479],[13906,209781],[8467,200412],[8508,210057],[8318,203270],[45901,209908],[9768,209108]]},{"busy":[[3730947202,211088],[4328157423,210048],[4301804945,210402],[4323540333,205409],[4540313610,208820],[4648238349,212581],[4210429726,209180],[4366321151,209361],[4523073653,205000],[4280761768,203874],[4293706255,212524],[3757776151,210578],[3481225659,206835],[3868855669,213364]],"completions":1487,"idle":[[45964,206944],[8984,201630],[8401,201336],[8131,210048],[7913,208772],[9583,201314],[8643,210050],[7885,210056],[8559,208674],[8538,210066]]},{"busy":[[4255439478,206980],[5264208196,205242],[5214811963,204405],[5395772318,206428],[5316089712,207684],[5196700145,200449],[5153471651,201885],[5141902782,200936],[5241519521,201828],[5413664899,201409],[5436963018,200236],[5541061598,202274],[5425114028,205726],[5573546337,210068]],"completions":1748,"idle":[[42576,207347],[8880,201461],[9841,200368],[8869,200456],[9378,200422],[8427,205554],[8938,200795],[9040,200155],[9062,200747],[10338,210063]]},{"busy":[[4896518653,210054],[5901628361,210046],[5895380817,210041],[5944612270,210034],[5966731000,210032],[5958041880,210038],[5843378581,210038],[5944286752,210047],[5949199446,210041],[5769416773,202874],[5953908625,210040],[5878536082,210031],[5931924213,210044],[5776236465,210037]],"completions":1888,"idle":[[42284,210073],[3437,210050],[7497,203478],[11909,210082],[8544,203022],[3935,209353],[7858,203239],[7620,205911],[8035,204597],[4530,206765]]},{"busy":[[4763130897,209749],[5565614050,201300],[5873156345,210067],[5052650210,206331],[4801651531,207450],[4560684865,203255],[4731626923,210072],[4422558157,204258],[4647344450,203536],[4783648114,208834],[4673496673,207073],[4472307192,203067],[4494337025,201235],[4393158590,200250]],"completions":1694,"idle":[[18144,205034],[7479,210064],[2736,210071],[2873,210062],[2642,210068],[3026,201169],[2585,210060],[2532,210063],[4418,210075],[2985,209934]]}]},{"capability":"wai.mesh.draco","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.mesh.glb","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.bmshj2018","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.cmic","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dcvc_fm","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dcvc_rt","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.elic","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.encodec32","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.glc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.int_hyper","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[{"busy":[[3601499969,211886],[4461847032,217072],[4594153089,202584],[4816562886,214096],[4584023773,208541],[5136912838,206704],[3612148892,210052],[4639616936,206711],[4804392297,208404],[4560741946,210466],[4991583656,214500],[4598755841,207600],[4288812956,210045],[3815056412,206991]],"completions":247170,"idle":[[47085,210065],[9360,201906],[9290,208781],[10001,201145],[8104,200847],[10054,210071],[10138,201514],[8840,204679],[8389,202881],[8919,208888]]},{"busy":[[2781536148,212296],[2459160446,213798],[2599767489,204887],[2770852473,202017],[2299899895,201105],[2288511300,208909],[2073577820,212448],[2851666186,215751],[2850562081,210302],[2350765706,206987],[2964006789,212469],[2604298281,212644],[2395384937,216492],[2112283758,210387]],"completions":142010,"idle":[[20400,207820],[9505,210067],[8828,206224],[10880,203690],[10346,208609],[10520,207541],[9718,203606],[11049,210063],[10008,209926],[9995,207000]]},{"busy":[[3741864988,207143],[4180222632,209055],[3671672024,205803],[4335633638,212399],[5028001052,210050],[4814900979,204750],[4641332605,210033],[4367548000,201730],[4582518881,202189],[4679054322,211374],[4984173518,208417],[4176159523,210087],[4813857305,207972],[4001685346,210046]],"completions":245341,"idle":[[32074,200217],[8980,202197],[10300,203439],[10072,209629],[8167,207215],[8708,203295],[10755,200446],[10063,210159],[9493,204781],[9103,209451]]},{"busy":[[3721692328,220507],[3623536422,212083],[3755560810,210880],[3925061709,205592],[3808639139,205802],[3360207043,201689],[3886505306,202058],[4744738806,210168],[4825985651,203887],[4106209098,203884],[3699712308,207628],[4536706195,200561],[3750663203,203578],[4186371696,204137]],"completions":223027,"idle":[[42510,205781],[19104,210091],[59209,210082],[5367,203689],[10290,208112],[9111,201127],[13817,204216],[10298,209077],[9598,201214],[9960,209641]]},{"busy":[[5862877270,216563],[6918874999,210038],[6943405913,208389],[6751185095,200857],[7101266257,210035],[7156531918,210032],[7068736612,210031],[7109526702,210031],[6964834053,205241],[7110524631,210038],[7197759783,210034],[7015511678,206911],[6830361675,200959],[7111875555,210045]],"completions":358641,"idle":[[44937,210061],[3135,204525],[3188,209071],[46403,205750],[3237,204583],[7449,201585],[8622,201002],[7904,202562],[8036,203642],[9327,202519]]},{"busy":[[2101544829,208120],[2107688024,205896],[2798430881,211914],[3320581505,205008],[2523199190,201001],[2386449409,211994],[1785275215,211843],[2912719348,202376],[2205939280,210855],[1505737504,206782],[2355734905,218567],[1570155188,216083],[3156346769,204494],[3964573840,210993]],"completions":139687,"idle":[[37666,200623],[10515,200944],[9936,207083],[49394,204329],[10439,204256],[8676,208641],[10187,200626],[9215,200129],[8550,204068],[10277,208327]]},{"busy":[[5590622941,210050],[7039906759,210048],[6657947764,204453],[7078056456,210051],[7151616141,210054],[7234764050,210048],[7107510521,210051],[7097505911,210064],[7040205223,210041],[6775053178,202005],[7014247821,210043],[6830522501,201029],[7184020579,210048],[6802992417,200151]],"completions":358173,"idle":[[18463,200974],[3084,200355],[3131,203312],[2966,201847],[2762,201685],[2953,206374],[2968,210073],[3189,210076],[2864,202389],[3212,210063]]}]},{"capability":"wai.neural.int_mlicpp","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[]},{"capability":"wai.neural.int_synth","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[{"busy":[[1468349492,205148],[1340378480,213054],[1506587019,209458],[837678918,207429],[1441366007,206466],[1378167384,218430],[1277940758,206996],[913522764,212510],[1204972970,220030],[1462331995,207398],[1135831721,212037],[768563482,210074],[1257169788,203775],[1610214601,213295]],"completions":212,"idle":[[44104,208644],[11148,210087],[9360,204842],[11521,201423],[10107,200759],[10219,209106],[9916,207996],[10442,204446],[11904,208962],[10512,200258]]},{"busy":[[2235538681,214645],[635152193,207416],[1006126953,211880],[983762086,210739],[885422985,212469],[834927151,210886],[1076307690,231781],[1051128028,221570],[1021802027,216789],[1175732157,226815],[1261008496,214207],[1027426255,210469],[1094051164,208973],[1136574171,206891]],"completions":167,"idle":[[43361,202456],[9778,207359],[10886,200932],[9395,208951],[10283,206839],[10920,210063],[8908,206724],[10518,205740],[10657,203171],[8714,200085]]},{"busy":[[2713836688,205774],[1070594465,203601],[1497421618,211355],[908046952,229183],[1381937608,208515],[1043994157,208598],[935793763,212244],[942410021,204818],[1151869771,210221],[1331214073,213100],[1362700568,233978],[584532825,200900],[389072753,210057],[842937513,282725]],"completions":173,"idle":[[45142,202514],[10889,210075],[9495,200477],[8868,210066],[10399,210073],[11126,205038],[13037,201396],[9175,204864],[9309,209444],[10432,203596]]},{"busy":[[4066879114,207084],[3824836225,207142],[2787217818,203209],[2783607498,210763],[2709298718,210160],[2370988475,210061],[2438906689,207059],[4600922430,208180],[4866312507,208485],[3336022235,203742],[3642102355,202989],[4256412685,209659],[3887912092,209180],[4154770571,204013]],"completions":532,"idle":[[57414,210057],[9762,200715],[11310,200278],[9524,209661],[11430,204949],[12711,204751],[9623,202163],[11325,210061],[9638,200687],[9976,201042]]},{"busy":[[6334231351,207400],[8541506134,210040],[8408739157,203635],[8991114417,210830],[8631145888,210251],[8435163751,210049],[8633698366,210052],[8661764446,204137],[8602511651,210033],[8274119043,210063],[8888245697,210038],[8603059089,210064],[8198783372,203202],[8620153147,211470]],"completions":1088,"idle":[[41210,201013],[3236,210065],[8631,200959],[8516,201936],[7981,206431],[9357,208623],[8029,201739],[3530,207967],[3004,208892],[50665,210069]]},{"busy":[[3386558768,210069],[3278045799,210364],[4861914290,210063],[3340824280,207382],[4425983229,200874],[3157398452,201335],[1200190864,202439],[1568403342,200336],[2946252949,220326],[1861620576,210182],[1746656971,209682],[3966179275,210216],[3237183558,204766],[2864303496,205274]],"completions":452,"idle":[[79992,201011],[10678,200175],[9537,200656],[10103,204415],[8816,200691],[11308,202210],[11036,201129],[9891,200174],[10500,200228],[9817,210044]]},{"busy":[[3748872746,202379],[4258977881,210062],[4606575207,208820],[4965888835,206473],[4799034866,210158],[4736034558,210416],[4759907444,203257],[4820033022,203212],[4580362396,211181],[4498542774,210065],[4899517674,213647],[4385852384,208221],[4852284230,210091],[4372022274,211072]],"completions":761,"idle":[[19618,210060],[8598,201315],[2863,207322],[2900,202452],[2673,201671],[9726,206163],[3037,205759],[3059,201535],[2851,204089],[3130,210070]]}]},{"capability":"wai.neural.mbt2018_mean","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mimi","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mlicpp","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mlicv2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.snac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.video_bmshj2018","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.wavtokenizer","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.pc.gpcc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.pc.vpcc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.prior.nnc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.prior.set","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.quantum.circuit","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.quantum.circuit2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.semantic.clip","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.semantic.perco2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.4d","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.int_codec","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[]},{"capability":"wai.splat.sog","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.spz","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.tensor.nnc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.ternary.tt","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.text.xz","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[{"busy":[[2015540206,212989],[1840927591,221258],[1548381287,216941],[1231668513,203809],[1750069089,211873],[2150512489,223544],[1814776920,203570],[1382346981,213069],[1943296175,223159],[1897280480,217622],[1514587664,212092],[1769683509,211603],[1600987500,216892],[1580592899,209251]],"completions":674,"idle":[[43053,205910],[11863,205639],[10157,209791],[10245,204634],[10604,210082],[14569,209094],[7694,210074],[8948,207624],[10012,200511],[10080,202324]]},{"busy":[[2342055842,214857],[1843797609,219447],[2062786493,210527],[2064049032,211202],[1758257778,200183],[1497304837,206100],[1759683598,227572],[1732904636,204872],[1687311610,227476],[1566020711,230464],[1775265644,208358],[2358372268,220218],[2314365955,219500],[2068837442,207755]],"completions":740,"idle":[[46280,200365],[9967,209345],[11018,210064],[9279,210085],[11544,201713],[10374,206238],[10508,205871],[10651,201193],[10611,205225],[11674,204943]]},{"busy":[[1990939363,216708],[1871536317,216418],[2130224906,215435],[1743755058,217726],[1853384705,212519],[2169529515,212540],[2231022746,210120],[1740939957,210067],[2263827702,209923],[2552238179,214306],[2592674434,239045],[2404455730,219213],[2311245478,212500],[2697526030,219333]],"completions":868,"idle":[[45001,203694],[9676,210000],[9428,200352],[9618,208165],[10601,205619],[12103,210081],[9155,200787],[9192,201028],[8411,207705],[9362,200852]]},{"busy":[[3101221060,206685],[3046429671,210400],[3054830687,207370],[3725322112,210988],[3714602153,209333],[3293389453,212028],[1538386189,209422],[964771503,209807],[2185293108,206239],[3013699836,208395],[2272886349,202463],[2265881798,210103],[2201257962,207597],[2137268565,212290]],"completions":1015,"idle":[[43172,204103],[8252,209778],[9328,203394],[9678,204227],[10046,210070],[9897,209987],[10195,210002],[9381,210064],[8806,205826],[10939,210077]]},{"busy":[[3796648645,210127],[3518693478,202304],[4207603620,210203],[3857658225,207828],[3418614077,210220],[3337346417,211117],[2986414043,214082],[3024414693,206931],[3284806841,209494],[3283315663,210061],[3582073199,202591],[4007337679,200697],[4124701003,208771],[3982743913,209150]],"completions":1385,"idle":[[45905,200433],[11316,201715],[12202,205834],[10898,201920],[11421,210069],[10092,200324],[11187,202401],[10025,202952],[9341,201340],[45248,202799]]},{"busy":[[3423241873,215291],[3639425827,214840],[3520292449,205847],[4324171991,209192],[4776819738,200059],[4919262446,210039],[4797601689,210049],[4665426906,200822],[4704656753,200206],[4990954293,210042],[4888502360,210043],[4875768451,205748],[4395731195,210534],[4269285364,212794]],"completions":1642,"idle":[[41941,205027],[10392,200454],[9198,207235],[9084,203197],[9603,204766],[10064,204149],[9000,200130],[9599,201575],[9430,202407],[8374,200089]]},{"busy":[[3977664002,210047],[4758029195,200152],[4817098179,206421],[4696555253,201519],[4892389216,204513],[4719926823,202453],[4935062389,210043],[4750858653,202768],[4808887503,202631],[4850924186,208133],[4988479225,210029],[4785261260,204507],[4670912761,200107],[5027371088,210041]],"completions":1727,"idle":[[18509,210056],[8389,210055],[7397,210065],[3240,201914],[3507,208529],[3547,205365],[3694,201004],[3162,200089],[2974,201476],[3420,201539]]}]},{"capability":"wai.text.zstd","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[{"busy":[[2239962763,218774],[3159390097,215093],[3716085773,219528],[3695438467,221822],[3303088965,214206],[2602248631,207154],[2605155687,215888],[2961061739,210736],[3390947803,217369],[3348916506,215737],[4452385596,212372],[3805999095,219472],[3006464257,217603],[2941323900,204545]],"completions":8575,"idle":[[46094,210064],[10188,207557],[10140,200558],[9141,200920],[8707,202834],[10030,204510],[11725,210076],[10875,210078],[11864,200461],[50582,210057]]},{"busy":[[4041206637,209473],[3368603415,213914],[3171753213,201007],[3716178439,210371],[2616337936,223638],[3121218572,211558],[2999628654,212742],[3672393545,211164],[4058876062,210562],[3361921932,219996],[3312751072,230032],[4014209756,214281],[3018668358,217023],[3006011015,220169]],"completions":9025,"idle":[[45726,210066],[10906,210058],[9677,205335],[9937,202659],[10969,210057],[8316,202555],[10598,207449],[9342,200479],[11020,205564],[10554,203735]]},{"busy":[[4430389190,206547],[4601598839,207603],[5202633971,204812],[4846642896,211485],[4623720116,203321],[4693129661,206589],[4634279584,214939],[3697217004,207871],[4344474917,212621],[4014466968,209455],[3775120682,201517],[4904046367,210063],[4610372958,208696],[4547692033,215177]],"completions":12038,"idle":[[42541,206440],[9949,206859],[8564,200079],[9433,202118],[7457,206427],[9838,202980],[7571,206320],[44435,201133],[9657,200594],[10444,203316]]},{"busy":[[4438192223,210010],[4472365575,207976],[5244900870,218998],[4566870832,213015],[5039922383,213443],[4743652307,213697],[4811696337,214392],[4247510250,211657],[4761376724,203151],[5342543645,208275],[5247404510,210573],[5560465158,210706],[4953199918,204184],[5174692575,211435]],"completions":13025,"idle":[[16349,200319],[5350,200643],[9150,206505],[7789,209078],[8582,201968],[12646,201453],[7114,209159],[10343,202179],[8672,200159],[8865,209291]]},{"busy":[[5606485221,210064],[6712243700,206128],[6518251974,208639],[6289594453,210069],[6320924027,209444],[6071592430,206974],[5910797703,214098],[5934615949,210141],[5950983148,202924],[5987852001,210349],[6052346807,209202],[5789106907,204572],[5790038981,210955],[5837091162,209505]],"completions":15845,"idle":[[48696,203764],[10097,208657],[8982,200565],[9477,209071],[8586,210057],[10438,200916],[10652,200074],[7701,204534],[10277,210061],[13115,208006]]},{"busy":[[4825998769,202960],[5461175498,200137],[6097036240,201031],[6469752920,202759],[7609598504,210045],[7935576023,210064],[6916751168,209054],[6061188172,202527],[5723536728,213240],[5987070852,211892],[5777151035,203098],[7527813955,209415],[7801459879,205954],[5198737089,214021]],"completions":16656,"idle":[[52071,210069],[3083,210067],[3023,210049],[49624,210055],[8184,210070],[3430,200581],[2934,207994],[8354,210068],[3237,210069],[3078,204667]]},{"busy":[[4650714113,210282],[5513862310,210609],[5107777681,202951],[5556524252,206093],[5411743364,206492],[5161606752,205610],[5003593862,200166],[5657094667,210075],[5493024523,200493],[6772703588,200882],[7177192135,201140],[7966558958,210036],[7485588307,200863],[7290722816,200859]],"completions":15702,"idle":[[39851,203478],[3446,200214],[2904,210050],[4150,206885],[8436,201983],[8620,210062],[9464,201097],[8823,210067],[9444,208474],[9287,200294]]}]},{"capability":"wai.video.av1","decoder":"platform:media-framework","path":"hardware","profile":"macos-gpu","rounds":[]},{"capability":"wai.video.av1","decoder":"platform:media-framework","path":"hardware","profile":"macos-task","rounds":[{"busy":[[1099021126,214264],[106455698,210699],[562882485,205851],[178451212,208991],[131731726,212422],[103836831,208316],[482210544,219278],[911536266,211153],[190683132,203027],[246731323,210656],[200408879,210807],[195860787,205581],[236870293,210312],[422446030,208403]],"completions":10,"idle":[[51172,207847],[10241,206252],[11427,204687],[10956,210067],[9957,200716],[10681,206170],[8833,207941],[10450,204714],[10173,209731],[12806,205035]]},{"busy":[[1016901785,210245],[204996950,202974],[34038712,211211],[89405341,212659],[537313299,213053],[145535820,210347],[81663764,216649],[102670272,208826],[166708913,211795],[471407213,205281],[660598013,222393],[466025045,204863],[188096331,218511],[336665703,217043]],"completions":8,"idle":[[41300,201151],[13709,202798],[8546,200096],[9978,201531],[12045,204230],[11515,204012],[9200,205083],[11139,200121],[9572,201794],[11794,210074]]},{"busy":[[1216106902,205068],[835669254,208969],[1528227987,205356],[560837637,208175],[235677516,207996],[809721320,205477],[1034708847,207629],[937221225,206263],[512319064,209791],[431027610,209273],[452282044,201336],[948395884,211834],[619039160,201171],[920172950,211097]],"completions":26,"idle":[[10301,204674],[8220,200716],[9093,210051],[8369,203069],[7996,209585],[9095,202519],[11416,204577],[49808,209965],[9118,208512],[9584,204146]]},{"busy":[[1012594300,208113],[336102799,203235],[452049645,211805],[162403426,204643],[317082482,202264],[1086084937,214350],[408706897,212064],[232385854,210078],[601498740,214579],[189393160,210313],[755475552,214647],[960466988,215002],[482787564,205638],[555781683,214398]],"completions":16,"idle":[[56268,204078],[10032,209215],[8796,205317],[10132,202002],[8612,210069],[10170,205181],[10091,201634],[10306,208574],[11478,202592],[9374,203954]]},{"busy":[[931453551,210537],[935869650,208073],[1783292440,210107],[1124560959,208750],[1654691384,210152],[1166956160,205938],[1360392421,205270],[1112067053,210284],[1258289544,206617],[949102361,210271],[1641755766,207414],[934016688,210153],[1679698304,203873],[1040507758,209602]],"completions":48,"idle":[[20850,205188],[3054,210064],[9633,204270],[9594,201253],[10681,204415],[9171,210058],[8745,200627],[8587,204324],[9420,210058],[8256,209305]]},{"busy":[[1295084820,202501],[1505768173,210385],[1580166413,200515],[1829397983,210122],[968660835,210107],[2038817800,203049],[1639359650,208738],[1507249073,210111],[2159542954,207577],[1821886114,201389],[1805741963,210102],[2108485632,206860],[2089634865,206840],[1315964279,200427]],"completions":66,"idle":[[42808,200625],[49923,210051],[8807,210050],[8502,209412],[9277,206828],[8943,206327],[9391,200819],[9339,210074],[9637,200672],[9926,203450]]},{"busy":[[1311388773,203676],[2010939015,203145],[1268313637,200907],[2207177653,201925],[2040292813,210075],[961798691,201113],[1913484191,210074],[1570390245,200684],[1641106429,201812],[2026036679,206680],[1299780659,204239],[1971697320,200361],[1981215475,200343],[1385129843,200246]],"completions":67,"idle":[[41855,201896],[9888,210077],[3308,208063],[7772,210077],[3885,210062],[3109,209079],[8310,204239],[4468,202741],[2929,208791],[3075,202072]]}]},{"capability":"wai.video.av1","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[{"busy":[[5955811325,216301],[4664703700,219216],[3959060569,212995],[3446049990,213617],[3875996783,228699],[4228570868,215746],[5209041028,222877],[3588180600,213842],[4036759525,216746],[3968465761,208698],[4938883839,221420],[3384734405,208193],[4387326194,219631],[4457257296,220019]],"completions":24,"idle":[[41684,202059],[12016,204451],[8971,203210],[9195,201325],[11131,202181],[10569,201815],[11140,201084],[11172,200155],[9246,200384],[9301,210050]]},{"busy":[[2850320218,213901],[2972212734,213271],[2436843294,206908],[2162621028,206737],[2849493443,213779],[2031945904,213210],[1404349464,216105],[1978756010,217810],[1806418400,200892],[1565952860,212925],[2585625250,224905],[1666228839,230565],[1255065537,207877],[2009411658,209019]],"completions":8,"idle":[[53412,205877],[10696,210078],[10506,205249],[10230,204445],[9549,210086],[12493,209725],[8731,210067],[11296,210056],[8383,210069],[10681,205623]]},{"busy":[[3202017621,219838],[2661155981,207520],[2758825100,229240],[2467906457,239075],[2998616561,224946],[2020755756,211865],[1270614296,209764],[834155925,201382],[2118405793,213498],[1707513192,219793],[2693801445,210279],[2753184820,206344],[3303896939,215167],[1669442172,218924]],"completions":8,"idle":[[68255,200610],[10272,209495],[9945,210067],[9537,211141],[10984,202647],[10141,210073],[10058,201564],[43535,206044],[10552,207716],[9156,207773]]},{"busy":[[5969780054,210129],[4983575468,210996],[4187617162,211329],[3845425484,203662],[3551510277,204207],[3550515071,208722],[3544278249,217234],[4461110473,206223],[3435043074,213846],[4867168702,205838],[4202934058,214993],[3710978508,214132],[3832092007,202327],[6452425594,237547]],"completions":24,"idle":[[45990,207379],[8838,202315],[8702,207045],[9996,210066],[9011,201149],[9781,201789],[9057,206130],[9531,208173],[9289,210079],[10977,200083]]},{"busy":[[3728889713,203536],[3169488529,220953],[3917018301,221242],[3062686519,234498],[3637118219,212659],[4031536710,208992],[3912718397,223297],[2941543334,202577],[2746166496,210410],[1702415223,213288],[1991543514,210166],[3933080085,210556],[3699799539,210145],[3781350335,210175]],"completions":19,"idle":[[46875,206660],[9202,208274],[10790,204304],[9369,202101],[10735,210053],[8956,206553],[9978,208848],[10615,210078],[11116,208944],[9593,203538]]},{"busy":[[9893441627,203342],[11746983674,205461],[12479291809,210157],[12098821218,210832],[11674297642,210674],[12060319184,211857],[11895809199,206225],[12603822745,208585],[11540573609,205333],[12806614994,210204],[12534063136,204683],[12088803304,210635],[12580357210,211581],[12591025636,200726]],"completions":72,"idle":[[18137,210065],[2952,201664],[3140,210074],[2891,210068],[2806,210076],[3237,202142],[3143,210064],[2941,210066],[3215,210079],[3403,210072]]},{"busy":[[9298035359,208889],[11952168334,214827],[12002273595,209891],[12861151367,203466],[11482896527,205826],[8936575710,201026],[8657049207,207998],[9521113428,210193],[9906604286,210072],[9188078791,210125],[10888069685,200859],[9478883241,203634],[9921938031,202004],[10374753267,201896]],"completions":64,"idle":[[40504,210053],[7883,201131],[8166,201108],[8325,208636],[8540,210069],[9432,200569],[7950,201472],[7499,208391],[37699,210060],[3119,210053]]}]},{"capability":"wai.video.av1","decoder":"platform:media-library","path":"software","profile":"macos-task","rounds":[{"busy":[[712777525,200655],[808774750,217127],[765554132,213546],[998412840,212207],[1181415829,210158],[965328022,215279],[1131296575,215977],[848873940,209028],[1035115113,206768],[1002485610,211146],[930752337,207119],[990468921,206168],[972004370,218262],[796649207,207315]],"completions":18,"idle":[[44737,203673],[10255,210075],[9229,204396],[9354,203065],[10283,203047],[19862,210078],[49166,200207],[11419,204280],[9263,209100],[9288,208568]]},{"busy":[[2668886009,212479],[1594342640,207978],[954416469,207991],[1028989841,205729],[1088547260,213675],[618400811,210190],[955631022,215924],[754925533,215630],[650316227,202951],[711146120,213612],[1609366863,213300],[1289658203,208109],[1443714771,209145],[774242043,222786]],"completions":19,"idle":[[46262,201497],[10033,201552],[9678,204850],[9255,206163],[9301,210096],[12728,210084],[11134,200734],[9442,212393],[9968,200981],[44936,210634]]},{"busy":[[2816615093,218128],[2304971594,211362],[2206513333,208450],[1395570679,221182],[1544188678,209504],[1719827288,219876],[2353065681,211507],[2654402720,212120],[1536918027,203845],[1478320748,200849],[1866321500,237268],[1263995373,223963],[1711710385,209864],[992174083,210754]],"completions":34,"idle":[[42986,201205],[8581,203407],[8705,204623],[8916,208744],[9096,203435],[7806,201125],[9063,210064],[9148,202738],[8648,201327],[11536,208938]]},{"busy":[[1744508392,235106],[1476237554,230263],[1122090620,210344],[841460078,245503],[899059122,210275],[452190564,228606],[811930703,207168],[757579725,209201],[918339673,216688],[947201173,227183],[1543948783,215364],[762247368,210217],[905669353,220015],[1205688775,233708]],"completions":17,"idle":[[44313,205601],[16926,201925],[14575,200768],[10965,200208],[9832,200693],[10749,207381],[11077,200990],[8510,208516],[8388,205460],[10762,210068]]},{"busy":[[4864142191,210099],[5633880730,203034],[5689337819,210106],[5487977487,208074],[5770103830,203293],[6023762060,210102],[5544745049,200354],[5781756717,203531],[5926954357,210098],[5960241075,210107],[5852543826,210116],[5875418021,210113],[5881108633,207782],[5859351307,210112]],"completions":104,"idle":[[19336,210067],[2939,210057],[2595,210051],[2639,204511],[3156,200470],[9556,203072],[47522,201831],[3386,204033],[3218,204101],[9087,204880]]},{"busy":[[4016849195,208054],[5048066999,206389],[5813212759,210095],[5923776090,210099],[5589542084,207734],[5835396118,210113],[5769688124,206467],[6015030371,210112],[5876987570,210099],[5736225756,203728],[5932757886,210090],[5662101656,200541],[5890292181,210112],[5731732419,200566]],"completions":104,"idle":[[52116,203365],[10689,210070],[9985,210065],[9138,205798],[8894,209788],[8776,205939],[8749,210047],[7983,204855],[9352,207028],[9550,210057]]},{"busy":[[4876316308,210108],[6065237231,210103],[6008900018,210098],[6042552961,210091],[5873205903,210109],[5534612977,200103],[5599892381,201177],[5672973366,200136],[5785127335,206484],[5609307103,201477],[5666566305,201273],[5781569110,200345],[5810218803,203985],[5956825330,210090]],"completions":104,"idle":[[19629,205012],[3275,202084],[3105,202744],[3085,204785],[3075,210072],[3418,202080],[3277,206013],[2971,208240],[45765,210052],[7672,210067]]}]},{"capability":"wai.video.av1.lossless","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[]},{"capability":"wai.video.av2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.avc","decoder":"platform:media-framework","path":"hardware","profile":"macos-gpu","rounds":[]},{"capability":"wai.video.avc","decoder":"platform:media-framework","path":"hardware","profile":"macos-task","rounds":[{"busy":[[1004518366,204559],[170138101,204910],[70224026,215452],[585204217,213743],[75466748,202410],[89809234,214648],[92148917,211645],[245756716,210930],[659311604,208575],[564775041,207104],[245186185,211882],[240541572,218602],[547691931,203270],[120613289,217518]],"completions":8,"idle":[[50572,210072],[10327,207084],[10482,210068],[9964,205462],[9222,200636],[13619,210076],[9626,200107],[10458,201954],[9142,204154],[10478,200683]]},{"busy":[[1097199063,210841],[174759285,213094],[449578381,207827],[62773216,205193],[39348498,214435],[51483322,215046],[53920699,218798],[59328970,211968],[83112694,213550],[216734607,202206],[634114524,216914],[486758077,209731],[255330832,213154],[138648968,212318]],"completions":8,"idle":[[18410,208910],[10509,210073],[11128,207127],[9455,201249],[10554,210068],[10769,204480],[10152,210065],[10861,209670],[8434,200081],[9850,210063]]},{"busy":[[1202490180,206260],[391759237,212648],[407409497,205144],[147383919,213569],[468632734,209145],[983776420,208594],[466224016,217121],[615696654,208393],[165087989,206510],[141974036,210853],[1000158714,215263],[760908089,209546],[584767775,207987],[350149681,209863]],"completions":16,"idle":[[43380,205408],[9816,205570],[11364,204270],[9837,202731],[10052,202790],[10418,200184],[9118,203313],[7659,208346],[10066,201732],[10120,210078]]},{"busy":[[1061557357,217261],[105823172,213037],[647870001,214171],[131918169,212875],[143434076,213039],[364520888,211916],[1120596417,212452],[351766455,210199],[433343670,211617],[381637547,210179],[127058897,201245],[331291867,208817],[1065050360,210127],[544092139,201027]],"completions":16,"idle":[[42817,200903],[9675,204389],[11099,201013],[8662,208935],[9578,202124],[11314,200597],[46115,203109],[9364,203854],[12156,202289],[10973,204237]]},{"busy":[[1245457932,204520],[785062095,207619],[1761499011,202411],[792781530,210732],[345589456,201308],[1522242857,204858],[794706299,211097],[499507851,207205],[1275305908,208718],[738630277,201899],[797057622,203960],[1193805257,208937],[879581205,200830],[1265635238,207391]],"completions":37,"idle":[[19887,203125],[8289,207745],[9255,202663],[9602,210074],[8272,203613],[10112,210078],[11335,202217],[12111,210327],[10096,204727],[10172,203174]]},{"busy":[[1040201652,210051],[176126590,201464],[554461978,210739],[146490606,210174],[302984346,203654],[1219405472,212985],[272379459,208816],[432069701,203986],[334073239,203763],[180170269,203188],[1196221259,214605],[536085441,208195],[547540144,210137],[332558012,207260]],"completions":16,"idle":[[49613,200206],[12254,201806],[11574,205203],[10720,201276],[9863,202886],[10198,208865],[11142,202780],[9210,201147],[10147,201139],[10081,210073]]},{"busy":[[1461755789,200325],[1872286639,210060],[1480536642,210109],[2219981262,210076],[1925209766,209843],[2003434592,210085],[1742460256,205811],[2250373628,210082],[1695831203,210093],[1898037384,205395],[1825552882,210077],[1673706158,201187],[1701255008,200512],[1837633784,208953]],"completions":73,"idle":[[41278,210061],[3373,210054],[4351,205575],[8306,210070],[3014,201079],[9747,204538],[5162,210058],[9367,210080],[3498,210080],[3557,207928]]}]},{"capability":"wai.video.avc","decoder":"platform:media-library","path":"software","profile":"macos-task","rounds":[{"busy":[[1179032531,202120],[1355100761,220293],[1686372034,249137],[1327895601,211566],[1299270403,214232],[699176833,213191],[1199306924,210177],[1757497465,247480],[1188957239,212209],[1034796207,217827],[1040210193,227412],[1074859389,219589],[1283353721,233220],[1020101229,231302]],"completions":17,"idle":[[41684,200695],[10956,204251],[10270,202468],[52811,210084],[12165,205447],[12292,208341],[9917,207463],[9360,210072],[9758,203884],[11285,201489]]},{"busy":[[1138554632,206884],[1270278849,211551],[1624272553,212229],[1881335451,215116],[1130207638,206135],[925495165,207860],[1424464271,211953],[1689397512,218527],[1173940950,208581],[1018935839,215741],[1237757688,215851],[1595858216,215389],[1767907426,208923],[1552519480,218578]],"completions":22,"idle":[[51642,210053],[9407,210100],[10170,201043],[8854,205952],[10149,201445],[10203,204599],[11268,204564],[8738,210076],[12147,210087],[9729,210064]]},{"busy":[[2746607097,210363],[2671264434,230029],[2229936092,210813],[3271880719,204057],[1929880654,204744],[2037843834,201011],[2292406248,224273],[1638466907,208951],[1993057283,213001],[1808816781,213634],[2277983834,212109],[2536591248,213085],[2506932656,213624],[1981831657,209212]],"completions":36,"idle":[[45319,202944],[8733,202335],[9777,201571],[9935,200687],[9858,207514],[10654,201106],[8758,209193],[10375,200398],[9903,202125],[9246,209956]]},{"busy":[[1980818144,201115],[1197387496,219541],[1469003910,223393],[1684506568,210840],[1212738705,209769],[1247923355,227066],[1191723333,217079],[1415908372,213003],[1447920649,226578],[1556312335,205858],[1631792020,217520],[1949610737,210140],[1334675553,209414],[2742860744,216256]],"completions":24,"idle":[[44701,205282],[44273,204790],[9299,210068],[9733,200555],[10465,200149],[8452,207419],[8831,207249],[9571,206581],[9461,200245],[9264,205039]]},{"busy":[[2454882569,207738],[6562568176,204887],[6835877064,210097],[6599478949,210087],[6520756468,210094],[5297361603,204207],[5542401500,208924],[5360131064,210159],[6919660020,214120],[6632902589,209654],[6939561664,210082],[6241007208,209850],[6197259125,210090],[6820146635,205089]],"completions":92,"idle":[[45412,205136],[11627,210066],[10141,200410],[8935,207851],[13597,201949],[9495,204562],[8393,200250],[7925,201637],[8214,203421],[8454,200091]]},{"busy":[[4759834870,209108],[5185861051,202313],[5243839296,203113],[5709618701,206128],[5614727358,203107],[5236780198,201509],[5032346972,210145],[4953280609,203904],[5372128845,212405],[3515905381,211671],[3274179783,211909],[3259123048,206447],[2580182327,207874],[2326724904,218474]],"completions":72,"idle":[[22655,200570],[10456,203848],[9110,201383],[9623,200824],[9490,205501],[10053,205208],[9531,201457],[8418,206497],[8891,209254],[9003,202379]]},{"busy":[[5533665295,200124],[6999371768,203867],[7259702544,208575],[7098060851,210087],[6954254587,201280],[7138365039,210093],[6933736233,203683],[6983150390,202697],[7160172737,210094],[7017170849,205553],[7142632070,207066],[6986938667,205564],[6899300783,201714],[6871048896,202108]],"completions":104,"idle":[[14444,210079],[3535,202198],[3883,200139],[4303,210065],[3489,207822],[3313,210065],[2938,200084],[3143,204705],[3466,201467],[7829,202009]]}]},{"capability":"wai.video.avs3","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.evc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.evc.baseline","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.hevc","decoder":"platform:media-framework","path":"hardware","profile":"macos-gpu","rounds":[]},{"capability":"wai.video.hevc","decoder":"platform:media-framework","path":"hardware","profile":"macos-task","rounds":[{"busy":[[564220575,209408],[365503,200829],[902353287,210098],[407469531,213904],[571600600,273600],[176264354,204304],[96114782,207534],[90420924,216493],[314613107,216059],[197255616,214713],[115890414,223037],[458417857,207041],[299461213,205308],[180381027,205320]],"completions":8,"idle":[[52983,200512],[10435,210061],[9943,201832],[10701,202086],[10577,208622],[10350,203822],[46297,202286],[10882,207434],[10967,206943],[10410,210080]]},{"busy":[[972445824,214841],[85585203,200795],[499451324,210115],[220061450,204664],[100295018,211719],[102980111,208136],[122382116,222270],[407683642,248407],[272580596,232683],[377135596,201006],[275027547,219072],[193740966,214716],[127364333,212601],[87368748,205432]],"completions":8,"idle":[[39823,205495],[10028,209746],[8912,203540],[10472,209098],[10723,210080],[12341,215677],[9711,206918],[10088,203027],[9635,201003],[10389,200187]]},{"busy":[[833244693,203263],[379637675,225414],[63573343,212260],[752945084,208078],[258801078,208798],[1103775247,209496],[583256420,211598],[643039216,202748],[266484696,204261],[319373691,208044],[1490617330,206072],[792170419,203350],[555486582,209623],[1470227414,209527]],"completions":24,"idle":[[67653,200134],[10901,200131],[8640,209675],[11137,205047],[10088,204064],[10235,205972],[8000,204187],[10958,210060],[8893,208103],[9692,205143]]},{"busy":[[917283038,210464],[250177623,211945],[507658146,208323],[162842145,214118],[116163189,213986],[240137139,204827],[865750483,211752],[403229730,205923],[267579089,213367],[514924757,211790],[203475365,210156],[126178169,203749],[207065556,215199],[571288116,201872]],"completions":15,"idle":[[43845,204870],[9973,207976],[8413,205611],[9663,210067],[9427,203792],[8835,209362],[8552,205709],[8837,207546],[8975,203624],[10615,206907]]},{"busy":[[961038561,206839],[1143266276,210057],[1582239123,210652],[1350108293,202217],[1345253205,205393],[1270684237,211268],[1287709924,202867],[1195767706,210134],[1254005298,208227],[1004185913,201777],[1071111373,200473],[810503871,208836],[1398882118,210110],[1083217159,205316]],"completions":48,"idle":[[18836,205208],[44963,210077],[3448,202462],[3124,202148],[3325,203291],[3345,210076],[3207,202213],[3081,210077],[3303,208856],[8978,201043]]},{"busy":[[1843230298,201214],[1764237862,201363],[1143411272,210113],[2072416173,209272],[1342464000,210084],[2005369299,207812],[2119359455,205197],[1434401521,202281],[1423864274,210153],[1911638113,202041],[1121242797,206637],[1886148260,210215],[1610753661,203877],[1356693053,200165]],"completions":64,"idle":[[197019,201726],[9441,205727],[8114,201185],[9209,207819],[9659,208913],[10129,208776],[10291,202416],[8144,204562],[8631,210055],[8569,203818]]},{"busy":[[1440008116,209536],[1982793455,201116],[1005104036,210081],[2118121908,203370],[1970806760,209831],[1294799897,210095],[2032695947,202860],[2196614278,210076],[1885982929,210069],[1775961923,203705],[1729300087,201782],[1668087635,200466],[1852158538,200286],[1772333822,200112]],"completions":72,"idle":[[24015,210065],[3261,203163],[3239,200503],[11242,203676],[8902,203245],[9398,202729],[8686,200294],[4322,207640],[2960,208308],[2948,210072]]}]},{"capability":"wai.video.hevc","decoder":"platform:media-library","path":"software","profile":"macos-task","rounds":[{"busy":[[1702601915,211437],[1237783658,212634],[1175089995,219760],[1484824057,232318],[1667857012,231175],[1467605453,219852],[1018260113,215329],[1241387846,209776],[1879148410,212777],[1482433694,201774],[1304743075,213343],[953480873,216954],[1300625834,213611],[780961917,211884]],"completions":16,"idle":[[8412,201998],[9057,202129],[10692,210061],[8624,207783],[9658,210068],[10367,210070],[10028,200831],[8846,206616],[8749,209061],[9319,203821]]},{"busy":[[1257771335,209605],[1451519209,220559],[1372670826,215030],[1557365667,227808],[1315401903,215850],[940909623,217518],[674052828,210131],[1121528676,204455],[1296451782,208133],[1555826245,209007],[929448207,202346],[1566477417,208896],[1358896267,211243],[762096215,213112]],"completions":16,"idle":[[43752,202890],[10839,210069],[8637,204583],[9534,210063],[9731,205374],[9468,210079],[10483,200666],[9788,210069],[10609,204796],[12117,204389]]},{"busy":[[2697475927,206664],[1371387438,214874],[3236755355,207386],[1958614949,213703],[1719153661,207247],[2819194364,217022],[2691055183,214827],[2954300378,210888],[3325179587,218961],[2391082314,213019],[1601238573,214672],[2281928844,212594],[2497349040,211880],[2217072661,213551]],"completions":30,"idle":[[44443,207417],[8821,201820],[8339,203314],[9587,207511],[9642,204597],[9202,208797],[9309,208028],[10536,204002],[8995,204355],[9145,207135]]},{"busy":[[3569052034,223043],[2253626218,205962],[2676021032,210321],[1800895758,204926],[2995539617,209178],[2160025529,201998],[2360593353,215176],[2042839934,210633],[2273289677,206661],[3014159806,209716],[2649376482,221135],[2914823377,207718],[2066803288,204473],[3784956185,210100]],"completions":32,"idle":[[29133,203456],[8307,203822],[9470,203592],[11132,207036],[8562,203246],[9677,203262],[10945,202237],[8455,201813],[9667,200495],[11811,200857]]},{"busy":[[4887689996,201950],[6403915126,210094],[6299709686,204831],[6017711103,206275],[6311755796,201520],[6250202440,202690],[6654212417,210102],[6346519867,206638],[6282025133,204356],[6063914404,210158],[5655452273,200917],[6404629324,204967],[6191678375,203616],[6193954610,202431]],"completions":72,"idle":[[19234,210070],[3126,200248],[3449,210061],[2898,206552],[3272,202324],[50955,210081],[4082,207892],[4585,202108],[3323,210066],[3308,204167]]},{"busy":[[4553434147,208601],[4734145127,209298],[4389578494,204269],[4327344003,211920],[3754073796,209447],[3720066580,202971],[3998919701,211518],[3474793240,209599],[3160244271,211657],[3681739389,202497],[4126665629,211009],[4153064683,200454],[3730475678,213403],[4326307085,208406]],"completions":53,"idle":[[46113,207433],[9763,205480],[10994,210070],[11103,203972],[8882,200680],[16575,203359],[7527,203012],[11222,202096],[11372,209436],[9466,205450]]},{"busy":[[4804911873,204690],[6453420747,210099],[6597293541,210086],[6347739395,200429],[6354954315,204311],[6494707267,204805],[6374011305,200126],[6144987587,210107],[6439027151,209976],[6491500312,210109],[6775216507,210092],[6520178254,208864],[6543339023,210103],[6388259556,200224]],"completions":80,"idle":[[47455,204518],[9409,201483],[8391,200128],[9859,209397],[10807,201975],[5497,208404],[8741,200126],[9757,201799],[45007,210081],[3835,206090]]}]},{"capability":"wai.video.int_hyper","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[]},{"capability":"wai.video.int_motion","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[{"busy":[[3162282442,204867],[2942708828,213734],[3441252840,209541],[3343124107,211304],[3470070953,216343],[3132646681,204569],[3108359122,211377],[2862156843,218962],[2956295313,210054],[3195396957,219486],[3152440927,216255],[3140718554,227558],[2964722565,216342],[2566492642,213459]],"completions":4086,"idle":[[48650,210059],[9139,204160],[9274,210058],[9407,200872],[9180,204644],[9564,203137],[10068,210060],[8394,202651],[9759,208717],[8504,201357]]},{"busy":[[2006313440,207498],[888284953,213307],[1175742415,212959],[1467691133,208830],[1790028436,213889],[2030561542,225230],[1554242995,206872],[1415976553,217382],[880102667,210896],[1115938995,204871],[1220901439,224723],[1010569340,202856],[1638641206,209409],[1687195160,205640]],"completions":1849,"idle":[[46262,200802],[9745,203044],[9907,206007],[8360,202938],[8134,210155],[9129,200765],[8459,206295],[9505,210030],[9112,200979],[11837,203244]]},{"busy":[[2861563745,205462],[2893028022,207584],[2737632228,210038],[2592675713,211264],[2672406839,210047],[2430225120,203276],[2690974829,200931],[2905444091,213733],[2636089547,201447],[3185963778,210589],[2857773047,202546],[3090111260,207069],[3268012696,208934],[2099070564,208352]],"completions":3620,"idle":[[44623,203594],[9172,200626],[9722,204799],[9720,207790],[9440,208776],[9104,204740],[10163,200161],[9081,208705],[9559,201260],[8584,202571]]},{"busy":[[2788333840,209437],[3099320923,202210],[3265381921,209468],[3009950601,200912],[3040292140,206501],[2960652338,209306],[2641115253,204626],[3557667666,210042],[2928876092,204829],[3852951997,201194],[3396931514,201801],[3664996136,202470],[3947742746,206741],[3479067411,209230]],"completions":4268,"idle":[[53936,208568],[8546,204133],[9924,210534],[10702,203729],[11118,209291],[11149,206847],[11043,208683],[10461,202324],[8391,207884],[10019,201725]]},{"busy":[[3778895008,203955],[4748785176,202270],[4924958862,210039],[4788783205,208659],[4890401307,210032],[4860199659,208520],[4738805572,201052],[4849802882,210037],[4932072504,210036],[4905412833,210039],[4893830694,210041],[4869891667,210036],[4686261731,200584],[4651374165,200746]],"completions":5850,"idle":[[16696,210063],[2817,210062],[2473,204524],[2519,210052],[2491,210055],[2504,210065],[2849,210049],[46554,210075],[3553,205935],[3498,201448]]},{"busy":[[2737216421,201947],[2396790382,206084],[1926640467,217266],[1976638230,210048],[1313070369,211401],[1282577607,201509],[1342781089,201388],[899949647,204278],[1422399741,215529],[1017903603,210053],[1254904115,206361],[2295436855,210049],[1308931272,208010],[1625454443,216500]],"completions":2234,"idle":[[44077,208132],[10023,204745],[8751,203845],[9810,200061],[8422,200167],[36112,203041],[45320,206277],[9215,202951],[8735,201672],[10888,200486]]},{"busy":[[3907144844,210053],[4855170688,210055],[4857733354,210063],[4689901508,202132],[4850448293,210044],[4785129100,204897],[4926793223,210056],[4855770896,207001],[4905252717,209054],[4961823092,210052],[4893758531,210049],[5010182116,210046],[4957269204,210037],[4921722113,207822]],"completions":5964,"idle":[[18472,205522],[3110,210066],[3027,210069],[3068,201301],[3023,204672],[3790,204532],[3228,202874],[3100,210067],[3213,210071],[2949,210059]]}]},{"capability":"wai.video.j2k","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.vp8","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.vp9","decoder":"platform:media-framework","path":"hardware","profile":"macos-gpu","rounds":[]},{"capability":"wai.video.vp9","decoder":"platform:media-framework","path":"hardware","profile":"macos-task","rounds":[{"busy":[[1166272322,205230],[130227898,203340],[288612900,206008],[411105295,209381],[105509092,205368],[99031625,215759],[114525767,210874],[525747069,210770],[883324652,212256],[419119170,207686],[115683373,203963],[358272632,211870],[161132236,215834],[304323317,210168]],"completions":8,"idle":[[47697,204357],[11406,201485],[9485,205139],[9975,203791],[11107,210073],[11546,209616],[9573,206485],[44618,201972],[9921,204037],[10104,205524]]},{"busy":[[836592501,204845],[342660082,213200],[41836588,203671],[605470932,208719],[108288142,206576],[114382983,223886],[100847544,214830],[153763257,208050],[278742965,203744],[706018333,217770],[598029819,213367],[154695227,201000],[179006838,210644],[162144639,205144]],"completions":8,"idle":[[42904,208676],[9342,201105],[8261,200416],[9661,207733],[9925,201046],[10223,209515],[10610,204375],[8885,206382],[8225,200226],[9089,206794]]},{"busy":[[1130245802,202848],[657302445,209461],[366019672,201994],[68390326,210394],[64183961,215133],[68768576,211832],[847253472,205015],[1056968018,209923],[732704059,207849],[208945179,209206],[133216153,208555],[932044452,207612],[769242659,216069],[266171269,210800]],"completions":16,"idle":[[19128,203807],[8902,210164],[8063,204714],[25607,208934],[33526,204315],[12673,205465],[9120,202247],[8322,205667],[14608,201822],[5416,202827]]},{"busy":[[1178233366,201087],[636724773,203478],[206658112,204067],[149587920,206996],[674098037,210820],[1072287622,216895],[472461578,209864],[477198028,205568],[213797008,207457],[1085407813,210116],[860144871,205668],[680082808,210111],[232315902,211697],[999498893,201309]],"completions":22,"idle":[[44613,202413],[10014,202180],[8980,202385],[10022,203330],[9655,205602],[9767,208876],[9175,203310],[9294,206650],[10194,203020],[9356,203185]]},{"busy":[[1540957787,210077],[390472917,210115],[2191881331,206318],[832897232,203696],[1981943030,204497],[1445234622,210096],[1868495511,210078],[1463786446,202744],[2041558707,207554],[1057001555,210083],[2090203989,210080],[1271848597,201263],[2119120571,210079],[1057897330,210093]],"completions":57,"idle":[[45727,202268],[8678,210069],[3669,210069],[10128,203925],[7925,210050],[8582,201244],[9593,210060],[9050,210057],[8932,210067],[2999,210071]]},{"busy":[[1673412118,209750],[369965110,203918],[2010185300,202475],[762352437,210102],[2011860763,210071],[729287071,210083],[2024058999,210071],[770803859,210094],[1978518023,203673],[798525605,210106],[1707548143,200175],[968546771,202404],[681000702,210090],[1832501926,202326]],"completions":48,"idle":[[21956,206347],[3100,204498],[3243,210056],[4517,205501],[3529,210060],[6177,210048],[9005,204595],[5155,210069],[3015,200749],[4643,204255]]},{"busy":[[1453470632,206109],[615932974,203507],[1880701903,205848],[766470033,201823],[1996157357,205054],[729425331,201090],[1328261970,200182],[1446225533,200871],[482845375,200973],[2265106145,200096],[413148040,209234],[2266911191,201556],[460788469,200406],[2071156705,201148]],"completions":48,"idle":[[23215,200751],[2981,208786],[2827,202171],[3652,210067],[3038,200805],[4633,208112],[3290,210072],[2899,208098],[2703,207977],[2503,200069]]}]},{"capability":"wai.video.vp9","decoder":"platform:media-library","path":"software","profile":"macos-task","rounds":[{"busy":[[499486156,207238],[95329,203545],[655901832,202830],[1119795497,216722],[1346758847,213230],[1593426005,204535],[1450348157,210633],[1359717362,201751],[1491144960,206347],[1392396079,202818],[1490822318,202535],[1806040069,206891],[1959055843,209233],[1618130645,207985]],"completions":23,"idle":[[61894,206697],[9766,209454],[9521,203898],[8548,210062],[10103,202358],[10404,203000],[10252,203109],[9978,201863],[10184,208902],[10027,206685]]},{"busy":[[1936198975,221398],[1811351491,202870],[1428902673,205105],[1400252616,214762],[1495710621,203257],[1186319628,209559],[1442757247,217766],[1430267349,212413],[1228495179,221873],[1669747148,202849],[1854383359,217733],[1715170419,211844],[1304169479,215772],[1270718362,216989]],"completions":29,"idle":[[27799,202564],[8676,207117],[9098,201876],[9025,200942],[9315,204684],[11214,208749],[9978,201469],[7815,209657],[9890,207045],[10277,200958]]},{"busy":[[2471495737,201480],[2912504822,208248],[2252803595,209831],[1993770199,208993],[2048422439,212848],[1409716766,210215],[2232862743,211489],[2339772047,204549],[1987192703,202575],[1649401140,209780],[1544151030,210358],[1342815550,204826],[1336139780,208936],[1394998978,210085]],"completions":40,"idle":[[43101,208761],[10110,210177],[8931,204111],[8647,201967],[8561,202933],[16999,206780],[4867,203467],[9267,208394],[14857,207639],[4625,201034]]},{"busy":[[2054941171,208027],[2462414489,201419],[2286106617,210155],[3908692511,202561],[3725707289,212942],[1982222637,208413],[2007145013,205379],[1724966951,210077],[2544114985,211903],[1797810223,206140],[1111470943,209550],[1319509824,211822],[1612818069,219989],[1149938739,216198]],"completions":44,"idle":[[62922,209011],[9868,205901],[9338,205810],[9665,204493],[9138,203943],[9897,202674],[9037,203983],[49361,210054],[9893,204202],[11630,204790]]},{"busy":[[4689449500,207640],[5409056750,203014],[5634342887,204891],[5798295876,206158],[5739925615,205195],[5889729023,200651],[6091921467,210101],[6249751042,210091],[5675901721,210160],[4603538498,200349],[5405463496,201155],[5854151499,210095],[5990725760,210156],[5829500808,207479]],"completions":112,"idle":[[45425,201114],[11469,208107],[9343,210062],[8468,210067],[3240,210058],[10181,210066],[2865,210061],[9920,204816],[4115,201559],[3417,200689]]},{"busy":[[4208029091,200607],[5894442896,205810],[5585814268,210156],[5188660628,202148],[5877965534,204857],[6274355388,210103],[5212031836,200855],[6109533173,207971],[5366296340,210122],[5054337092,204743],[5722436455,200204],[6131257455,210137],[5654426097,206474],[5808561191,210181]],"completions":112,"idle":[[37895,210066],[10191,204589],[9366,202979],[7894,202208],[8950,210062],[10227,200667],[56788,210068],[7912,209699],[8711,210054],[9013,200496]]},{"busy":[[4336446254,202363],[5228946061,202276],[5247732519,203369],[5358559282,200389],[5247462868,206772],[5229260017,204225],[5618776961,208080],[5820390879,207768],[5429094946,205133],[5574181121,210184],[5255766089,202046],[5403348088,200374],[5282198686,210174],[5359955149,202459]],"completions":104,"idle":[[18550,201670],[10030,205506],[3056,205386],[3136,210069],[3049,209402],[3130,200763],[3170,201420],[2836,201138],[2951,209141],[3735,210065]]}]},{"capability":"wai.video.vvc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.live","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.model","decoder":"builtin:wai@0.3.0","path":"software","profile":"macos-task","rounds":[{"busy":[[1498821549,216372],[403311090,220809],[226180720,217260],[324659193,222544],[216946551,203711],[243823649,213523],[240940073,206761],[239643369,217054],[234816483,211344],[229808670,226329],[198505467,215628],[179430579,212555],[198436666,214809],[206604536,216262]],"completions":201,"idle":[[46409,210077],[11383,201657],[8859,202456],[11152,204306],[12994,208280],[7570,200472],[10667,210071],[10514,210078],[11599,210072],[9862,207673]]},{"busy":[[1208391597,210189],[557246911,209765],[453795347,204762],[493440010,221615],[416337383,210715],[399237560,214763],[546121876,210045],[388636152,211347],[664571233,208164],[656030880,206350],[685588932,213085],[664827805,204699],[708557499,206745],[775719242,204588]],"completions":310,"idle":[[24309,201381],[10209,205507],[9778,209388],[11181,202330],[8359,208973],[10900,210076],[10267,207388],[10994,210072],[10256,206676],[12009,204895]]},{"busy":[[1840201522,200925],[791216598,205435],[607114343,203080],[782535608,209242],[1037191543,207624],[1084385191,214699],[1145719209,203725],[966776999,206412],[1113509230,211250],[861238634,205283],[1119714705,203035],[1316336215,204947],[1192787906,201186],[885617378,209293]],"completions":498,"idle":[[57534,206669],[10076,201547],[8749,200742],[8948,203147],[9233,204284],[10049,201020],[10853,203264],[10122,210056],[10962,200912],[10437,209989]]},{"busy":[[2750118803,207424],[2768623215,203539],[2769138406,209337],[2332414292,202256],[1796161643,209935],[1785920370,209729],[1394379036,202687],[2171763730,203700],[2251959742,209242],[2254310082,210998],[2433837965,205132],[2730111319,206790],[2000788384,202447],[2571438798,210053]],"completions":1099,"idle":[[39004,201839],[8729,200571],[9543,208552],[13150,201641],[5201,200067],[8724,202186],[8880,207131],[9608,202486],[41312,207351],[8754,210157]]},{"busy":[[2660054022,201674],[1441956336,210818],[1408033456,213276],[1441617475,210303],[1068577781,206563],[1261174036,208139],[1625068768,210700],[1839291668,206091],[2769512338,204213],[3437465196,202938],[4384524446,209426],[4043283742,206741],[4482076582,210074],[3405924532,201039]],"completions":1207,"idle":[[44991,201500],[8876,208581],[11300,209666],[8628,201808],[9567,200416],[9348,203836],[10177,204752],[8707,206778],[8504,205989],[10438,204784]]},{"busy":[[1938722490,207206],[2083188682,217565],[1625451387,206207],[2115096796,210715],[1568781644,214281],[1235486496,205743],[1806265586,205843],[2306869055,213006],[2093441554,211252],[2407382658,206864],[2999971526,210867],[3004284122,206491],[2610586193,204619],[3706265576,209737]],"completions":1094,"idle":[[45298,205599],[9353,207826],[9528,208570],[10573,204434],[11465,208373],[11578,202078],[10360,200087],[9494,203493],[10508,201958],[9776,201649]]},{"busy":[[6893069936,210046],[7851973895,203096],[8280405366,210043],[8252840602,210048],[8273456477,210045],[8255719746,210039],[8297457574,210039],[8299629464,210043],[8420044875,210044],[8341117863,210040],[8280379132,210034],[7957022860,202842],[7910455736,200570],[8247835002,210042]],"completions":3321,"idle":[[18095,200750],[3051,210059],[2711,201675],[2965,200412],[3460,209004],[2826,209887],[2904,210063],[2651,206778],[2700,210070],[3056,210072]]}]},{"capability":"wai.world.replay","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.scene","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]}],"wai_sink_readings":1} \ No newline at end of file diff --git a/wai/live-conformance/sink-energy/tables/macos-arm64/table.json b/wai/live-conformance/sink-energy/tables/macos-arm64/table.json new file mode 100644 index 000000000..8cd775fe1 --- /dev/null +++ b/wai/live-conformance/sink-energy/tables/macos-arm64/table.json @@ -0,0 +1,5031 @@ +{ + "wai_sink_table": 1, + "signer": "5f8823017ac5f259575e1c43369a132cd44159e64c3a6dcdda69bb6b3b782c96", + "procedure": "wai-sink-table/1", + "procedure_sha256": "f7434a76fbf4c839910e365d7a9a77e63cb3889124735471cb58e687e3116d94", + "readings_sha256": "8add0bfed3ec41d5f0d2f37e2885975b7a1b4ee3df83f0d9b4dbca04261c563d", + "machine": { + "os": "macos 27.0.1", + "arch": "aarch64", + "processor": "aarch64 processor, 32 logical cores (24 Performance + 8 Efficiency)", + "cores": 32, + "memory_gib": 256 + }, + "build": { + "profile": "release", + "toolchain": "rustc 1.98.0 (88d9e12ae 2026-08-18)", + "target": "aarch64-apple-darwin" + }, + "measured": "2026-10-22", + "rounds": 7, + "sample_us": 200000, + "counters": [ + { + "profile": "macos-gpu", + "status": "frozen", + "detail": "IOReport Energy Model / GPU Energy" + }, + { + "profile": "macos-task", + "status": "live", + "detail": "proc_pid_rusage RUSAGE_INFO_V6: ri_energy_nj + ri_billed_energy - ri_serviced_energy, this process and its children" + } + ], + "rows": [ + { + "capability": "wai.audio.aac", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.ac4", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.avs3", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.flac", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "content-ms", + "units": 10000, + "width": null, + "height": null, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 69, + "sd_uw": 55, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 14481757, + "sd_uw": 1787676, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2975977, + "completions": 2831 + }, + { + "idle": { + "n": 10, + "mean_uw": 85, + "sd_uw": 82, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 11186978, + "sd_uw": 3530983, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3011126, + "completions": 2242 + }, + { + "idle": { + "n": 10, + "mean_uw": 65, + "sd_uw": 61, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 13282130, + "sd_uw": 1752760, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2924811, + "completions": 2576 + }, + { + "idle": { + "n": 10, + "mean_uw": 64, + "sd_uw": 57, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 21190939, + "sd_uw": 1786371, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2916693, + "completions": 4065 + }, + { + "idle": { + "n": 10, + "mean_uw": 63, + "sd_uw": 73, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 27555424, + "sd_uw": 2030599, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2884103, + "completions": 4871 + }, + { + "idle": { + "n": 10, + "mean_uw": 67, + "sd_uw": 53, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 26427018, + "sd_uw": 2305757, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2876494, + "completions": 4806 + }, + { + "idle": { + "n": 10, + "mean_uw": 49, + "sd_uw": 61, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 28718966, + "sd_uw": 1752206, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2877431, + "completions": 4987 + } + ], + "status": "figure", + "reason": null, + "median": 0, + "range": [ + 14998, + 16557 + ], + "joules_micro": 15202, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 33002, + "window_us": 2975977 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.iamf", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.int_codec", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "content-ms", + "units": 512, + "width": null, + "height": null, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 67, + "sd_uw": 56, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2375018, + "sd_uw": 1459710, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3029273, + "completions": 1338 + }, + { + "idle": { + "n": 10, + "mean_uw": 84, + "sd_uw": 73, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3227587, + "sd_uw": 1275315, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2966269, + "completions": 1431 + }, + { + "idle": { + "n": 10, + "mean_uw": 65, + "sd_uw": 59, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5476155, + "sd_uw": 1359350, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2906360, + "completions": 2217 + }, + { + "idle": { + "n": 10, + "mean_uw": 62, + "sd_uw": 45, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 11777656, + "sd_uw": 1073777, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2921566, + "completions": 4816 + }, + { + "idle": { + "n": 10, + "mean_uw": 46, + "sd_uw": 64, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 25016067, + "sd_uw": 12213333, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2939491, + "completions": 9927 + }, + { + "idle": { + "n": 10, + "mean_uw": 89, + "sd_uw": 71, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 6204394, + "sd_uw": 3271984, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2909067, + "completions": 2669 + }, + { + "idle": { + "n": 10, + "mean_uw": 35, + "sd_uw": 63, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 33801425, + "sd_uw": 3455581, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2912078, + "completions": 12727 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 5361, + 7732 + ], + "joules_micro": 7139, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 95875, + "window_us": 2921566 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.ivas", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.mpegh", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.opus", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "content-ms", + "units": 10000, + "width": null, + "height": null, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 66, + "sd_uw": 53, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 15793303, + "sd_uw": 2857932, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2999623, + "completions": 1262 + }, + { + "idle": { + "n": 10, + "mean_uw": 57, + "sd_uw": 31, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 11099877, + "sd_uw": 1859932, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2944607, + "completions": 898 + }, + { + "idle": { + "n": 10, + "mean_uw": 83, + "sd_uw": 71, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 11135181, + "sd_uw": 1552017, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2957409, + "completions": 891 + }, + { + "idle": { + "n": 10, + "mean_uw": 61, + "sd_uw": 53, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 23719490, + "sd_uw": 2517269, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2909449, + "completions": 1813 + }, + { + "idle": { + "n": 10, + "mean_uw": 66, + "sd_uw": 62, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 28999436, + "sd_uw": 982258, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2901013, + "completions": 2066 + }, + { + "idle": { + "n": 10, + "mean_uw": 64, + "sd_uw": 51, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 21923094, + "sd_uw": 2056396, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2926975, + "completions": 1713 + }, + { + "idle": { + "n": 10, + "mean_uw": 53, + "sd_uw": 78, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 29241336, + "sd_uw": 1554252, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2919951, + "completions": 2108 + } + ], + "status": "figure", + "reason": null, + "median": 0, + "range": [ + 36236, + 40641 + ], + "joules_micro": 37420, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 48398, + "window_us": 2999623 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.opus.packet", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.scene", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.score", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.avatar.pose", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.feed.state", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.film.linear", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.film.reel", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.generate.spectral", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.haptic.mihs", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.haptic.signal", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.avif", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 73, + "sd_uw": 59, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 18075561, + "sd_uw": 2149591, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2991839, + "completions": 696 + }, + { + "idle": { + "n": 10, + "mean_uw": 88, + "sd_uw": 83, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 10920261, + "sd_uw": 1293845, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2967772, + "completions": 428 + }, + { + "idle": { + "n": 10, + "mean_uw": 82, + "sd_uw": 103, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 10574124, + "sd_uw": 1973515, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2948558, + "completions": 411 + }, + { + "idle": { + "n": 10, + "mean_uw": 63, + "sd_uw": 63, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 23204719, + "sd_uw": 3177553, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2877995, + "completions": 869 + }, + { + "idle": { + "n": 10, + "mean_uw": 62, + "sd_uw": 48, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 30223687, + "sd_uw": 1858979, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2892573, + "completions": 1066 + }, + { + "idle": { + "n": 10, + "mean_uw": 32, + "sd_uw": 30, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 32451320, + "sd_uw": 1798157, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2879227, + "completions": 1117 + }, + { + "idle": { + "n": 10, + "mean_uw": 77, + "sd_uw": 78, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 30948109, + "sd_uw": 1965432, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2890053, + "completions": 1071 + } + ], + "status": "figure", + "reason": null, + "median": 0, + "range": [ + 75020, + 83349 + ], + "joules_micro": 77256, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 31955, + "window_us": 2991839 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.image.jp2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.jpeg", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 65, + "sd_uw": 51, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 16557897, + "sd_uw": 2018332, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3050972, + "completions": 1425 + }, + { + "idle": { + "n": 10, + "mean_uw": 66, + "sd_uw": 56, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 11384718, + "sd_uw": 1094422, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2965286, + "completions": 969 + }, + { + "idle": { + "n": 10, + "mean_uw": 56, + "sd_uw": 22, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 11440460, + "sd_uw": 2174962, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2945555, + "completions": 949 + }, + { + "idle": { + "n": 10, + "mean_uw": 53, + "sd_uw": 36, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 23006077, + "sd_uw": 1621858, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2904131, + "completions": 1853 + }, + { + "idle": { + "n": 10, + "mean_uw": 67, + "sd_uw": 66, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 28528164, + "sd_uw": 2616606, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2859395, + "completions": 2164 + }, + { + "idle": { + "n": 10, + "mean_uw": 42, + "sd_uw": 56, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 33399576, + "sd_uw": 2079520, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2920798, + "completions": 2647 + }, + { + "idle": { + "n": 10, + "mean_uw": 76, + "sd_uw": 87, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 23735435, + "sd_uw": 5004491, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2907624, + "completions": 1909 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 34696, + 37626 + ], + "joules_micro": 35979, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 23509, + "window_us": 2904131 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.image.jpegai", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.jxl", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 66, + "sd_uw": 54, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 27472290, + "sd_uw": 2270070, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2982809, + "completions": 1133 + }, + { + "idle": { + "n": 10, + "mean_uw": 66, + "sd_uw": 53, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 16170421, + "sd_uw": 1821015, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2960294, + "completions": 673 + }, + { + "idle": { + "n": 10, + "mean_uw": 157, + "sd_uw": 333, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 12149463, + "sd_uw": 4350640, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2917622, + "completions": 499 + }, + { + "idle": { + "n": 10, + "mean_uw": 80, + "sd_uw": 75, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 27074418, + "sd_uw": 3762476, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2933837, + "completions": 1103 + }, + { + "idle": { + "n": 10, + "mean_uw": 65, + "sd_uw": 60, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 36506608, + "sd_uw": 3867034, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2885429, + "completions": 1417 + }, + { + "idle": { + "n": 10, + "mean_uw": 60, + "sd_uw": 55, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 39871041, + "sd_uw": 3805243, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2868025, + "completions": 1512 + }, + { + "idle": { + "n": 10, + "mean_uw": 73, + "sd_uw": 67, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 42474703, + "sd_uw": 1990974, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2884784, + "completions": 1621 + } + ], + "status": "figure", + "reason": null, + "median": 0, + "range": [ + 70471, + 75429 + ], + "joules_micro": 72071, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 22178, + "window_us": 2982809 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 69, + "sd_uw": 49, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 12536562, + "sd_uw": 4024083, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3105645, + "completions": 986 + }, + { + "idle": { + "n": 10, + "mean_uw": 68, + "sd_uw": 57, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 8502607, + "sd_uw": 1252590, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2995950, + "completions": 682 + }, + { + "idle": { + "n": 10, + "mean_uw": 82, + "sd_uw": 75, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 7700235, + "sd_uw": 2943539, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2986074, + "completions": 585 + }, + { + "idle": { + "n": 10, + "mean_uw": 60, + "sd_uw": 58, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 20031398, + "sd_uw": 1705013, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2929064, + "completions": 1487 + }, + { + "idle": { + "n": 10, + "mean_uw": 61, + "sd_uw": 51, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 25771192, + "sd_uw": 1623642, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2855550, + "completions": 1748 + }, + { + "idle": { + "n": 10, + "mean_uw": 51, + "sd_uw": 55, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 27822446, + "sd_uw": 1324371, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2933397, + "completions": 1888 + }, + { + "idle": { + "n": 10, + "mean_uw": 24, + "sd_uw": 24, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 23369169, + "sd_uw": 1994460, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2876477, + "completions": 1694 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 37133, + 43136 + ], + "joules_micro": 39351, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 44038, + "window_us": 2929064 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.mesh.draco", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.mesh.glb", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.bmshj2018", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.cmic", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.dac", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.dcvc_fm", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.dcvc_rt", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.elic", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.encodec32", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.glc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_hyper", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 16, + "height": 16, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 63, + "sd_uw": 57, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 21299765, + "sd_uw": 2328026, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2935652, + "completions": 247170 + }, + { + "idle": { + "n": 10, + "mean_uw": 54, + "sd_uw": 16, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 12042256, + "sd_uw": 1363721, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2940492, + "completions": 142010 + }, + { + "idle": { + "n": 10, + "mean_uw": 58, + "sd_uw": 37, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 21307629, + "sd_uw": 2101904, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2911048, + "completions": 245341 + }, + { + "idle": { + "n": 10, + "mean_uw": 91, + "sd_uw": 85, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 19358664, + "sd_uw": 2262865, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2892454, + "completions": 223027 + }, + { + "idle": { + "n": 10, + "mean_uw": 69, + "sd_uw": 81, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 33296095, + "sd_uw": 1820821, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2919204, + "completions": 358641 + }, + { + "idle": { + "n": 10, + "mean_uw": 81, + "sd_uw": 72, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 11884335, + "sd_uw": 3412517, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2925926, + "completions": 139687 + }, + { + "idle": { + "n": 10, + "mean_uw": 22, + "sd_uw": 25, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 33223415, + "sd_uw": 1953200, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2908136, + "completions": 358173 + } + ], + "status": "figure", + "reason": null, + "median": 0, + "range": [ + 249, + 271 + ], + "joules_micro": 253, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 29231, + "window_us": 2935652 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_synth", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 128, + "height": 128, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 67, + "sd_uw": 51, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5978869, + "sd_uw": 1251935, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2946100, + "completions": 212 + }, + { + "idle": { + "n": 10, + "mean_uw": 65, + "sd_uw": 53, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5128751, + "sd_uw": 1668806, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3005530, + "completions": 167 + }, + { + "idle": { + "n": 10, + "mean_uw": 67, + "sd_uw": 56, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5387846, + "sd_uw": 2694848, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3035069, + "completions": 173 + }, + { + "idle": { + "n": 10, + "mean_uw": 74, + "sd_uw": 71, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 17142559, + "sd_uw": 3951843, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2901726, + "completions": 532 + }, + { + "idle": { + "n": 10, + "mean_uw": 70, + "sd_uw": 82, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 40329972, + "sd_uw": 2956054, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2921264, + "completions": 1088 + }, + { + "idle": { + "n": 10, + "mean_uw": 85, + "sd_uw": 111, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 14401520, + "sd_uw": 5184175, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2903308, + "completions": 452 + }, + { + "idle": { + "n": 10, + "mean_uw": 28, + "sd_uw": 27, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 22021478, + "sd_uw": 1524870, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2919054, + "completions": 761 + } + ], + "status": "figure", + "reason": null, + "median": 5, + "range": [ + 81547, + 107889 + ], + "joules_micro": 91693, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 96341, + "window_us": 2903308 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.mbt2018_mean", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.mimi", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.mlicpp", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.mlicv2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.snac", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.video_bmshj2018", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.wavtokenizer", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.pc.gpcc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.pc.vpcc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.prior.nnc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.prior.set", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.quantum.circuit", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.quantum.circuit2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.semantic.clip", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.semantic.perco2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.splat.4d", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.splat.int_codec", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.splat.sog", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.splat.spz", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.tensor.nnc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.ternary.tt", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.text.xz", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "byte", + "units": 4194304, + "width": null, + "height": null, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 67, + "sd_uw": 51, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 8008032, + "sd_uw": 1077745, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2997672, + "completions": 674 + }, + { + "idle": { + "n": 10, + "mean_uw": 70, + "sd_uw": 57, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 8932624, + "sd_uw": 1356374, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3008531, + "completions": 740 + }, + { + "idle": { + "n": 10, + "mean_uw": 65, + "sd_uw": 56, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 10089227, + "sd_uw": 1339180, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3025853, + "completions": 868 + }, + { + "idle": { + "n": 10, + "mean_uw": 63, + "sd_uw": 53, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 12488076, + "sd_uw": 3813095, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2923120, + "completions": 1015 + }, + { + "idle": { + "n": 10, + "mean_uw": 88, + "sd_uw": 74, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 17316689, + "sd_uw": 2020034, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2913576, + "completions": 1385 + }, + { + "idle": { + "n": 10, + "mean_uw": 62, + "sd_uw": 51, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 21368369, + "sd_uw": 2850257, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2915506, + "completions": 1642 + }, + { + "idle": { + "n": 10, + "mean_uw": 28, + "sd_uw": 23, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 23212803, + "sd_uw": 1252733, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2873364, + "completions": 1727 + } + ], + "status": "figure", + "reason": null, + "median": 1, + "range": [ + 35010, + 38532 + ], + "joules_micro": 36121, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 40701, + "window_us": 3008531 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.text.zstd", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "byte", + "units": 4194304, + "width": null, + "height": null, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 86, + "sd_uw": 77, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 15017173, + "sd_uw": 2606788, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3010299, + "completions": 8575 + }, + { + "idle": { + "n": 10, + "mean_uw": 66, + "sd_uw": 54, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 15834490, + "sd_uw": 2322039, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3005930, + "completions": 9025 + }, + { + "idle": { + "n": 10, + "mean_uw": 79, + "sd_uw": 72, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 21545992, + "sd_uw": 2011427, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2920696, + "completions": 12038 + }, + { + "idle": { + "n": 10, + "mean_uw": 47, + "sd_uw": 16, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 23248507, + "sd_uw": 1845451, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2951512, + "completions": 13025 + }, + { + "idle": { + "n": 10, + "mean_uw": 67, + "sd_uw": 61, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 29009195, + "sd_uw": 1582613, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2923064, + "completions": 15845 + }, + { + "idle": { + "n": 10, + "mean_uw": 65, + "sd_uw": 94, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 30855738, + "sd_uw": 4754462, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2896197, + "completions": 16656 + }, + { + "idle": { + "n": 10, + "mean_uw": 51, + "sd_uw": 53, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 29425234, + "sd_uw": 5447106, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2866551, + "completions": 15702 + } + ], + "status": "figure", + "reason": null, + "median": 1, + "range": [ + 5226, + 5370 + ], + "joules_micro": 5272, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 39191, + "window_us": 3005930 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-gpu", + "unit": "frame", + "units": 60, + "width": 1920, + "height": 1080, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_counter", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 71, + "sd_uw": 62, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 1716046, + "sd_uw": 1443594, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2939760, + "completions": 10 + }, + { + "idle": { + "n": 10, + "mean_uw": 69, + "sd_uw": 49, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 1517960, + "sd_uw": 1319063, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2965850, + "completions": 8 + }, + { + "idle": { + "n": 10, + "mean_uw": 64, + "sd_uw": 61, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3809113, + "sd_uw": 1680856, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2899435, + "completions": 26 + }, + { + "idle": { + "n": 10, + "mean_uw": 71, + "sd_uw": 73, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2553274, + "sd_uw": 1431471, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2941129, + "completions": 16 + }, + { + "idle": { + "n": 10, + "mean_uw": 48, + "sd_uw": 22, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 6029953, + "sd_uw": 1524269, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2917041, + "completions": 48 + }, + { + "idle": { + "n": 10, + "mean_uw": 81, + "sd_uw": 77, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 8194586, + "sd_uw": 1712863, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2888723, + "completions": 66 + }, + { + "idle": { + "n": 10, + "mean_uw": 43, + "sd_uw": 59, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 8282520, + "sd_uw": 1860101, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2845280, + "completions": 67 + } + ], + "status": "figure", + "reason": null, + "median": 0, + "range": [ + 331915, + 375465 + ], + "joules_micro": 360325, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 278861, + "window_us": 2939760 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "frame", + "units": 30, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 66, + "sd_uw": 50, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 19762133, + "sd_uw": 3138337, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3038000, + "completions": 24 + }, + { + "idle": { + "n": 10, + "mean_uw": 70, + "sd_uw": 67, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 9904524, + "sd_uw": 2601704, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2987904, + "completions": 8 + }, + { + "idle": { + "n": 10, + "mean_uw": 94, + "sd_uw": 101, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 10688521, + "sd_uw": 3350748, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3027635, + "completions": 8 + }, + { + "idle": { + "n": 10, + "mean_uw": 64, + "sd_uw": 56, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 20411994, + "sd_uw": 3954206, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2961185, + "completions": 24 + }, + { + "idle": { + "n": 10, + "mean_uw": 66, + "sd_uw": 57, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 15475228, + "sd_uw": 3526143, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2992494, + "completions": 19 + }, + { + "idle": { + "n": 10, + "mean_uw": 22, + "sd_uw": 23, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 57927742, + "sd_uw": 3409922, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2910295, + "completions": 72 + }, + { + "idle": { + "n": 10, + "mean_uw": 67, + "sd_uw": 64, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 49989211, + "sd_uw": 6350892, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2890706, + "completions": 64 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 2013449, + 2696721 + ], + "joules_micro": 2158696, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 97383, + "window_us": 2961185 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "platform:media-library", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 90, + "sd_uw": 78, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 4451415, + "sd_uw": 631555, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2950755, + "completions": 18 + }, + { + "idle": { + "n": 10, + "mean_uw": 84, + "sd_uw": 73, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5459580, + "sd_uw": 2604385, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2959499, + "completions": 19 + }, + { + "idle": { + "n": 10, + "mean_uw": 61, + "sd_uw": 54, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 8629859, + "sd_uw": 2544756, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2998672, + "completions": 34 + }, + { + "idle": { + "n": 10, + "mean_uw": 72, + "sd_uw": 53, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 4633227, + "sd_uw": 1517702, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3099641, + "completions": 17 + }, + { + "idle": { + "n": 10, + "mean_uw": 51, + "sd_uw": 70, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 27576829, + "sd_uw": 1406794, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2906921, + "completions": 104 + }, + { + "idle": { + "n": 10, + "mean_uw": 66, + "sd_uw": 68, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 27149763, + "sd_uw": 2484614, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2904199, + "completions": 104 + }, + { + "idle": { + "n": 10, + "mean_uw": 46, + "sd_uw": 66, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 27924213, + "sd_uw": 1413677, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2875579, + "completions": 104 + } + ], + "status": "figure", + "reason": null, + "median": 1, + "range": [ + 597035, + 743501 + ], + "joules_micro": 702494, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 162286, + "window_us": 2959499 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1.lossless", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.avc", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-gpu", + "unit": "frame", + "units": 60, + "width": 1920, + "height": 1080, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_counter", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.avc", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 70, + "sd_uw": 61, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 1613644, + "sd_uw": 1396680, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2945248, + "completions": 8 + }, + { + "idle": { + "n": 10, + "mean_uw": 53, + "sd_uw": 13, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 1285612, + "sd_uw": 1433431, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2965075, + "completions": 8 + }, + { + "idle": { + "n": 10, + "mean_uw": 65, + "sd_uw": 52, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2616949, + "sd_uw": 1595252, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2940896, + "completions": 16 + }, + { + "idle": { + "n": 10, + "mean_uw": 85, + "sd_uw": 72, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2301829, + "sd_uw": 1685545, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2947962, + "completions": 16 + }, + { + "idle": { + "n": 10, + "mean_uw": 53, + "sd_uw": 17, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 4823639, + "sd_uw": 1936151, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2881485, + "completions": 37 + }, + { + "idle": { + "n": 10, + "mean_uw": 72, + "sd_uw": 63, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2476813, + "sd_uw": 1721599, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2909017, + "completions": 16 + }, + { + "idle": { + "n": 10, + "mean_uw": 44, + "sd_uw": 56, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 8808138, + "sd_uw": 1037249, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2902608, + "completions": 73 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 317648, + 396031 + ], + "joules_micro": 339273, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 227238, + "window_us": 2947962 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.avc", + "path": "software", + "decoder": "platform:media-library", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 88, + "sd_uw": 76, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5494105, + "sd_uw": 1022361, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3109755, + "completions": 17 + }, + { + "idle": { + "n": 10, + "mean_uw": 69, + "sd_uw": 63, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 6525242, + "sd_uw": 1356959, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2973318, + "completions": 22 + }, + { + "idle": { + "n": 10, + "mean_uw": 65, + "sd_uw": 56, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 10755979, + "sd_uw": 2074942, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2968906, + "completions": 36 + }, + { + "idle": { + "n": 10, + "mean_uw": 80, + "sd_uw": 73, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 7362123, + "sd_uw": 2053182, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3007572, + "completions": 24 + }, + { + "idle": { + "n": 10, + "mean_uw": 65, + "sd_uw": 56, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 29020635, + "sd_uw": 5600159, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2925078, + "completions": 92 + }, + { + "idle": { + "n": 10, + "mean_uw": 53, + "sd_uw": 22, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 21427522, + "sd_uw": 5917268, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2908107, + "completions": 72 + }, + { + "idle": { + "n": 10, + "mean_uw": 24, + "sd_uw": 18, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 33749979, + "sd_uw": 1777183, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2872505, + "completions": 104 + } + ], + "status": "figure", + "reason": null, + "median": 0, + "range": [ + 746208, + 897657 + ], + "joules_micro": 813574, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 120695, + "window_us": 3109755 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.avs3", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.evc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.evc.baseline", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.hevc", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-gpu", + "unit": "frame", + "units": 60, + "width": 1920, + "height": 1080, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_counter", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.hevc", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 90, + "sd_uw": 83, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 1441032, + "sd_uw": 1136682, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3007648, + "completions": 8 + }, + { + "idle": { + "n": 10, + "mean_uw": 64, + "sd_uw": 46, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 1271068, + "sd_uw": 1114447, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3006457, + "completions": 8 + }, + { + "idle": { + "n": 10, + "mean_uw": 77, + "sd_uw": 92, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3269536, + "sd_uw": 2111069, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2922532, + "completions": 24 + }, + { + "idle": { + "n": 10, + "mean_uw": 62, + "sd_uw": 54, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 1825937, + "sd_uw": 1246158, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2937471, + "completions": 15 + }, + { + "idle": { + "n": 10, + "mean_uw": 46, + "sd_uw": 64, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5788258, + "sd_uw": 948768, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2894166, + "completions": 48 + }, + { + "idle": { + "n": 10, + "mean_uw": 138, + "sd_uw": 295, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 8002900, + "sd_uw": 1672477, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2880424, + "completions": 64 + }, + { + "idle": { + "n": 10, + "mean_uw": 38, + "sd_uw": 31, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 8618160, + "sd_uw": 1644793, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2873385, + "completions": 72 + } + ], + "status": "figure", + "reason": null, + "median": 6, + "range": [ + 282287, + 361154 + ], + "joules_micro": 325831, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 69874, + "window_us": 2873385 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.hevc", + "path": "software", + "decoder": "platform:media-library", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 46, + "sd_uw": 4, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 6186609, + "sd_uw": 1405382, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3022624, + "completions": 16 + }, + { + "idle": { + "n": 10, + "mean_uw": 66, + "sd_uw": 54, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5763471, + "sd_uw": 1350212, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2973693, + "completions": 16 + }, + { + "idle": { + "n": 10, + "mean_uw": 62, + "sd_uw": 54, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 11342894, + "sd_uw": 2805639, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2977288, + "completions": 30 + }, + { + "idle": { + "n": 10, + "mean_uw": 58, + "sd_uw": 31, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 12403365, + "sd_uw": 2626269, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2941040, + "completions": 32 + }, + { + "idle": { + "n": 10, + "mean_uw": 47, + "sd_uw": 73, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 29939328, + "sd_uw": 1924780, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2870545, + "completions": 72 + }, + { + "idle": { + "n": 10, + "mean_uw": 70, + "sd_uw": 55, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 19265724, + "sd_uw": 2178497, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2915049, + "completions": 53 + }, + { + "idle": { + "n": 10, + "mean_uw": 77, + "sd_uw": 78, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 30660949, + "sd_uw": 2194501, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2894021, + "completions": 80 + } + ], + "status": "figure", + "reason": null, + "median": 2, + "range": [ + 856930, + 1130816 + ], + "joules_micro": 993261, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 94783, + "window_us": 2977288 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.int_hyper", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.int_motion", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "frame", + "units": 8, + "width": 96, + "height": 96, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 64, + "sd_uw": 60, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 14522637, + "sd_uw": 1221868, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2993851, + "completions": 4086 + }, + { + "idle": { + "n": 10, + "mean_uw": 64, + "sd_uw": 59, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 6704169, + "sd_uw": 1789450, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2964362, + "completions": 1849 + }, + { + "idle": { + "n": 10, + "mean_uw": 63, + "sd_uw": 55, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 13414128, + "sd_uw": 1428953, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2901272, + "completions": 3620 + }, + { + "idle": { + "n": 10, + "mean_uw": 70, + "sd_uw": 67, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 15859366, + "sd_uw": 2000377, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2878767, + "completions": 4268 + }, + { + "idle": { + "n": 10, + "mean_uw": 41, + "sd_uw": 67, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 22965130, + "sd_uw": 1287934, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2896046, + "completions": 5850 + }, + { + "idle": { + "n": 10, + "mean_uw": 94, + "sd_uw": 77, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 7811921, + "sd_uw": 2691362, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2920423, + "completions": 2234 + }, + { + "idle": { + "n": 10, + "mean_uw": 23, + "sd_uw": 24, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 23065841, + "sd_uw": 1305559, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2921361, + "completions": 5964 + } + ], + "status": "figure", + "reason": null, + "median": 1, + "range": [ + 10194, + 11361 + ], + "joules_micro": 10725, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 71348, + "window_us": 2964362 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.j2k", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vp8", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vp9", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-gpu", + "unit": "frame", + "units": 60, + "width": 1920, + "height": 1080, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_counter", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vp9", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 86, + "sd_uw": 75, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 1737875, + "sd_uw": 1530638, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2928507, + "completions": 8 + }, + { + "idle": { + "n": 10, + "mean_uw": 62, + "sd_uw": 51, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 1490282, + "sd_uw": 1243648, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2935446, + "completions": 8 + }, + { + "idle": { + "n": 10, + "mean_uw": 71, + "sd_uw": 44, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2508041, + "sd_uw": 1903422, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2926691, + "completions": 16 + }, + { + "idle": { + "n": 10, + "mean_uw": 64, + "sd_uw": 55, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3079681, + "sd_uw": 1744920, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2905133, + "completions": 22 + }, + { + "idle": { + "n": 10, + "mean_uw": 56, + "sd_uw": 61, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 7346454, + "sd_uw": 2658019, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2906773, + "completions": 57 + }, + { + "idle": { + "n": 10, + "mean_uw": 31, + "sd_uw": 28, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 6345023, + "sd_uw": 3064991, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2895338, + "completions": 48 + }, + { + "idle": { + "n": 10, + "mean_uw": 25, + "sd_uw": 32, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 6410718, + "sd_uw": 3517337, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2837897, + "completions": 48 + } + ], + "status": "figure", + "reason": null, + "median": 5, + "range": [ + 344104, + 424094 + ], + "joules_micro": 353286, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 136528, + "window_us": 2895338 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vp9", + "path": "software", + "decoder": "platform:media-library", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 73, + "sd_uw": 80, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 6133433, + "sd_uw": 2596562, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2896293, + "completions": 23 + }, + { + "idle": { + "n": 10, + "mean_uw": 56, + "sd_uw": 30, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 7130073, + "sd_uw": 1185658, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2974190, + "completions": 29 + }, + { + "idle": { + "n": 10, + "mean_uw": 63, + "sd_uw": 54, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 9247062, + "sd_uw": 2402527, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2914213, + "completions": 40 + }, + { + "idle": { + "n": 10, + "mean_uw": 92, + "sd_uw": 95, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 10155754, + "sd_uw": 4136797, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2934575, + "completions": 44 + }, + { + "idle": { + "n": 10, + "mean_uw": 53, + "sd_uw": 63, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 27303331, + "sd_uw": 2117346, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2887135, + "completions": 112 + }, + { + "idle": { + "n": 10, + "mean_uw": 80, + "sd_uw": 80, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 27053811, + "sd_uw": 2385713, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2884368, + "completions": 112 + }, + { + "idle": { + "n": 10, + "mean_uw": 26, + "sd_uw": 26, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 25956939, + "sd_uw": 1505916, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2865612, + "completions": 104 + } + ], + "status": "figure", + "reason": null, + "median": 0, + "range": [ + 612448, + 688726 + ], + "joules_micro": 657926, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 141838, + "window_us": 2896293 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vvc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.world.live", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.world.model", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "macos-task", + "unit": "frame", + "units": 9, + "width": 64, + "height": 64, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 68, + "sd_uw": 55, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 1535719, + "sd_uw": 1571892, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 3014961, + "completions": 201 + }, + { + "idle": { + "n": 10, + "mean_uw": 58, + "sd_uw": 23, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2941278, + "sd_uw": 1026038, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2936832, + "completions": 310 + }, + { + "idle": { + "n": 10, + "mean_uw": 72, + "sd_uw": 73, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5118405, + "sd_uw": 1493647, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2886136, + "completions": 498 + }, + { + "idle": { + "n": 10, + "mean_uw": 75, + "sd_uw": 65, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 11062262, + "sd_uw": 2054104, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2893269, + "completions": 1099 + }, + { + "idle": { + "n": 10, + "mean_uw": 64, + "sd_uw": 56, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 12187658, + "sd_uw": 6001688, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2901995, + "completions": 1207 + }, + { + "idle": { + "n": 10, + "mean_uw": 67, + "sd_uw": 54, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 10752542, + "sd_uw": 3164476, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2930396, + "completions": 1094 + }, + { + "idle": { + "n": 10, + "mean_uw": 22, + "sd_uw": 24, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 38933013, + "sd_uw": 1786558, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2916971, + "completions": 3321 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 22585, + 34155 + ], + "joules_micro": 29017, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 115104, + "window_us": 2893269 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.world.replay", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.world.scene", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } + ], + "sig": "0da56a772fdb46c29b1e6bda3dc347320f87e906d82092eb36a6e3f72cd64c8bab6a95c13ed494a547e571b82b357590cbe147174840b5aab122107439348906" +} -- 2.54.0 (Apple Git-157) From f4bfb5dd3b12243f5ac73fb0cfb6effca7d4e714 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:04:34 -0400 Subject: [PATCH 058/242] sink-energy: one vocabulary with the per-fabric record; a hop is restated only for its sole user The sink profiles name the fabrics a reading covers (cpu, gpu, media, neural) and how it is the sink's: task, the operating system's account of the sink's own processes, or bracket, the marginal over an idle bracket on a shared counter. No counter is divided among its users by their busy time; a hop's figure is restated in a session's split only where the session was its sole user, at least 99% of its busy time, and is otherwise absent as hop_shared. Co-Authored-By: Claude Opus 5.5 --- wai/CHANGELOG.md | 39 ++++++++++++---------- wai/SPEC.md | 5 +-- wai/extensions/energy-measurement.md | 39 ++++++++++++++-------- wai/extensions/sink-energy.md | 50 ++++++++++++++++------------ wai/wai-rs/src/sink_energy.rs | 37 +++++++++++--------- wai/wai-rs/src/sink_energy/corpus.rs | 46 +++++++++++++++---------- wai/wai-rs/src/sink_energy/tests.rs | 2 +- 7 files changed, 129 insertions(+), 89 deletions(-) diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index 1de10682d..a67498f99 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -9,20 +9,22 @@ wire format may change and such changes are recorded here. ### `wai.sink.energy`: what decoding cost at the sink, per segment, and one session split by hop - **Sink energy profiles** ([energy-measurement](extensions/energy-measurement.md) §9). For each - kind of platform: the counter a sink reads for its decode, what it sums (`task`, `gpu`, - `package`, `board`), whether it counts only the sink's processes or the whole machine, how - the idle baseline comes off (a bracket: the sink's own parked workers, or the machine's - draw), and the shortest window. `macos-task` is the kernel's per-process energy with the - work billed to the process (`proc_pid_rusage`, `RUSAGE_INFO_V6`); `macos-gpu` is IOReport's - `GPU Energy`; `linux-rapl` is the powercap package zone; `linux-nvml` is the NVIDIA total - energy counter behind a plausibility rule. All are `OnChipCounter` with filtering - `Unknown`, and none is ever `HwShunt`. On the reference macOS sink every other `Energy - Model` channel was frozen for an unprivileged reader, and no channel names the video - decoder, so a hardware decode path is `Partial`: it does not cover the media engine. - Platforms with no rail (phones, a page) state an `Estimator` by `estimator-cpu-time/1` - with its inputs and a stated uncertainty; the display is an `Estimator` of its own, - `estimator-display/1`, from the panel's declared on-mode power and its luminance setting, - never added to a decode figure. + kind of platform: the counter a sink reads for its decode, the fabrics it covers (`cpu`, + `gpu`, `media`, `neural`) and what else it sums, how the reading is the sink's (`task`: the + operating system's account of the sink's own processes; `bracket`: §3's marginal over an + idle bracket on a counter every user shares), and the shortest window. No counter is ever + divided among its users by their busy time; a whole rail is one user's only where that user + was its sole user, at least 99% of the busy time (`rail`). `macos-task` is the kernel's + per-process energy with the work billed to the process (`proc_pid_rusage`, + `RUSAGE_INFO_V6`); `macos-gpu` is IOReport's `GPU Energy`; `linux-rapl` is the powercap + package zone; `linux-nvml` is the NVIDIA total energy counter behind a plausibility rule. + All are `OnChipCounter` with filtering `Unknown`, and none is ever `HwShunt`. On the + reference macOS sink every other `Energy Model` channel was frozen for an unprivileged + reader, and no channel names the video decoder, so a hardware decode path is `Partial`: it + does not cover the media engine. Platforms with no rail (phones, a page) state an + `Estimator` by `estimator-cpu-time/1` with its inputs and a stated uncertainty; the display + is an `Estimator` of its own, `estimator-display/1`, from the panel's declared on-mode power + and its luminance setting, never added to a decode figure. - **The extension** ([sink-energy](extensions/sink-energy.md)) registers the record `wai.sink.energy` with three JSON records signed with Ed25519 over RFC 8785: - a **decode table** (§4): one figure per registered capability, decode path and profile, @@ -38,10 +40,11 @@ wire format may change and such changes are recorded here. term), taken from the sink's own table (`ModelBased`) or another key's (`Estimator`), estimated, or `unmetered` with its reason; - a **session receipt** (§6): the per-hop split into `origin`, `relay`, `sink_decode` and - `display`, each with its own class. A relay part is a share of a live-metering per-hop - record (`WMH1`): the whole record keeps its class, a part of it is an `Estimator`. A - missing part is absent with its reason, never a zero, and no total is stated across - classes. + `display`, each with its own class. A relay part restates a live-metering per-hop record + (`WMH1`) with its class only where the session was the hop's sole user (at least 99% of + its subscriber time); a hop shared with other sessions is absent as `hop_shared`, never + divided by time. A missing part is absent with its reason, never a zero, and no total is + stated across classes. - **Corpus and verifiers.** `live-conformance/sink-energy/` holds accepted and refused tables, segments, chains and sessions (every reason of §8 refuses at least one) and the measured tables of the reference sinks under `tables/`, each with its readings and its procedure. diff --git a/wai/SPEC.md b/wai/SPEC.md index 82dc4ff60..a5077f780 100644 --- a/wai/SPEC.md +++ b/wai/SPEC.md @@ -1041,8 +1041,9 @@ record and every counter reading in a readings file it binds by digest; a **segm receipt** per segment of each stream it decodes, chained, whose figure is measured over the segment's own decode, taken from the sink's own table (`ModelBased`) or another key's (`Estimator`), estimated from processor time, or stated as unmetered with its reason; and a -**session receipt** that splits one viewing session into its origin, its relay (a share of a -hop's per-hop record), its decode and its display (an estimate from the panel's declared +**session receipt** that splits one viewing session into its origin, its relay (a hop's +per-hop record, restated only where the session was the hop's sole user), its decode and its +display (an estimate from the panel's declared on-mode power and its luminance setting), each part with its own acquisition class and a missing part named as absent, never a zero. The counters, their scopes, their idle baselines and their windows are the sink profiles of energy-measurement §9: diff --git a/wai/extensions/energy-measurement.md b/wai/extensions/energy-measurement.md index e6c88116a..ec61d0caa 100644 --- a/wai/extensions/energy-measurement.md +++ b/wai/extensions/energy-measurement.md @@ -1110,20 +1110,33 @@ term in its heading. | item | meaning | |---|---| | counter | the interface and the channel read, its unit, and where it wraps | -| scope | what the counter sums: `task` (the energy the sink's own processes spent on processors, with the work other processes did for them that the platform bills to them), `gpu` (a graphics processor's rail), `package` (a processor package), or `board` (an accelerator board) | -| attribution | `process` where the counter counts only the sink's own processes, `machine` where it counts every process that used the scope | +| fabrics | the kinds of processor its reading covers — `cpu`, `gpu`, `media` (a hardware video engine), `neural` (a neural-network engine) — and what else it sums with them | +| attribution | how the reading is the sink's: `task` or `bracket` (below) | | idle baseline | how the energy present without the decode is taken off | | sampling interval and minimum window | the time between reads, and the shortest busy window a figure may come from | | class | the class a reading under the profile is sealed with (§2) | +The attributions: + +- **`task`**: the operating system's account of the energy the sink's own processes spent + on the fabric, which other work on the host does not enter. +- **`bracket`**: the counter counts every user of its fabrics, and the sink's figure is + §3's marginal over an idle bracket, with §4's ratio deciding whether it is one. +- A counter is never divided among its users by the time each kept it busy: busy time does + not apportion energy (a fabric busy at a low clock and at a high clock accrues the same + time and draws very differently). A whole rail is attributed to one user only where that + user was its sole user, at least 99% of the busy time every user accrued over the span + (attribution `rail`); no sink profile of this revision establishes that, and §6.4 of + [sink-energy](sink-energy.md) applies the rule to a hop. + Every profile shares these rules: - **The baseline is a bracket.** A figure is the difference of two bracket means (§3): an idle bracket read with the decode not running, and a busy bracket read over it. Under - `process` attribution the idle bracket is the sink's own processes with their decode + `task` attribution the idle bracket is the sink's own processes with their decode workers parked, so its mean is the sink's own overhead (the reads of the counter among - it). Under `machine` attribution it is the whole machine's draw, and §4's ratio decides - whether the decode rose clear of it. + it). Under `bracket` attribution it is the whole machine's draw on the counter, and §4's + ratio decides whether the decode rose clear of it. - **Liveness and the window.** Both brackets are read at the profile's sampling interval, and §3.1's liveness rule applies to each. A busy window shorter than the profile's minimum window gives no figure. @@ -1155,8 +1168,8 @@ before it is reaped, since a reaped process's energy enters no counter. | item | value | |---|---| -| scope | `task`: processor energy of the sink's processes and of the work billed to them | -| attribution | `process` | +| fabrics | `cpu`: the processor energy of the sink's processes and of the work billed to them | +| attribution | `task` | | idle baseline | the sink's processes with their decode workers parked (§9.1) | | sampling interval | 200 ms | | minimum window | 1 s, five sampling intervals | @@ -1173,8 +1186,8 @@ read without privileges. | item | value | |---|---| -| scope | `gpu` | -| attribution | `machine` | +| fabrics | `gpu`: the graphics processor's whole rail | +| attribution | `bracket` | | idle baseline | the machine's graphics draw before the burst (§3) | | sampling interval | 200 ms | | minimum window | 1 s, five sampling intervals | @@ -1204,8 +1217,8 @@ reference sinks. | item | value | |---|---| -| scope | `package`: the processor package (its cores, its uncore and, where the package holds one, its integrated graphics) | -| attribution | `machine` | +| fabrics | `cpu`: the processor package, which also sums its uncore and, where the package holds one, its integrated graphics | +| attribution | `bracket` | | idle baseline | the machine's package draw before the burst (§3) | | sampling interval | 200 ms | | minimum window | 1 s, five sampling intervals | @@ -1224,8 +1237,8 @@ paragraph 8.6). | item | value | |---|---| -| scope | `board` | -| attribution | `machine` | +| fabrics | `gpu`: the accelerator board | +| attribution | `bracket` | | idle baseline | the board's draw before the burst (§3) | | sampling interval | 1 s | | minimum window | 5 s, five sampling intervals | diff --git a/wai/extensions/sink-energy.md b/wai/extensions/sink-energy.md index 76a5b08f4..27dce0d9a 100644 --- a/wai/extensions/sink-energy.md +++ b/wai/extensions/sink-energy.md @@ -141,7 +141,7 @@ every raw reading in a file it binds. | `path` | term | `software` or `hardware` | | `decoder` | string | the decoder: `builtin:@` as jwp-receipts §2.4.12 writes one, or `platform:` for a decoder of the platform, `` 1 to 64 of `a`–`z`, `0`–`9` and `-` | | `profile` | term or null | the profile read: `macos-task`, `macos-gpu`, `linux-rapl` or `linux-nvml` (energy-measurement §9.2, §9.3), whose minimum windows are 1 s, 1 s, 1 s and 5 s; `null` only in a row `absent` for `no_decoder` | -| `fabrics` | array | the fabrics the profile's scope covers, exactly: `["cpu"]` for `macos-task` and `estimator-cpu-time/1`, `["gpu"]` for `macos-gpu`, `["package"]` for `linux-rapl`, `["board"]` for `linux-nvml`; empty in an absent row | +| `fabrics` | array | the fabrics the profile's reading covers (energy-measurement §9.1), exactly: `["cpu"]` for `macos-task`, `linux-rapl` and `estimator-cpu-time/1`, `["gpu"]` for `macos-gpu` and `linux-nvml`; empty in an absent row | | `coverage` | term or null | `Partial` (§4.5); `null` in an absent row | | `unit` | term | `image`, `frame`, `content-ms` or `byte` | | `units` | integer | units per completion, at least 1 | @@ -368,7 +368,7 @@ stopped decoding has a last segment; it has no receipt after it. | member | type | meaning | |---|---|---| | `status` | term | `figure` or `absent` | -| `reason` | term or null | for `absent`: `not_reported`, `hop_unmetered`, `unmetered` or `display_undeclared`; `null` for `figure` | +| `reason` | term or null | for `absent`: `not_reported`, `hop_unmetered`, `hop_shared`, `unmetered` or `display_undeclared`; `null` for `figure` | | `joules_micro`, `energy_provenance`, `energy_uncertainty`, `energy_counter_filtering`, `energy_calibration_ref` | — | the part's figure and its own class (§2) | | `coverage` | term or null | `null` when `absent`; for a figure, `Whole` for `display` (the estimate is of the whole panel's draw), `Partial` for `origin` and `relay` (a hop's record does not state what its figure covers), and `sink_decode`'s by §6.3 | | `source` | object or null | for `origin` and `relay` with `figure`: the hop record the part restates (§6.4); otherwise `null` | @@ -412,11 +412,12 @@ present and its `E` is at least 1; `absent` with reason `display_undeclared` whe `display` is `null`, and with `unmetered` when `E` is 0. A reader presents it with the stated uncertainty `display.relative_ppm`, as an estimate, apart. -**`origin`** and **`relay`.** A share of a hop's own signed figure (§6.4), or `absent`: -`not_reported` when the sink holds no such figure, `hop_unmetered` when the hop's record -carries the unmetered marker. +**`origin`** and **`relay`.** A hop's own signed figure, where this session was the +hop's sole user (§6.4), or `absent`: `not_reported` when the sink holds no such figure, +`hop_unmetered` when the hop's record carries the unmetered marker, `hop_shared` when the +session shared the hop with other sessions. -### 6.4 A hop's share +### 6.4 A hop's figure, where the session was its sole user ``` source = { "kind": "live-metering-hop" or "other", "record": 32 B hex, "hop_joules": integer, @@ -425,19 +426,25 @@ source = { "kind": "live-metering-hop" or "other", "record": 32 B hex, "hop_joul `record` is the hop's record's digest: for `live-metering-hop` the digest of live-metering §3.5, and for `other` the SHA-256 of the record's bytes as published. -`hop_joules` and -`hop_class` its figure and class label, `hop_joules` at least 1; `1 ≤ share_num ≤ -share_den`. The part's figure is +`hop_joules` and `hop_class` are its figure and class label, `hop_joules` at least 1. +`share_num` is the busy time this session accrued on the hop and `share_den` the busy +time every user of the hop accrued over the hop's span, `1 ≤ share_num ≤ share_den`. + +A hop's figure is about every session it carried, and it is not divided among them by +the time each kept it busy: busy time does not apportion energy (energy-measurement +§9.1). The part restates the hop's figure only where this session was the hop's sole +user, at least 99% of its busy time: ``` +100 · share_num ≥ 99 · share_den joules_micro = ⌊(2 · hop_joules · share_num + share_den) / (2 · share_den)⌋ ``` -at least 1. When `share_num` equals `share_den` the part is the hop's whole figure, and -its class and evidence are the hop's: `energy_provenance` is `hop_class`. Otherwise the -part is an allocation of a figure about the hop's whole session by a share, which no -counter measured, and its class is `Estimator`. A `HwShunt` hop gives `hwshunt_forbidden` -whatever its share. +and then `joules_micro` is at least 1, its attribution is `rail` (energy-measurement +§9.1), and its class and evidence are the hop's: `energy_provenance` is `hop_class`. A +hop this session shared below that gives no part: it is `absent`, with reason +`hop_shared`, and the sink states no figure for it. A `HwShunt` hop gives +`hwshunt_forbidden`. For `live-metering-hop` the record is a per-hop session record `WMH1` (live-metering §3.1), and `share_den` is the sum of its nine `subscriber_us`: `share_num` is the sink's @@ -445,10 +452,9 @@ statement of its own subscriber-microseconds on that hop, which the record does (live-metering §3 keeps no per-viewer entry). A verifier holding the record's bytes requires: they read as a per-hop record of the room their own `room` field names, its signature verified (live-metering §3.6); their digest is `record`; its -`joules` is `hop_joules`, its class label is `hop_class`, and with a whole share its -evidence is the part's; and `share_den` is its subscriber-microseconds' sum -(`split_hop_mismatch`). For `other`, the arithmetic and the class rule are checked from -the receipt alone. +`joules` is `hop_joules`, its class label is `hop_class` and its evidence the part's; +and `share_den` is its subscriber-microseconds' sum (`split_hop_mismatch`). For `other`, +the arithmetic and the class rule are checked from the receipt alone. ### 6.5 Against the segment receipts @@ -550,13 +556,13 @@ refusal. 1 and the `coverage` §6.2 gives it (for `sink_decode`, any of the two here, its value checked against §6.3 below), and a `source` exactly when it is `origin` or `relay`; an `absent` part has the unmetered marker, `coverage` and `source` `null`, - and a reason its part may have: `not_reported` or `hop_unmetered` for `origin` and - `relay`, `unmetered` for `sink_decode`, `display_undeclared` or `unmetered` for + and a reason its part may have: `not_reported`, `hop_unmetered` or `hop_shared` for + `origin` and `relay`, `unmetered` for `sink_decode`, `display_undeclared` or `unmetered` for `display`; - the display part against §6.3, its declaration's ranges included (`split_display_mismatch`); - - the `origin` then the `relay` part against §6.4: the figure, the class rule, and, - holding the record, the record (`split_hop_mismatch`); + - the `origin` then the `relay` part against §6.4: the sole-user rule, the figure, the + class and evidence, and, holding the record, the record (`split_hop_mismatch`); - holding the segment receipts (a verifier given none holds none), §6.5: each receipt verifies (steps 1–5) and the streams agree (`session_streams_mismatch`), then `sink_decode` is what §6.3 gives diff --git a/wai/wai-rs/src/sink_energy.rs b/wai/wai-rs/src/sink_energy.rs index ce2f8ef52..aade33c29 100644 --- a/wai/wai-rs/src/sink_energy.rs +++ b/wai/wai-rs/src/sink_energy.rs @@ -55,11 +55,12 @@ pub const ESTIMATOR_CPU: &str = "estimator-cpu-time/1"; /// The display estimator (energy-measurement §9.5). pub const ESTIMATOR_DISPLAY: &str = "estimator-display/1"; -/// The machine profiles of energy-measurement §9.2 and §9.3: name, the fabric its scope -/// covers, its minimum window in microseconds. In ascending order of name. +/// The machine profiles of energy-measurement §9.2 and §9.3: name, the fabric its reading +/// covers (the fabric vocabulary `cpu`, `gpu`, `media`, `neural`), its minimum window in +/// microseconds. In ascending order of name. pub const PROFILES: [(&str, &str, u64); 4] = [ - ("linux-nvml", "board", 5_000_000), - ("linux-rapl", "package", 1_000_000), + ("linux-nvml", "gpu", 5_000_000), + ("linux-rapl", "cpu", 1_000_000), ("macos-gpu", "gpu", 1_000_000), ("macos-task", "cpu", 1_000_000), ]; @@ -383,9 +384,9 @@ const METHODS: &[&str] = &["measured", "table", "estimator", "unmetered"]; const MACHINE_PROFILES: &[&str] = &["linux-nvml", "linux-rapl", "macos-gpu", "macos-task"]; const SEGMENT_PROFILES: &[&str] = &["linux-nvml", "linux-rapl", "macos-gpu", "macos-task", ESTIMATOR_CPU]; const COUNTER_STATUSES: &[&str] = &["live", "frozen", "needs-privilege", "no-counter", "not-implemented", "implausible"]; -const FABRICS: &[&str] = &["cpu", "gpu", "package", "board"]; +const FABRICS: &[&str] = &["cpu", "gpu", "media", "neural"]; const PART_STATUSES: &[&str] = &["figure", "absent"]; -const PART_REASONS: &[&str] = &["not_reported", "hop_unmetered", "unmetered", "display_undeclared"]; +const PART_REASONS: &[&str] = &["not_reported", "hop_unmetered", "hop_shared", "unmetered", "display_undeclared"]; const HOP_KINDS: &[&str] = &["live-metering-hop", "other"]; const PANEL_CLASSES: &[&str] = &["backlit", "emissive"]; const BUILD_PROFILES: &[&str] = &["release", "debug"]; @@ -1111,7 +1112,8 @@ pub fn table_figure(per_completion_uj: u64, units: u64, units_per_completion: u6 (units_per_completion > 0).then(|| (2 * per_completion_uj as u128 * units as u128 + units_per_completion as u128) / (2 * units_per_completion as u128)) } -/// A hop share (§6.4), `None` with a zero denominator. +/// A hop's figure over the session's part of its busy time (§6.4), `None` with a zero +/// denominator. Only a sole user's (at least 99%) is a part's figure. pub fn share_figure(hop_joules: u64, num: u64, den: u64) -> Option { (den > 0).then(|| (2 * hop_joules as u128 * num as u128 + den as u128) / (2 * den as u128)) } @@ -1451,7 +1453,7 @@ pub fn verify_session(s: &Value, segments: &[Value], tables: &[Value], hops: &BT } } else { let allowed: &[&str] = match p { - "origin" | "relay" => &["not_reported", "hop_unmetered"], + "origin" | "relay" => &["not_reported", "hop_unmetered", "hop_shared"], "sink_decode" => &["unmetered"], _ => &["display_undeclared", "unmetered"], }; @@ -1507,17 +1509,20 @@ fn check_hop(part: &Value, class: Option<&Class>, hops: &BTreeMap<[u8; 32], Vec< let g = |k: &str| int(&src[k]).unwrap(); let (hj, num, den) = (g("hop_joules"), g("share_num"), g("share_den")); if hj < 1 || num < 1 || num > den { - return Err(bad("the share is out of range".into())); + return Err(bad("the busy times are out of range".into())); + } + // §6.4: the session was the hop's sole user, at least 99% of its busy time; a hop's + // figure is never divided among its users by their busy time. + if 100 * num as u128 < 99 * den as u128 { + return Err(bad("the session was not the hop's sole user".into())); } let want = share_figure(hj, num, den).unwrap(); if want == 0 || int(&part["joules_micro"]).map(|x| x as u128) != Some(want) { - return Err(bad(format!("the figure is not the share {want}"))); + return Err(bad(format!("the figure is not the hop's {want}"))); } let class = class.expect("a figure has a class"); - let whole = num == den; - let label_ok = if whole { class.label() == src["hop_class"].as_str().unwrap() } else { *class == Class::Estimator }; - if !label_ok { - return Err(bad("the class does not follow the share".into())); + if class.label() != src["hop_class"].as_str().unwrap() { + return Err(bad("the class is not the hop's".into())); } if src["kind"] != "live-metering-hop" { return Ok(()); @@ -1537,8 +1542,8 @@ fn check_hop(part: &Value, class: Option<&Class>, hops: &BTreeMap<[u8; 32], Vec< if den_rec != den as u128 { return Err(bad("share_den is not the record's subscriber-microseconds".into())); } - if whole && !same_evidence(class, hc) { - return Err(bad("a whole share's evidence is not the record's".into())); + if !same_evidence(class, hc) { + return Err(bad("the evidence is not the record's".into())); } Ok(()) } diff --git a/wai/wai-rs/src/sink_energy/corpus.rs b/wai/wai-rs/src/sink_energy/corpus.rs index 80970a4e3..db369bbc6 100644 --- a/wai/wai-rs/src/sink_energy/corpus.rs +++ b/wai/wai-rs/src/sink_energy/corpus.rs @@ -176,7 +176,7 @@ fn segment_base(sink: &SigningKey, stream: &str, segment: u64, prev: Option<[u8; fn metered(m: &mut Map, method: &str, profile: &str, joules: u64, class: &Class) { m.insert("method".into(), method.into()); m.insert("profile".into(), profile.into()); - m.insert("fabrics".into(), json!([if profile.starts_with("linux-rapl") { "package" } else { "cpu" }])); + m.insert("fabrics".into(), json!([if profile == "macos-gpu" || profile == "linux-nvml" { "gpu" } else { "cpu" }])); m.insert("coverage".into(), "Partial".into()); m.insert("reason".into(), Value::Null); put_energy(m, joules, Some(class)); @@ -267,16 +267,18 @@ fn part_figure(joules: u64, class: &Class, coverage: &str, source: Value) -> Val Value::Object(m) } +/// The relay part for a session that accrued `share_num` of the hop's busy time: the hop's +/// figure where the session was its sole user (at least 99%), and `hop_shared` otherwise. fn hop_part(rec: &HopRecord, share_num: u64) -> Value { let den: u64 = rec.subscriber_us.iter().sum(); - let class = match share_num == den { - true => match rec.class.as_ref().unwrap() { - EnergyProvenance::OnChipCounter { uncertainty, .. } => { - Class::OnChip { relative_ppm: uncertainty.relative_ppm, window_us: uncertainty.window_us, filtering: Filtering::Unknown } - } - _ => unreachable!("the corpus's hop is an on-chip counter"), - }, - false => Class::Estimator, + if 100 * share_num < 99 * den { + return part_absent("hop_shared"); + } + let class = match rec.class.as_ref().unwrap() { + EnergyProvenance::OnChipCounter { uncertainty, .. } => { + Class::OnChip { relative_ppm: uncertainty.relative_ppm, window_us: uncertainty.window_us, filtering: Filtering::Unknown } + } + _ => unreachable!("the corpus's hop is an on-chip counter"), }; let source = json!({ "kind": "live-metering-hop", "record": hex(&rec.digest()), "hop_joules": rec.joules, "hop_class": "OnChipCounter", "share_num": share_num, "share_den": den }); @@ -472,11 +474,15 @@ pub fn generate() -> String { let hops = [hop_hex.clone()]; cases.push(case("session, four parts", "session", Some(&session), None, &tabs, &segs, &hops, "accept")); cases.push(case("session, the relay's whole figure", "session", Some(&whole), None, &[], &[&w0], &hops, "accept")); + let nearly = sign(Kind::Session, session_body(&sink, std::slice::from_ref(&w0), None, 4_000_000, part_absent("not_reported"), hop_part(&hop, 3_582_000_000)), &sink); + cases.push(case("session, the relay at 99.5% of its busy time", "session", Some(&nearly), None, &[], &[&w0], &hops, "accept")); cases.push(case("session, without its hop record", "session", Some(&session), None, &tabs, &segs, &[], "accept")); - let other = json!({ "kind": "other", "record": digest_hex("origin record"), "hop_joules": 9_000_000, "hop_class": "ModelBased", "share_num": 1, "share_den": 3 }); + // An origin that served this session alone, from a record of another kind, checked from the + // receipt: its whole figure and its class. + let other = json!({ "kind": "other", "record": digest_hex("origin record"), "hop_joules": 9_000_000, "hop_class": "ModelBased", "share_num": 2_000_000, "share_den": 2_000_000 }); let with_origin = sign(Kind::Session, session_body(&sink, &all, Some(display.clone()), 120_000_000, - part_figure(share_figure(9_000_000, 1, 3).unwrap() as u64, &Class::Estimator, "Partial", other), hop_part(&hop, 120_000_000)), &sink); - cases.push(case("session, an origin's share of another kind of record", "session", Some(&with_origin), None, &tabs, &segs, &hops, "accept")); + part_figure(9_000_000, &Class::Model, "Partial", other), hop_part(&hop, 120_000_000)), &sink); + cases.push(case("session, an origin's figure from another kind of record", "session", Some(&with_origin), None, &tabs, &segs, &hops, "accept")); let cal: Vec = { let mut out: Vec = Vec::new(); for i in 0..2u64 { @@ -495,12 +501,18 @@ pub fn generate() -> String { let j = v["split"]["display"]["joules_micro"].as_u64().unwrap(); v["split"]["display"]["joules_micro"] = (j + 1).into(); })), None, &tabs, &segs, &hops, "split_display_mismatch")); - cases.push(case("session, a share over another denominator", "session", Some(&ms(&|v| { + let mw = |f: &dyn Fn(&mut Value)| mutate(Kind::Session, &whole, &sink, |v| f(v)); + cases.push(case("session, the relay's busy time over another denominator", "session", Some(&mw(&|v| { + let r = &mut v["split"]["relay"]["source"]; + r["share_num"] = 3_000_000_000u64.into(); + r["share_den"] = 3_000_000_000u64.into(); + })), None, &[], &[&w0], &hops, "split_hop_mismatch")); + cases.push(case("session, a shared hop stated as the session's", "session", Some(&mw(&|v| { let r = &mut v["split"]["relay"]; - r["source"]["share_den"] = 3_000_000_000u64.into(); - r["joules_micro"] = (share_figure(50_000_000, 120_000_000, 3_000_000_000).unwrap() as u64).into(); - })), None, &tabs, &segs, &hops, "split_hop_mismatch")); - cases.push(case("session, a relay part sealed HwShunt", "session", Some(&ms(&|v| { v["split"]["relay"]["source"]["hop_class"] = "HwShunt".into(); })), None, &tabs, &segs, &hops, "hwshunt_forbidden")); + r["source"]["share_num"] = 1_200_000_000u64.into(); + r["joules_micro"] = (share_figure(50_000_000, 1_200_000_000, 3_600_000_000).unwrap() as u64).into(); + })), None, &[], &[&w0], &hops, "split_hop_mismatch")); + cases.push(case("session, a relay part sealed HwShunt", "session", Some(&mw(&|v| { v["split"]["relay"]["source"]["hop_class"] = "HwShunt".into(); })), None, &[], &[&w0], &hops, "hwshunt_forbidden")); cases.push(case("session, a decode part one microjoule high", "session", Some(&ms(&|v| { let j = v["split"]["sink_decode"]["joules_micro"].as_u64().unwrap(); v["split"]["sink_decode"]["joules_micro"] = (j + 1).into(); diff --git a/wai/wai-rs/src/sink_energy/tests.rs b/wai/wai-rs/src/sink_energy/tests.rs index 562abd350..8d1548e42 100644 --- a/wai/wai-rs/src/sink_energy/tests.rs +++ b/wai/wai-rs/src/sink_energy/tests.rs @@ -130,7 +130,7 @@ fn the_estimators_and_shares_round_half_up() { assert_eq!(display_estimate(1, 1, 1, 0), None); assert_eq!(table_figure(10_000, 48, 60), Some(8_000)); assert_eq!(table_figure(1, 1, 2), Some(1)); - assert_eq!(share_figure(50_000_000, 120_000_000, 3_600_000_000), Some(1_666_667)); + assert_eq!(share_figure(50_000_000, 3_582_000_000, 3_600_000_000), Some(49_750_000)); // A display estimate past 128 bits in its numerator still divides exactly. let big = display_estimate(MAX_INTEGER, MAX_INTEGER, MAX_INTEGER, MAX_INTEGER); assert!(big.is_none() || big.unwrap() > 0); -- 2.54.0 (Apple Git-157) From c596ecd14de43e827316e7d27416610985e9b943 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:05:24 -0400 Subject: [PATCH 059/242] sink-energy: corpus regenerated under the sole-user hop rule Co-Authored-By: Claude Opus 5.5 --- wai/live-conformance/sink-energy/vectors.json | 3855 ++--------------- wai/wai-rs/src/sink_energy.rs | 2 +- 2 files changed, 393 insertions(+), 3464 deletions(-) diff --git a/wai/live-conformance/sink-energy/vectors.json b/wai/live-conformance/sink-energy/vectors.json index 17d2bbf40..5c906ab96 100644 --- a/wai/live-conformance/sink-energy/vectors.json +++ b/wai/live-conformance/sink-energy/vectors.json @@ -26735,19 +26735,12 @@ "energy_calibration_ref": null }, "relay": { - "status": "figure", - "reason": null, - "coverage": "Partial", - "source": { - "kind": "live-metering-hop", - "record": "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f", - "hop_joules": 50000000, - "hop_class": "OnChipCounter", - "share_num": 120000000, - "share_den": 3600000000 - }, - "joules_micro": 1666667, - "energy_provenance": "Estimator", + "status": "absent", + "reason": "hop_shared", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null @@ -26775,7 +26768,7 @@ "energy_calibration_ref": null } }, - "sig": "90e6034baeea1de600f605ec51da42281c7b62c9c382d08d3001f8f19baa4e94e7e61721d1a8df5e10553cfff42afdc89332fbaab20bfe9e336da9069d212002" + "sig": "c630579878ecc4f1a19a27524c319c7b9a81fc1b3f757ee8df78fc2970995800dcc13d9a1703f0707aeede0bdf98d0eef9f159c4bbfce67ec604a763c7289701" }, "readings": null, "tables": [ @@ -28483,7 +28476,7 @@ "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f" ], "expect": "accept", - "identity": "8f4df2220d026f31915f6964d0d3af327e97378f90deb250d63bc22723fd3d69" + "identity": "a67d65abe23ccc175fb87178d72d9ac800687e43d029d95e5c6328526c619726" }, { "name": "session, the relay's whole figure", @@ -28626,6 +28619,147 @@ "expect": "accept", "identity": "532338930699763ff93a0f68b2951bb4362d683d998153b535658e80e8a90289" }, + { + "name": "session, the relay at 99.5% of its busy time", + "subject": "session", + "record": { + "wai_sink_session": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "4b777ee53618ddcc501be1c8e64c6d5ad2fac78db6d6652ab14906f8ccd1a97e", + "streams": [ + { + "stream": "3db62d96e3723f398918580d381d7a956a910ee3847e397f358a660cba0101cf", + "segments": 1, + "last": "f47397e94feac4e1aaf73a46035eb718b9d95cabca0763eaff2388806810cffd" + } + ], + "media_us": 2000000, + "presented_us": 4000000, + "display": null, + "split": { + "origin": { + "status": "absent", + "reason": "not_reported", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + "relay": { + "status": "figure", + "reason": null, + "coverage": "Partial", + "source": { + "kind": "live-metering-hop", + "record": "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f", + "hop_joules": 50000000, + "hop_class": "OnChipCounter", + "share_num": 3582000000, + "share_den": 3600000000 + }, + "joules_micro": 49750000, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 25000, + "window_us": 600000000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + "sink_decode": { + "status": "figure", + "reason": null, + "coverage": "Partial", + "source": null, + "joules_micro": 6000000, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 15203, + "window_us": 4000000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + "display": { + "status": "absent", + "reason": "display_undeclared", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } + }, + "sig": "c49022feb1135218583b5c0d85e285ba2d40aa4834ee3976b9259692cbab81e00bdeaf9bd93843de12ede0841082d81edc8e3a1486018dcfe0c0380a187dc000" + }, + "readings": null, + "tables": [], + "segments": [ + { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "4b777ee53618ddcc501be1c8e64c6d5ad2fac78db6d6652ab14906f8ccd1a97e", + "stream": "3db62d96e3723f398918580d381d7a956a910ee3847e397f358a660cba0101cf", + "segment": 0, + "prev": null, + "object": "d793fcb0eb2856e8e76567f18d952900d00466ebd7e99bdfe039910825494973", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "measured", + "profile": "macos-task", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 40000, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 10, + "mean_uw": 3500000, + "sd_uw": 60000, + "live_intervals": 10, + "longest_live_run": 10 + }, + "window_us": 4000000, + "sample_us": 200000 + }, + "table": null, + "estimator": null, + "reason": null, + "joules_micro": 6000000, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 15203, + "window_us": 4000000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null, + "sig": "07eea7d79646e1cd06c6a7f6b986818476dba4d24eed41b57abbc24bbc1f5ad58e08a5285595437b63d56e08609bf996b7ee342eb032350733cbba039fdd9c0b" + } + ], + "hops": [ + "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f" + ], + "expect": "accept", + "identity": "f173d6c33c422d53a73e0860bc0b3a0552ee027dc7114d08f5225dd19d04aace" + }, { "name": "session, without its hop record", "subject": "session", @@ -28669,19 +28803,12 @@ "energy_calibration_ref": null }, "relay": { - "status": "figure", - "reason": null, - "coverage": "Partial", - "source": { - "kind": "live-metering-hop", - "record": "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f", - "hop_joules": 50000000, - "hop_class": "OnChipCounter", - "share_num": 120000000, - "share_den": 3600000000 - }, - "joules_micro": 1666667, - "energy_provenance": "Estimator", + "status": "absent", + "reason": "hop_shared", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null @@ -28709,7 +28836,7 @@ "energy_calibration_ref": null } }, - "sig": "90e6034baeea1de600f605ec51da42281c7b62c9c382d08d3001f8f19baa4e94e7e61721d1a8df5e10553cfff42afdc89332fbaab20bfe9e336da9069d212002" + "sig": "c630579878ecc4f1a19a27524c319c7b9a81fc1b3f757ee8df78fc2970995800dcc13d9a1703f0707aeede0bdf98d0eef9f159c4bbfce67ec604a763c7289701" }, "readings": null, "tables": [ @@ -30415,10 +30542,10 @@ ], "hops": [], "expect": "accept", - "identity": "8f4df2220d026f31915f6964d0d3af327e97378f90deb250d63bc22723fd3d69" + "identity": "a67d65abe23ccc175fb87178d72d9ac800687e43d029d95e5c6328526c619726" }, { - "name": "session, an origin's share of another kind of record", + "name": "session, an origin's figure from another kind of record", "subject": "session", "record": { "wai_sink_session": 1, @@ -30457,29 +30584,22 @@ "record": "668c00a7d6976901a17f6c9d8ef661fc90ba9daf85e37c203e880c53045f108a", "hop_joules": 9000000, "hop_class": "ModelBased", - "share_num": 1, - "share_den": 3 + "share_num": 2000000, + "share_den": 2000000 }, - "joules_micro": 3000000, - "energy_provenance": "Estimator", + "joules_micro": 9000000, + "energy_provenance": "ModelBased", "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null }, "relay": { - "status": "figure", - "reason": null, - "coverage": "Partial", - "source": { - "kind": "live-metering-hop", - "record": "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f", - "hop_joules": 50000000, - "hop_class": "OnChipCounter", - "share_num": 120000000, - "share_den": 3600000000 - }, - "joules_micro": 1666667, - "energy_provenance": "Estimator", + "status": "absent", + "reason": "hop_shared", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null @@ -30507,7 +30627,7 @@ "energy_calibration_ref": null } }, - "sig": "37fc46d8dd4daf63c314f23940994e766d88c593f5beb9bda3b25dfc3b95e7d4976471753c8596b176fbd008938fafbf8e82717c0784e2b9efd9faba68996f0d" + "sig": "24fd8ca438242852617053e4abff12d27ff299b11a341c61e6d079cd8274959a2cbb0d714f2dd08191d891e465d8b07b41a073a028fd4894b0e2c27cf5c88008" }, "readings": null, "tables": [ @@ -32215,7 +32335,7 @@ "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f" ], "expect": "accept", - "identity": "ae7a442e00d3202b0703152660984f16a32164d119b5a22787cd85af6c8083d6" + "identity": "8edc73a3d90c805708da4288808b769a9eee9d988091dd0a2c61050bf73a1321" }, { "name": "session, decode read from a calibrated instrument", @@ -32442,19 +32562,12 @@ "energy_calibration_ref": null }, "relay": { - "status": "figure", - "reason": null, - "coverage": "Partial", - "source": { - "kind": "live-metering-hop", - "record": "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f", - "hop_joules": 50000000, - "hop_class": "OnChipCounter", - "share_num": 120000000, - "share_den": 3600000000 - }, - "joules_micro": 1666667, - "energy_provenance": "Estimator", + "status": "absent", + "reason": "hop_shared", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null @@ -32482,7 +32595,7 @@ "energy_calibration_ref": null } }, - "sig": "6c2be43891d04f1f9d0957834d9c0f0331c939c40a778ad72a116434a5e5f9d492815283f86abbd81c0dc8e2197c6ce0559f8c6bae12c9e9d28907e456d4f90c" + "sig": "a429824ebd4192d0eb5db022c5130c1d7e965fd4fa46da98214936812f92c0f6f0718c72a3f01b32dfc582dbe8a0f2e8a3955b510255b28b01f89843279b7003" }, "readings": null, "tables": [ @@ -34235,19 +34348,12 @@ "energy_calibration_ref": null }, "relay": { - "status": "figure", - "reason": null, - "coverage": "Partial", - "source": { - "kind": "live-metering-hop", - "record": "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f", - "hop_joules": 50000000, - "hop_class": "OnChipCounter", - "share_num": 120000000, - "share_den": 3600000000 - }, - "joules_micro": 1666667, - "energy_provenance": "Estimator", + "status": "absent", + "reason": "hop_shared", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null @@ -34275,7 +34381,7 @@ "energy_calibration_ref": null } }, - "sig": "ffd90245a5e10a0ef75fd46729641b5b3ab1df59942205e004e1696ee5af6a8ade8ff6d6577bfcf87129ba3287552e90e06f2dba73f4941f190958cbaf535904" + "sig": "b8da3502fe77bfc7365a8a7894e07faf38cc6bb1103c90817a699a47366e2961b7887bc15d49c881b849c91e1e86cae7e58c25f550de3ee630097947156ae403" }, "readings": null, "tables": [ @@ -35986,35 +36092,22 @@ "identity": null }, { - "name": "session, a share over another denominator", + "name": "session, the relay's busy time over another denominator", "subject": "session", "record": { "wai_sink_session": 1, "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", - "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "session": "4b777ee53618ddcc501be1c8e64c6d5ad2fac78db6d6652ab14906f8ccd1a97e", "streams": [ { - "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", - "segments": 3, - "last": "9ca9c2eff1d8b74b8cf7f08758f611900f78387c091eb54e5ce4717378716ed0" - }, - { - "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", - "segments": 2, - "last": "83e0d157261d6a7272d72dbba17a9c319ab7938615a2562f6eebed9222147624" + "stream": "3db62d96e3723f398918580d381d7a956a910ee3847e397f358a660cba0101cf", + "segments": 1, + "last": "f47397e94feac4e1aaf73a46035eb718b9d95cabca0763eaff2388806810cffd" } ], - "media_us": 10000000, - "presented_us": 120000000, - "display": { - "method": "estimator-display/1", - "panel_class": "backlit", - "on_mode_mw": 30000, - "reference_nits": 250, - "nits": 200, - "basis": "https://label.example/display/model-x", - "relative_ppm": 300000 - }, + "media_us": 2000000, + "presented_us": 4000000, + "display": null, "split": { "origin": { "status": "absent", @@ -36036,13 +36129,16 @@ "record": "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f", "hop_joules": 50000000, "hop_class": "OnChipCounter", - "share_num": 120000000, + "share_num": 3000000000, "share_den": 3000000000 }, - "joules_micro": 2000000, - "energy_provenance": "Estimator", - "energy_uncertainty": null, - "energy_counter_filtering": null, + "joules_micro": 50000000, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 25000, + "window_us": 600000000 + }, + "energy_counter_filtering": "Unknown", "energy_calibration_ref": null }, "sink_decode": { @@ -36050,1536 +36146,40 @@ "reason": null, "coverage": "Partial", "source": null, - "joules_micro": 2338254, - "energy_provenance": "Estimator", - "energy_uncertainty": null, - "energy_counter_filtering": null, + "joules_micro": 6000000, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 15203, + "window_us": 4000000 + }, + "energy_counter_filtering": "Unknown", "energy_calibration_ref": null }, "display": { - "status": "figure", - "reason": null, - "coverage": "Whole", + "status": "absent", + "reason": "display_undeclared", + "coverage": null, "source": null, - "joules_micro": 2880000000, - "energy_provenance": "Estimator", + "joules_micro": 0, + "energy_provenance": null, "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null } }, - "sig": "ebb8b1f2a19c2926260c1ef3554a98d40169f2dc53937d17b862802eb26516174940035b37f1b9b2921320fccabbb89061211080978e63bb1e5dbd028e7e540b" + "sig": "6a150e55f27a9ddaa8b2b03843f9fb61cb13c114161192aaba869219ad620e9995734570f9372c2a02dfd7645a7c6a8ed3fde763d4169cb934c0459e14e7d80e" }, "readings": null, - "tables": [ - { - "wai_sink_table": 1, - "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", - "procedure": "wai-sink-table/1", - "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", - "machine": { - "os": "corpus", - "arch": "corpus", - "processor": "synthetic", - "cores": 16, - "memory_gib": 64 - }, - "build": { - "profile": "release", - "toolchain": null, - "target": null - }, - "measured": "2026-10-22", - "rounds": 7, - "sample_us": 200000, - "counters": [ - { - "profile": "macos-gpu", - "status": "frozen", - "detail": "Energy Model/GPU Energy" - }, - { - "profile": "macos-task", - "status": "live", - "detail": "proc_pid_rusage V6" - } - ], - "rows": [ - { - "capability": "wai.audio.flac", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "content-ms", - "units": 10000, - "width": null, - "height": null, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 50 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 53 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 56 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 59 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 62 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 65 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 68 - } - ], - "status": "unmetered", - "reason": "rail_not_live", - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.image.png", - "path": "hardware", - "decoder": "platform:media-framework", - "profile": "macos-task", - "unit": "image", - "units": 1, - "width": 1920, - "height": 1080, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_hardware_path", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.image.png", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "image", - "units": 1, - "width": 1920, - "height": 1080, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 2144400, - "sd_uw": 356790, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2652643, - "sd_uw": 391226, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 400 - }, - { - "idle": { - "n": 10, - "mean_uw": 2212000, - "sd_uw": 397991, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2606000, - "sd_uw": 332414, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 403 - }, - { - "idle": { - "n": 10, - "mean_uw": 2066400, - "sd_uw": 307198, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2586286, - "sd_uw": 342938, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 406 - }, - { - "idle": { - "n": 10, - "mean_uw": 1885700, - "sd_uw": 332615, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2577429, - "sd_uw": 398088, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 409 - }, - { - "idle": { - "n": 10, - "mean_uw": 2074500, - "sd_uw": 452434, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2637429, - "sd_uw": 386990, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 412 - }, - { - "idle": { - "n": 10, - "mean_uw": 2040300, - "sd_uw": 384214, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2626143, - "sd_uw": 418958, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 415 - }, - { - "idle": { - "n": 10, - "mean_uw": 1851400, - "sd_uw": 287343, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2489429, - "sd_uw": 369320, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 418 - } - ], - "status": "unmetered", - "reason": "below_attribution_floor", - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.neural.int_mlicpp", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "image", - "units": 1, - "width": 256, - "height": 256, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_work_item", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.video.av1", - "path": "hardware", - "decoder": "platform:media-framework", - "profile": "macos-task", - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 1890800, - "sd_uw": 173618, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3406857, - "sd_uw": 291462, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3000 - }, - { - "idle": { - "n": 10, - "mean_uw": 2003700, - "sd_uw": 258242, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3502000, - "sd_uw": 238914, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3003 - }, - { - "idle": { - "n": 10, - "mean_uw": 2141600, - "sd_uw": 275043, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3416357, - "sd_uw": 204354, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3006 - }, - { - "idle": { - "n": 10, - "mean_uw": 2028200, - "sd_uw": 196893, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3475500, - "sd_uw": 226251, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3009 - }, - { - "idle": { - "n": 10, - "mean_uw": 2040400, - "sd_uw": 214134, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3434357, - "sd_uw": 223672, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3012 - }, - { - "idle": { - "n": 10, - "mean_uw": 2030100, - "sd_uw": 307907, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3404571, - "sd_uw": 219703, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3015 - }, - { - "idle": { - "n": 10, - "mean_uw": 2050600, - "sd_uw": 244479, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3408571, - "sd_uw": 225896, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3018 - } - ], - "status": "relative", - "reason": null, - "median": 4, - "range": [ - 1186, - 1413 - ], - "joules_micro": 1294, - "energy_provenance": "OnChipCounter", - "energy_uncertainty": { - "relative_ppm": 64810, - "window_us": 2800000 - }, - "energy_counter_filtering": "Unknown", - "energy_calibration_ref": null - }, - { - "capability": "wai.video.av1", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-gpu", - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_counter", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.video.av1", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 1987200, - "sd_uw": 15719, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5002143, - "sd_uw": 17502, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1000 - }, - { - "idle": { - "n": 10, - "mean_uw": 2002700, - "sd_uw": 19682, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5004857, - "sd_uw": 20184, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1003 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000800, - "sd_uw": 18850, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5001143, - "sd_uw": 17629, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1006 - }, - { - "idle": { - "n": 10, - "mean_uw": 2002500, - "sd_uw": 18549, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 4999571, - "sd_uw": 16787, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1009 - }, - { - "idle": { - "n": 10, - "mean_uw": 1997400, - "sd_uw": 20824, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5002429, - "sd_uw": 18559, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1012 - }, - { - "idle": { - "n": 10, - "mean_uw": 1996900, - "sd_uw": 17445, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5001000, - "sd_uw": 19415, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1015 - }, - { - "idle": { - "n": 10, - "mean_uw": 2008700, - "sd_uw": 13777, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5001214, - "sd_uw": 17215, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1018 - } - ], - "status": "figure", - "reason": null, - "median": 3, - "range": [ - 8199, - 8408 - ], - "joules_micro": 8284, - "energy_provenance": "OnChipCounter", - "energy_uncertainty": { - "relative_ppm": 7284, - "window_us": 2800000 - }, - "energy_counter_filtering": "Unknown", - "energy_calibration_ref": null - }, - { - "capability": "wai.video.vvc", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": null, - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_decoder", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - } - ], - "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" - }, - { - "wai_sink_table": 1, - "signer": "849e713d6bb5b2daf8790b7d6ed3779d08d3a71fa30fe14b230a5539ff54e6a9", - "procedure": "wai-sink-table/1", - "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", - "machine": { - "os": "corpus", - "arch": "corpus", - "processor": "synthetic", - "cores": 16, - "memory_gib": 64 - }, - "build": { - "profile": "release", - "toolchain": null, - "target": null - }, - "measured": "2026-10-22", - "rounds": 7, - "sample_us": 200000, - "counters": [ - { - "profile": "macos-gpu", - "status": "frozen", - "detail": "Energy Model/GPU Energy" - }, - { - "profile": "macos-task", - "status": "live", - "detail": "proc_pid_rusage V6" - } - ], - "rows": [ - { - "capability": "wai.audio.flac", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "content-ms", - "units": 10000, - "width": null, - "height": null, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 50 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 53 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 56 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 59 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 62 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 65 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 68 - } - ], - "status": "unmetered", - "reason": "rail_not_live", - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.image.png", - "path": "hardware", - "decoder": "platform:media-framework", - "profile": "macos-task", - "unit": "image", - "units": 1, - "width": 1920, - "height": 1080, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_hardware_path", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.image.png", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "image", - "units": 1, - "width": 1920, - "height": 1080, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 2144400, - "sd_uw": 356790, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2652643, - "sd_uw": 391226, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 400 - }, - { - "idle": { - "n": 10, - "mean_uw": 2212000, - "sd_uw": 397991, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2606000, - "sd_uw": 332414, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 403 - }, - { - "idle": { - "n": 10, - "mean_uw": 2066400, - "sd_uw": 307198, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2586286, - "sd_uw": 342938, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 406 - }, - { - "idle": { - "n": 10, - "mean_uw": 1885700, - "sd_uw": 332615, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2577429, - "sd_uw": 398088, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 409 - }, - { - "idle": { - "n": 10, - "mean_uw": 2074500, - "sd_uw": 452434, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2637429, - "sd_uw": 386990, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 412 - }, - { - "idle": { - "n": 10, - "mean_uw": 2040300, - "sd_uw": 384214, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2626143, - "sd_uw": 418958, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 415 - }, - { - "idle": { - "n": 10, - "mean_uw": 1851400, - "sd_uw": 287343, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2489429, - "sd_uw": 369320, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 418 - } - ], - "status": "unmetered", - "reason": "below_attribution_floor", - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.neural.int_mlicpp", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "image", - "units": 1, - "width": 256, - "height": 256, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_work_item", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.video.av1", - "path": "hardware", - "decoder": "platform:media-framework", - "profile": "macos-task", - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 1890800, - "sd_uw": 173618, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3406857, - "sd_uw": 291462, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3000 - }, - { - "idle": { - "n": 10, - "mean_uw": 2003700, - "sd_uw": 258242, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3502000, - "sd_uw": 238914, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3003 - }, - { - "idle": { - "n": 10, - "mean_uw": 2141600, - "sd_uw": 275043, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3416357, - "sd_uw": 204354, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3006 - }, - { - "idle": { - "n": 10, - "mean_uw": 2028200, - "sd_uw": 196893, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3475500, - "sd_uw": 226251, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3009 - }, - { - "idle": { - "n": 10, - "mean_uw": 2040400, - "sd_uw": 214134, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3434357, - "sd_uw": 223672, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3012 - }, - { - "idle": { - "n": 10, - "mean_uw": 2030100, - "sd_uw": 307907, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3404571, - "sd_uw": 219703, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3015 - }, - { - "idle": { - "n": 10, - "mean_uw": 2050600, - "sd_uw": 244479, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3408571, - "sd_uw": 225896, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3018 - } - ], - "status": "relative", - "reason": null, - "median": 4, - "range": [ - 1186, - 1413 - ], - "joules_micro": 1294, - "energy_provenance": "OnChipCounter", - "energy_uncertainty": { - "relative_ppm": 64810, - "window_us": 2800000 - }, - "energy_counter_filtering": "Unknown", - "energy_calibration_ref": null - }, - { - "capability": "wai.video.av1", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-gpu", - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_counter", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.video.av1", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 1987200, - "sd_uw": 15719, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5002143, - "sd_uw": 17502, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1000 - }, - { - "idle": { - "n": 10, - "mean_uw": 2002700, - "sd_uw": 19682, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5004857, - "sd_uw": 20184, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1003 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000800, - "sd_uw": 18850, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5001143, - "sd_uw": 17629, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1006 - }, - { - "idle": { - "n": 10, - "mean_uw": 2002500, - "sd_uw": 18549, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 4999571, - "sd_uw": 16787, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1009 - }, - { - "idle": { - "n": 10, - "mean_uw": 1997400, - "sd_uw": 20824, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5002429, - "sd_uw": 18559, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1012 - }, - { - "idle": { - "n": 10, - "mean_uw": 1996900, - "sd_uw": 17445, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5001000, - "sd_uw": 19415, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1015 - }, - { - "idle": { - "n": 10, - "mean_uw": 2008700, - "sd_uw": 13777, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5001214, - "sd_uw": 17215, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1018 - } - ], - "status": "figure", - "reason": null, - "median": 3, - "range": [ - 8199, - 8408 - ], - "joules_micro": 8284, - "energy_provenance": "OnChipCounter", - "energy_uncertainty": { - "relative_ppm": 7284, - "window_us": 2800000 - }, - "energy_counter_filtering": "Unknown", - "energy_calibration_ref": null - }, - { - "capability": "wai.video.vvc", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": null, - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_decoder", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - } - ], - "sig": "80d62d05c16342e453409ec2b4cd034c8863029734bf8f1e3fcbdf2c1a969aba26b518f1e6239e427d810d75f612972f78982cb150f45e13ba1bbeb3f825d508" - } - ], + "tables": [], "segments": [ { "wai_sink_segment": 1, "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", - "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", - "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", + "session": "4b777ee53618ddcc501be1c8e64c6d5ad2fac78db6d6652ab14906f8ccd1a97e", + "stream": "3db62d96e3723f398918580d381d7a956a910ee3847e397f358a660cba0101cf", "segment": 0, "prev": null, - "object": "904fcef85345a6ab4cc7388f8a9d6a52700713dd5f0afab36deb9b859b0d5bf2", + "object": "d793fcb0eb2856e8e76567f18d952900d00466ebd7e99bdfe039910825494973", "capability": "wai.video.av1", "path": "software", "decoder": "builtin:wai@0.0.0-corpus", @@ -37604,143 +36204,123 @@ }, "busy": { "n": 10, - "mean_uw": 2900000, + "mean_uw": 3500000, "sd_uw": 60000, "live_intervals": 10, "longest_live_run": 10 }, - "window_us": 2000000, + "window_us": 4000000, "sample_us": 200000 }, "table": null, "estimator": null, "reason": null, - "joules_micro": 1800000, + "joules_micro": 6000000, "energy_provenance": "OnChipCounter", "energy_uncertainty": { - "relative_ppm": 25338, - "window_us": 2000000 + "relative_ppm": 15203, + "window_us": 4000000 }, "energy_counter_filtering": "Unknown", "energy_calibration_ref": null, - "sig": "3b05fcb31aaea9e0574691dd6ccaab9cf4754581b14f6d89e14f417d92217f294bf175ba14189ddcdfa003e400b8f4e9252cefc0478cd4ad178c5c4cec84de0e" - }, - { - "wai_sink_segment": 1, - "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", - "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", - "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", - "segment": 1, - "prev": "2299cf3c7fb5fc13ac56cad34b0f656487dbfdde2f30ad1e9f6ed7a754eb238a", - "object": "2ec96fd7e1b71588a9b60b1cdf1dc4bc7aea8f2ab33eb3cffd9d987b1ef87698", - "capability": "wai.video.av1", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "unit": "frame", - "units": 48, - "width": 1280, - "height": 720, - "media_us": 2000000, - "method": "table", - "profile": "macos-task", - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "bracket": null, - "table": { - "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", - "row": 6, - "per_completion_uj": 8284, - "units_per_completion": 60 + "sig": "07eea7d79646e1cd06c6a7f6b986818476dba4d24eed41b57abbc24bbc1f5ad58e08a5285595437b63d56e08609bf996b7ee342eb032350733cbba039fdd9c0b" + } + ], + "hops": [ + "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f" + ], + "expect": "split_hop_mismatch", + "identity": null + }, + { + "name": "session, a shared hop stated as the session's", + "subject": "session", + "record": { + "wai_sink_session": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "4b777ee53618ddcc501be1c8e64c6d5ad2fac78db6d6652ab14906f8ccd1a97e", + "streams": [ + { + "stream": "3db62d96e3723f398918580d381d7a956a910ee3847e397f358a660cba0101cf", + "segments": 1, + "last": "f47397e94feac4e1aaf73a46035eb718b9d95cabca0763eaff2388806810cffd" + } + ], + "media_us": 2000000, + "presented_us": 4000000, + "display": null, + "split": { + "origin": { + "status": "absent", + "reason": "not_reported", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null }, - "estimator": null, - "reason": null, - "joules_micro": 6627, - "energy_provenance": "ModelBased", - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null, - "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" + "relay": { + "status": "figure", + "reason": null, + "coverage": "Partial", + "source": { + "kind": "live-metering-hop", + "record": "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f", + "hop_joules": 50000000, + "hop_class": "OnChipCounter", + "share_num": 1200000000, + "share_den": 3600000000 + }, + "joules_micro": 16666667, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 25000, + "window_us": 600000000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + "sink_decode": { + "status": "figure", + "reason": null, + "coverage": "Partial", + "source": null, + "joules_micro": 6000000, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 15203, + "window_us": 4000000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + "display": { + "status": "absent", + "reason": "display_undeclared", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } }, + "sig": "757d3861999a2dfffa462541ac45910ae20e8c804afeb4b9aa6a4bdf05cf32668107cb4a0026758a8d1b158c9e57b2bf2e05200022fb6b503a8b9822f7083100" + }, + "readings": null, + "tables": [], + "segments": [ { "wai_sink_segment": 1, "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", - "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", - "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", - "segment": 2, - "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", - "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", - "capability": "wai.video.av1", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "unit": "frame", - "units": 48, - "width": 1280, - "height": 720, - "media_us": 2000000, - "method": "unmetered", - "profile": null, - "fabrics": [], - "coverage": null, - "bracket": null, - "table": null, - "estimator": null, - "reason": "not_reported", - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null, - "sig": "117485aa23543d0f3f6889adb8ed04262700e16edcab1fc70bc7fde3c9bc2c7dd58712375b5a52716d3ae0c7d626263a8e875dd8b767a3a8d994b887da43e309" - }, - { - "wai_sink_segment": 1, - "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", - "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", - "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", + "session": "4b777ee53618ddcc501be1c8e64c6d5ad2fac78db6d6652ab14906f8ccd1a97e", + "stream": "3db62d96e3723f398918580d381d7a956a910ee3847e397f358a660cba0101cf", "segment": 0, "prev": null, - "object": "46a5ce125bbfa822b69b37a2d86527fba30be66ae6d4d464e36a14a9a3c0190d", - "capability": "wai.audio.opus", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "unit": "content-ms", - "units": 2000, - "width": null, - "height": null, - "media_us": 2000000, - "method": "estimator", - "profile": "estimator-cpu-time/1", - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "bracket": null, - "table": null, - "estimator": { - "method": "estimator-cpu-time/1", - "cpu_us": 150000, - "active_mw": 3500, - "basis": "https://power.example/device-class/phone-a", - "relative_ppm": 400000 - }, - "reason": null, - "joules_micro": 525000, - "energy_provenance": "Estimator", - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null, - "sig": "c72bf30af2dfe67c3fb20d934f48e4b3e34fab7fe89ff796a952f41f7efde4dde8fa9f6a90737edbb56d42cfbe76b5f93dcc8aaa42585ebad97db30ac270b702" - }, - { - "wai_sink_segment": 1, - "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", - "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", - "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", - "segment": 1, - "prev": "350fa7be2744dc21b7cc5e4cf37fb9462a311266fb3c015a4ab00e454e8280a4", - "object": "ba2aed82898351ea19b5244df36506e731296164a2a7b7bcc52c60128a144071", + "object": "d793fcb0eb2856e8e76567f18d952900d00466ebd7e99bdfe039910825494973", "capability": "wai.video.av1", "path": "software", "decoder": "builtin:wai@0.0.0-corpus", @@ -37749,27 +36329,42 @@ "width": 1280, "height": 720, "media_us": 2000000, - "method": "table", + "method": "measured", "profile": "macos-task", "fabrics": [ "cpu" ], "coverage": "Partial", - "bracket": null, - "table": { - "id": "bd74e55dbd7822aec58d2097175e05c974e5d6f93c9b048c3350f94c346dfaff", - "row": 6, - "per_completion_uj": 8284, - "units_per_completion": 60 + "bracket": { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 40000, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 10, + "mean_uw": 3500000, + "sd_uw": 60000, + "live_intervals": 10, + "longest_live_run": 10 + }, + "window_us": 4000000, + "sample_us": 200000 }, + "table": null, "estimator": null, "reason": null, - "joules_micro": 6627, - "energy_provenance": "Estimator", - "energy_uncertainty": null, - "energy_counter_filtering": null, + "joules_micro": 6000000, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 15203, + "window_us": 4000000 + }, + "energy_counter_filtering": "Unknown", "energy_calibration_ref": null, - "sig": "c173f9db9a7efd77482a1367d4096f6f94ddf08251306b45775e893f2679f1296005289ea126b66285a83c7c02c97ce7839367309b869c9205df3babf64b1308" + "sig": "07eea7d79646e1cd06c6a7f6b986818476dba4d24eed41b57abbc24bbc1f5ad58e08a5285595437b63d56e08609bf996b7ee342eb032350733cbba039fdd9c0b" } ], "hops": [ @@ -37784,30 +36379,17 @@ "record": { "wai_sink_session": 1, "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", - "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "session": "4b777ee53618ddcc501be1c8e64c6d5ad2fac78db6d6652ab14906f8ccd1a97e", "streams": [ { - "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", - "segments": 3, - "last": "9ca9c2eff1d8b74b8cf7f08758f611900f78387c091eb54e5ce4717378716ed0" - }, - { - "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", - "segments": 2, - "last": "83e0d157261d6a7272d72dbba17a9c319ab7938615a2562f6eebed9222147624" + "stream": "3db62d96e3723f398918580d381d7a956a910ee3847e397f358a660cba0101cf", + "segments": 1, + "last": "f47397e94feac4e1aaf73a46035eb718b9d95cabca0763eaff2388806810cffd" } ], - "media_us": 10000000, - "presented_us": 120000000, - "display": { - "method": "estimator-display/1", - "panel_class": "backlit", - "on_mode_mw": 30000, - "reference_nits": 250, - "nits": 200, - "basis": "https://label.example/display/model-x", - "relative_ppm": 300000 - }, + "media_us": 2000000, + "presented_us": 4000000, + "display": null, "split": { "origin": { "status": "absent", @@ -37829,13 +36411,16 @@ "record": "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f", "hop_joules": 50000000, "hop_class": "HwShunt", - "share_num": 120000000, + "share_num": 3600000000, "share_den": 3600000000 }, - "joules_micro": 1666667, - "energy_provenance": "Estimator", - "energy_uncertainty": null, - "energy_counter_filtering": null, + "joules_micro": 50000000, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 25000, + "window_us": 600000000 + }, + "energy_counter_filtering": "Unknown", "energy_calibration_ref": null }, "sink_decode": { @@ -37843,1536 +36428,40 @@ "reason": null, "coverage": "Partial", "source": null, - "joules_micro": 2338254, - "energy_provenance": "Estimator", - "energy_uncertainty": null, - "energy_counter_filtering": null, + "joules_micro": 6000000, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 15203, + "window_us": 4000000 + }, + "energy_counter_filtering": "Unknown", "energy_calibration_ref": null }, "display": { - "status": "figure", - "reason": null, - "coverage": "Whole", + "status": "absent", + "reason": "display_undeclared", + "coverage": null, "source": null, - "joules_micro": 2880000000, - "energy_provenance": "Estimator", + "joules_micro": 0, + "energy_provenance": null, "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null } }, - "sig": "747f2f517732cccb55ae9cffaafae9eeeba26808ec0b1bd5569a3b7c9c30b7ab3b2ef269435df3a1192eb48e5574746861eb44cc15cd1bd1bb53857dc414b006" + "sig": "438a2431b44ae72c14d901b4a311b56a8155c1e522df21ed2dfa6b884698fd1681773c2efb45f0e8033b3ff6a73817e31b8529081d43873e794f161109068a06" }, "readings": null, - "tables": [ - { - "wai_sink_table": 1, - "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", - "procedure": "wai-sink-table/1", - "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", - "machine": { - "os": "corpus", - "arch": "corpus", - "processor": "synthetic", - "cores": 16, - "memory_gib": 64 - }, - "build": { - "profile": "release", - "toolchain": null, - "target": null - }, - "measured": "2026-10-22", - "rounds": 7, - "sample_us": 200000, - "counters": [ - { - "profile": "macos-gpu", - "status": "frozen", - "detail": "Energy Model/GPU Energy" - }, - { - "profile": "macos-task", - "status": "live", - "detail": "proc_pid_rusage V6" - } - ], - "rows": [ - { - "capability": "wai.audio.flac", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "content-ms", - "units": 10000, - "width": null, - "height": null, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 50 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 53 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 56 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 59 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 62 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 65 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 68 - } - ], - "status": "unmetered", - "reason": "rail_not_live", - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.image.png", - "path": "hardware", - "decoder": "platform:media-framework", - "profile": "macos-task", - "unit": "image", - "units": 1, - "width": 1920, - "height": 1080, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_hardware_path", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.image.png", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "image", - "units": 1, - "width": 1920, - "height": 1080, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 2144400, - "sd_uw": 356790, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2652643, - "sd_uw": 391226, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 400 - }, - { - "idle": { - "n": 10, - "mean_uw": 2212000, - "sd_uw": 397991, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2606000, - "sd_uw": 332414, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 403 - }, - { - "idle": { - "n": 10, - "mean_uw": 2066400, - "sd_uw": 307198, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2586286, - "sd_uw": 342938, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 406 - }, - { - "idle": { - "n": 10, - "mean_uw": 1885700, - "sd_uw": 332615, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2577429, - "sd_uw": 398088, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 409 - }, - { - "idle": { - "n": 10, - "mean_uw": 2074500, - "sd_uw": 452434, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2637429, - "sd_uw": 386990, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 412 - }, - { - "idle": { - "n": 10, - "mean_uw": 2040300, - "sd_uw": 384214, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2626143, - "sd_uw": 418958, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 415 - }, - { - "idle": { - "n": 10, - "mean_uw": 1851400, - "sd_uw": 287343, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2489429, - "sd_uw": 369320, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 418 - } - ], - "status": "unmetered", - "reason": "below_attribution_floor", - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.neural.int_mlicpp", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "image", - "units": 1, - "width": 256, - "height": 256, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_work_item", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.video.av1", - "path": "hardware", - "decoder": "platform:media-framework", - "profile": "macos-task", - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 1890800, - "sd_uw": 173618, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3406857, - "sd_uw": 291462, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3000 - }, - { - "idle": { - "n": 10, - "mean_uw": 2003700, - "sd_uw": 258242, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3502000, - "sd_uw": 238914, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3003 - }, - { - "idle": { - "n": 10, - "mean_uw": 2141600, - "sd_uw": 275043, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3416357, - "sd_uw": 204354, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3006 - }, - { - "idle": { - "n": 10, - "mean_uw": 2028200, - "sd_uw": 196893, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3475500, - "sd_uw": 226251, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3009 - }, - { - "idle": { - "n": 10, - "mean_uw": 2040400, - "sd_uw": 214134, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3434357, - "sd_uw": 223672, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3012 - }, - { - "idle": { - "n": 10, - "mean_uw": 2030100, - "sd_uw": 307907, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3404571, - "sd_uw": 219703, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3015 - }, - { - "idle": { - "n": 10, - "mean_uw": 2050600, - "sd_uw": 244479, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3408571, - "sd_uw": 225896, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3018 - } - ], - "status": "relative", - "reason": null, - "median": 4, - "range": [ - 1186, - 1413 - ], - "joules_micro": 1294, - "energy_provenance": "OnChipCounter", - "energy_uncertainty": { - "relative_ppm": 64810, - "window_us": 2800000 - }, - "energy_counter_filtering": "Unknown", - "energy_calibration_ref": null - }, - { - "capability": "wai.video.av1", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-gpu", - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_counter", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.video.av1", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 1987200, - "sd_uw": 15719, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5002143, - "sd_uw": 17502, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1000 - }, - { - "idle": { - "n": 10, - "mean_uw": 2002700, - "sd_uw": 19682, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5004857, - "sd_uw": 20184, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1003 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000800, - "sd_uw": 18850, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5001143, - "sd_uw": 17629, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1006 - }, - { - "idle": { - "n": 10, - "mean_uw": 2002500, - "sd_uw": 18549, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 4999571, - "sd_uw": 16787, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1009 - }, - { - "idle": { - "n": 10, - "mean_uw": 1997400, - "sd_uw": 20824, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5002429, - "sd_uw": 18559, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1012 - }, - { - "idle": { - "n": 10, - "mean_uw": 1996900, - "sd_uw": 17445, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5001000, - "sd_uw": 19415, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1015 - }, - { - "idle": { - "n": 10, - "mean_uw": 2008700, - "sd_uw": 13777, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5001214, - "sd_uw": 17215, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1018 - } - ], - "status": "figure", - "reason": null, - "median": 3, - "range": [ - 8199, - 8408 - ], - "joules_micro": 8284, - "energy_provenance": "OnChipCounter", - "energy_uncertainty": { - "relative_ppm": 7284, - "window_us": 2800000 - }, - "energy_counter_filtering": "Unknown", - "energy_calibration_ref": null - }, - { - "capability": "wai.video.vvc", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": null, - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_decoder", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - } - ], - "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" - }, - { - "wai_sink_table": 1, - "signer": "849e713d6bb5b2daf8790b7d6ed3779d08d3a71fa30fe14b230a5539ff54e6a9", - "procedure": "wai-sink-table/1", - "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", - "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", - "machine": { - "os": "corpus", - "arch": "corpus", - "processor": "synthetic", - "cores": 16, - "memory_gib": 64 - }, - "build": { - "profile": "release", - "toolchain": null, - "target": null - }, - "measured": "2026-10-22", - "rounds": 7, - "sample_us": 200000, - "counters": [ - { - "profile": "macos-gpu", - "status": "frozen", - "detail": "Energy Model/GPU Energy" - }, - { - "profile": "macos-task", - "status": "live", - "detail": "proc_pid_rusage V6" - } - ], - "rows": [ - { - "capability": "wai.audio.flac", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "content-ms", - "units": 10000, - "width": null, - "height": null, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 50 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 53 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 56 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 59 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 62 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 65 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000000, - "sd_uw": 0, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2900000, - "sd_uw": 0, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 68 - } - ], - "status": "unmetered", - "reason": "rail_not_live", - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.image.png", - "path": "hardware", - "decoder": "platform:media-framework", - "profile": "macos-task", - "unit": "image", - "units": 1, - "width": 1920, - "height": 1080, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_hardware_path", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.image.png", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "image", - "units": 1, - "width": 1920, - "height": 1080, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 2144400, - "sd_uw": 356790, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2652643, - "sd_uw": 391226, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 400 - }, - { - "idle": { - "n": 10, - "mean_uw": 2212000, - "sd_uw": 397991, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2606000, - "sd_uw": 332414, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 403 - }, - { - "idle": { - "n": 10, - "mean_uw": 2066400, - "sd_uw": 307198, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2586286, - "sd_uw": 342938, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 406 - }, - { - "idle": { - "n": 10, - "mean_uw": 1885700, - "sd_uw": 332615, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2577429, - "sd_uw": 398088, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 409 - }, - { - "idle": { - "n": 10, - "mean_uw": 2074500, - "sd_uw": 452434, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2637429, - "sd_uw": 386990, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 412 - }, - { - "idle": { - "n": 10, - "mean_uw": 2040300, - "sd_uw": 384214, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2626143, - "sd_uw": 418958, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 415 - }, - { - "idle": { - "n": 10, - "mean_uw": 1851400, - "sd_uw": 287343, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 2489429, - "sd_uw": 369320, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 418 - } - ], - "status": "unmetered", - "reason": "below_attribution_floor", - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.neural.int_mlicpp", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "image", - "units": 1, - "width": 256, - "height": 256, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_work_item", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.video.av1", - "path": "hardware", - "decoder": "platform:media-framework", - "profile": "macos-task", - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 1890800, - "sd_uw": 173618, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3406857, - "sd_uw": 291462, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3000 - }, - { - "idle": { - "n": 10, - "mean_uw": 2003700, - "sd_uw": 258242, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3502000, - "sd_uw": 238914, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3003 - }, - { - "idle": { - "n": 10, - "mean_uw": 2141600, - "sd_uw": 275043, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3416357, - "sd_uw": 204354, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3006 - }, - { - "idle": { - "n": 10, - "mean_uw": 2028200, - "sd_uw": 196893, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3475500, - "sd_uw": 226251, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3009 - }, - { - "idle": { - "n": 10, - "mean_uw": 2040400, - "sd_uw": 214134, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3434357, - "sd_uw": 223672, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3012 - }, - { - "idle": { - "n": 10, - "mean_uw": 2030100, - "sd_uw": 307907, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3404571, - "sd_uw": 219703, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3015 - }, - { - "idle": { - "n": 10, - "mean_uw": 2050600, - "sd_uw": 244479, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 3408571, - "sd_uw": 225896, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 3018 - } - ], - "status": "relative", - "reason": null, - "median": 4, - "range": [ - 1186, - 1413 - ], - "joules_micro": 1294, - "energy_provenance": "OnChipCounter", - "energy_uncertainty": { - "relative_ppm": 64810, - "window_us": 2800000 - }, - "energy_counter_filtering": "Unknown", - "energy_calibration_ref": null - }, - { - "capability": "wai.video.av1", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-gpu", - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_counter", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - { - "capability": "wai.video.av1", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": "macos-task", - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "workers": 8, - "rounds": [ - { - "idle": { - "n": 10, - "mean_uw": 1987200, - "sd_uw": 15719, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5002143, - "sd_uw": 17502, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1000 - }, - { - "idle": { - "n": 10, - "mean_uw": 2002700, - "sd_uw": 19682, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5004857, - "sd_uw": 20184, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1003 - }, - { - "idle": { - "n": 10, - "mean_uw": 2000800, - "sd_uw": 18850, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5001143, - "sd_uw": 17629, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1006 - }, - { - "idle": { - "n": 10, - "mean_uw": 2002500, - "sd_uw": 18549, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 4999571, - "sd_uw": 16787, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1009 - }, - { - "idle": { - "n": 10, - "mean_uw": 1997400, - "sd_uw": 20824, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5002429, - "sd_uw": 18559, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1012 - }, - { - "idle": { - "n": 10, - "mean_uw": 1996900, - "sd_uw": 17445, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5001000, - "sd_uw": 19415, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1015 - }, - { - "idle": { - "n": 10, - "mean_uw": 2008700, - "sd_uw": 13777, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 5001214, - "sd_uw": 17215, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2800000, - "completions": 1018 - } - ], - "status": "figure", - "reason": null, - "median": 3, - "range": [ - 8199, - 8408 - ], - "joules_micro": 8284, - "energy_provenance": "OnChipCounter", - "energy_uncertainty": { - "relative_ppm": 7284, - "window_us": 2800000 - }, - "energy_counter_filtering": "Unknown", - "energy_calibration_ref": null - }, - { - "capability": "wai.video.vvc", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "profile": null, - "unit": "frame", - "units": 60, - "width": 1280, - "height": 720, - "fabrics": [], - "coverage": null, - "workers": 0, - "status": "absent", - "reason": "no_decoder", - "rounds": [], - "median": null, - "range": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - } - ], - "sig": "80d62d05c16342e453409ec2b4cd034c8863029734bf8f1e3fcbdf2c1a969aba26b518f1e6239e427d810d75f612972f78982cb150f45e13ba1bbeb3f825d508" - } - ], + "tables": [], "segments": [ { "wai_sink_segment": 1, "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", - "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", - "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", + "session": "4b777ee53618ddcc501be1c8e64c6d5ad2fac78db6d6652ab14906f8ccd1a97e", + "stream": "3db62d96e3723f398918580d381d7a956a910ee3847e397f358a660cba0101cf", "segment": 0, "prev": null, - "object": "904fcef85345a6ab4cc7388f8a9d6a52700713dd5f0afab36deb9b859b0d5bf2", + "object": "d793fcb0eb2856e8e76567f18d952900d00466ebd7e99bdfe039910825494973", "capability": "wai.video.av1", "path": "software", "decoder": "builtin:wai@0.0.0-corpus", @@ -39397,172 +36486,26 @@ }, "busy": { "n": 10, - "mean_uw": 2900000, + "mean_uw": 3500000, "sd_uw": 60000, "live_intervals": 10, "longest_live_run": 10 }, - "window_us": 2000000, + "window_us": 4000000, "sample_us": 200000 }, "table": null, "estimator": null, "reason": null, - "joules_micro": 1800000, + "joules_micro": 6000000, "energy_provenance": "OnChipCounter", "energy_uncertainty": { - "relative_ppm": 25338, - "window_us": 2000000 + "relative_ppm": 15203, + "window_us": 4000000 }, "energy_counter_filtering": "Unknown", "energy_calibration_ref": null, - "sig": "3b05fcb31aaea9e0574691dd6ccaab9cf4754581b14f6d89e14f417d92217f294bf175ba14189ddcdfa003e400b8f4e9252cefc0478cd4ad178c5c4cec84de0e" - }, - { - "wai_sink_segment": 1, - "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", - "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", - "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", - "segment": 1, - "prev": "2299cf3c7fb5fc13ac56cad34b0f656487dbfdde2f30ad1e9f6ed7a754eb238a", - "object": "2ec96fd7e1b71588a9b60b1cdf1dc4bc7aea8f2ab33eb3cffd9d987b1ef87698", - "capability": "wai.video.av1", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "unit": "frame", - "units": 48, - "width": 1280, - "height": 720, - "media_us": 2000000, - "method": "table", - "profile": "macos-task", - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "bracket": null, - "table": { - "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", - "row": 6, - "per_completion_uj": 8284, - "units_per_completion": 60 - }, - "estimator": null, - "reason": null, - "joules_micro": 6627, - "energy_provenance": "ModelBased", - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null, - "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" - }, - { - "wai_sink_segment": 1, - "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", - "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", - "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", - "segment": 2, - "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", - "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", - "capability": "wai.video.av1", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "unit": "frame", - "units": 48, - "width": 1280, - "height": 720, - "media_us": 2000000, - "method": "unmetered", - "profile": null, - "fabrics": [], - "coverage": null, - "bracket": null, - "table": null, - "estimator": null, - "reason": "not_reported", - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null, - "sig": "117485aa23543d0f3f6889adb8ed04262700e16edcab1fc70bc7fde3c9bc2c7dd58712375b5a52716d3ae0c7d626263a8e875dd8b767a3a8d994b887da43e309" - }, - { - "wai_sink_segment": 1, - "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", - "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", - "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", - "segment": 0, - "prev": null, - "object": "46a5ce125bbfa822b69b37a2d86527fba30be66ae6d4d464e36a14a9a3c0190d", - "capability": "wai.audio.opus", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "unit": "content-ms", - "units": 2000, - "width": null, - "height": null, - "media_us": 2000000, - "method": "estimator", - "profile": "estimator-cpu-time/1", - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "bracket": null, - "table": null, - "estimator": { - "method": "estimator-cpu-time/1", - "cpu_us": 150000, - "active_mw": 3500, - "basis": "https://power.example/device-class/phone-a", - "relative_ppm": 400000 - }, - "reason": null, - "joules_micro": 525000, - "energy_provenance": "Estimator", - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null, - "sig": "c72bf30af2dfe67c3fb20d934f48e4b3e34fab7fe89ff796a952f41f7efde4dde8fa9f6a90737edbb56d42cfbe76b5f93dcc8aaa42585ebad97db30ac270b702" - }, - { - "wai_sink_segment": 1, - "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", - "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", - "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", - "segment": 1, - "prev": "350fa7be2744dc21b7cc5e4cf37fb9462a311266fb3c015a4ab00e454e8280a4", - "object": "ba2aed82898351ea19b5244df36506e731296164a2a7b7bcc52c60128a144071", - "capability": "wai.video.av1", - "path": "software", - "decoder": "builtin:wai@0.0.0-corpus", - "unit": "frame", - "units": 48, - "width": 1280, - "height": 720, - "media_us": 2000000, - "method": "table", - "profile": "macos-task", - "fabrics": [ - "cpu" - ], - "coverage": "Partial", - "bracket": null, - "table": { - "id": "bd74e55dbd7822aec58d2097175e05c974e5d6f93c9b048c3350f94c346dfaff", - "row": 6, - "per_completion_uj": 8284, - "units_per_completion": 60 - }, - "estimator": null, - "reason": null, - "joules_micro": 6627, - "energy_provenance": "Estimator", - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null, - "sig": "c173f9db9a7efd77482a1367d4096f6f94ddf08251306b45775e893f2679f1296005289ea126b66285a83c7c02c97ce7839367309b869c9205df3babf64b1308" + "sig": "07eea7d79646e1cd06c6a7f6b986818476dba4d24eed41b57abbc24bbc1f5ad58e08a5285595437b63d56e08609bf996b7ee342eb032350733cbba039fdd9c0b" } ], "hops": [ @@ -39614,19 +36557,12 @@ "energy_calibration_ref": null }, "relay": { - "status": "figure", - "reason": null, - "coverage": "Partial", - "source": { - "kind": "live-metering-hop", - "record": "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f", - "hop_joules": 50000000, - "hop_class": "OnChipCounter", - "share_num": 120000000, - "share_den": 3600000000 - }, - "joules_micro": 1666667, - "energy_provenance": "Estimator", + "status": "absent", + "reason": "hop_shared", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null @@ -39654,7 +36590,7 @@ "energy_calibration_ref": null } }, - "sig": "e4023904722a8592403b015d9173879893abc5ab602ff07dd2fddafe5b1307c865bbb066bf450493cab198fca0a6ad7f5eaa695d5c7c49b2e307451ccbae3303" + "sig": "6e646cb47b627686a3133f4feeb3d21749f70ee1ec88949db639957d5cbd1dae79db5feeb3aa4f32a5eaf1b07a4e06be83725c3d5bb7ae369794b0b1f8686205" }, "readings": null, "tables": [ @@ -41407,19 +38343,12 @@ "energy_calibration_ref": null }, "relay": { - "status": "figure", - "reason": null, - "coverage": "Partial", - "source": { - "kind": "live-metering-hop", - "record": "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f", - "hop_joules": 50000000, - "hop_class": "OnChipCounter", - "share_num": 120000000, - "share_den": 3600000000 - }, - "joules_micro": 1666667, - "energy_provenance": "Estimator", + "status": "absent", + "reason": "hop_shared", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, "energy_uncertainty": null, "energy_counter_filtering": null, "energy_calibration_ref": null @@ -41447,7 +38376,7 @@ "energy_calibration_ref": null } }, - "sig": "f652522dea94b7c9df974393e58e3feed5af7dee7274eacadf6a3ea2517d38c87c0728b8b0acfe0b99b4914685d18beac5b11b3d01fc5acee29545e48c37f201" + "sig": "111ac3236fd441984a06f9a69ddb3fb30610c4421d47858951da1ca398d30ff8dab4a28f9557e0ce800324c06d9b28c264e4839789d96d6479d107b47f4ac70b" }, "readings": null, "tables": [ diff --git a/wai/wai-rs/src/sink_energy.rs b/wai/wai-rs/src/sink_energy.rs index aade33c29..a0e870f41 100644 --- a/wai/wai-rs/src/sink_energy.rs +++ b/wai/wai-rs/src/sink_energy.rs @@ -1513,7 +1513,7 @@ fn check_hop(part: &Value, class: Option<&Class>, hops: &BTreeMap<[u8; 32], Vec< } // §6.4: the session was the hop's sole user, at least 99% of its busy time; a hop's // figure is never divided among its users by their busy time. - if 100 * num as u128 < 99 * den as u128 { + if 100 * (num as u128) < 99 * (den as u128) { return Err(bad("the session was not the hop's sole user".into())); } let want = share_figure(hj, num, den).unwrap(); -- 2.54.0 (Apple Git-157) From dba2c13b93818dedf07900cbcf727b8627173898 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:13:11 -0400 Subject: [PATCH 060/242] sink-energy: the 32-core Linux sink's table re-taken under the fabric vocabulary; fabrics a shape rule Co-Authored-By: Claude Opus 5.5 --- wai/extensions/sink-energy.md | 2 + .../tables/linux-x86_64-32-core/procedure.txt | 2 +- .../tables/linux-x86_64-32-core/readings.json | 2 +- .../tables/linux-x86_64-32-core/table.json | 742 +++++++++--------- wai/live-conformance/sink_energy_verify.py | 81 +- wai/wai-rs/src/sink_energy.rs | 2 +- wai/wai-rs/src/sink_energy/tests.rs | 2 +- 7 files changed, 423 insertions(+), 410 deletions(-) diff --git a/wai/extensions/sink-energy.md b/wai/extensions/sink-energy.md index 27dce0d9a..3e06f4694 100644 --- a/wai/extensions/sink-energy.md +++ b/wai/extensions/sink-energy.md @@ -480,6 +480,8 @@ refusal. - `width` and `height` are integers of at least 1 for `image` and `frame`, and `null` for the other units; `units` is at least 1; - `decoder` follows its grammar (§4.2); + - every member of a `fabrics` array is one of `cpu`, `gpu`, `media` and `neural` + (energy-measurement §9.1), none twice; - `measured` is a date `YYYY-MM-DD`; - a `source` (§6.4) and a `display` (§6.3) have exactly their members, of their types; - `split` has exactly its four parts, each with exactly the members of §6.2; diff --git a/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/procedure.txt b/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/procedure.txt index 642dd4356..39a898f13 100644 --- a/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/procedure.txt +++ b/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/procedure.txt @@ -4,7 +4,7 @@ command: /home/dcharlot/ena-target/release/wai_sink_table run --out /home/dcharl sampling interval: 200000 us; idle bracket 10 intervals; busy bracket 14 intervals; rounds 7; workers 8, released together from a start barrier order: every case once per round, the order rotated by one each round; 2 s of settling before each idle bracket build: release rustc 1.99.0 (b940084d7 2026-09-28) x86_64-unknown-linux-gnu -machine: linux 7.0.0-38-generic, x86_64 processor, 32 logical cores; release build; load averages at the end Some(("5.49", "3.89", "3.12")) +machine: linux 7.0.0-38-generic, x86_64 processor, 32 logical cores; release build; load averages at the end Some(("5.02", "2.91", "1.32")) survey: linux-nvml no-counter: libnvidia-ml: no device linux-rapl live: powercap energy_uj: /sys/class/powercap/intel-rapl:0 diff --git a/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/readings.json b/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/readings.json index cc1309470..afba67863 100644 --- a/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/readings.json +++ b/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/readings.json @@ -1 +1 @@ -{"rows":[{"capability":"wai.audio.aac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.ac4","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.avs3","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.flac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.iamf","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.int_codec","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[20373534000,200115],[20477472000,200082],[20358216000,200088],[20405255000,200088],[20474405000,200085],[20368256000,200082],[20313402000,200087],[20080703000,200106],[20265828000,200088],[20060868000,200086],[20215202000,200085],[20372909000,200084],[20212883000,200090],[20244070000,200101]],"completions":5935,"idle":[[1348365000,201134],[1086278000,200827],[850557000,200678],[763236000,200680],[1584421000,200690],[1702030000,200242],[870530000,200717],[483266000,200608],[1221678000,200448],[1152803000,200383]]},{"busy":[[20081160000,200106],[20331179000,200094],[20382735000,200084],[20125393000,200085],[20317736000,200082],[20053651000,200078],[20230582000,200084],[20179376000,200086],[20116452000,200107],[20135907000,200087],[20222190000,200095],[20062927000,200094],[20791100000,200097],[20179484000,200102]],"completions":5872,"idle":[[1305398000,201158],[863038000,201364],[1746538000,201023],[1373632000,201319],[1118869000,200407],[882156000,201042],[564653000,200846],[1797530000,201128],[1479751000,200588],[1085775000,200675]]},{"busy":[[20650894000,200102],[20422238000,200075],[20326448000,200074],[20302127000,200076],[20321535000,200080],[20177454000,200115],[20371490000,200083],[20204018000,200084],[20189004000,200096],[20035356000,200038],[19961584000,200076],[20161204000,200080],[20204323000,200082],[20222328000,200075]],"completions":5911,"idle":[[1221433000,201107],[1048332000,201303],[1282816000,201301],[1142397000,200192],[1276454000,201167],[886703000,201145],[1074041000,201229],[1268596000,201330],[447243000,201116],[1452241000,201152]]},{"busy":[[20819068000,200125],[20446727000,200091],[19830838000,200090],[19963201000,200099],[20283253000,200090],[19771835000,200085],[20188211000,200086],[20258886000,200087],[19978688000,200084],[19982105000,200082],[19813398000,200079],[19975514000,200080],[19713306000,200086],[20106275000,200082]],"completions":5859,"idle":[[1762284000,200885],[863237000,200836],[788930000,200696],[1094838000,200689],[988199000,200218],[1721133000,200235],[1148211000,200683],[1217863000,200680],[924039000,200852],[0,200685]]},{"busy":[[21679757000,200128],[21594372000,200124],[21504702000,200144],[21679848000,200147],[21589307000,200118],[21672234000,200136],[21752431000,200139],[21178394000,200123],[21456074000,200137],[21314358000,200149],[21378289000,200157],[21544311000,200158],[21348185000,200148],[21576124000,200336]],"completions":5816,"idle":[[4958056000,200159],[4670337000,200154],[4765561000,200192],[4374759000,200166],[4558419000,200157],[4781369000,200166],[4635792000,200163],[4666675000,200164],[4692094000,200159],[4507961000,200141]]},{"busy":[[21996390000,200131],[22168638000,200122],[22188855000,200135],[22228297000,200113],[21669122000,200146],[21666436000,200134],[21672448000,200126],[28171730000,203516],[30893758000,201042],[25118986000,200117],[28584124000,200905],[30690490000,200108],[32321037000,205843],[30371186000,200131]],"completions":4885,"idle":[[5054380000,200125],[5337630000,200121],[5132425000,200134],[5335051000,200119],[5077389000,200117],[5073179000,200120],[5327773000,200130],[5154167000,200157],[5399440000,200155],[5252018000,200165]]},{"busy":[[20285893000,200104],[20250585000,200086],[20022753000,200091],[20337434000,200082],[20155864000,200107],[20364318000,200090],[20196527000,200712],[20097196000,200120],[20161265000,200082],[20207101000,200103],[20113477000,200122],[20186090000,200099],[20147349000,200089],[20207284000,200101]],"completions":5913,"idle":[[1012643000,200641],[1095951000,200640],[1379522000,200834],[1291269000,200744],[1066534000,200692],[1224730000,200704],[1067388000,200699],[1149553000,200705],[1161256000,200651],[1309701000,200792]]}]},{"capability":"wai.audio.ivas","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.mpegh","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.opus","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.opus.packet","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.scene","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.score","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.avatar.pose","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.feed.state","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.film.linear","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.film.reel","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.generate.spectral","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.haptic.mihs","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.haptic.signal","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.avif","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jp2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jpeg","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jpegai","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jxl","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.png","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.mesh.draco","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.mesh.glb","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.bmshj2018","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.cmic","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dcvc_fm","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dcvc_rt","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.elic","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.encodec32","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.glc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.int_hyper","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[19480926000,200514],[18994699000,200502],[19107853000,200507],[18975917000,200559],[18945004000,200496],[18868729000,200527],[18969417000,200496],[19127887000,200525],[19058508000,200517],[18841540000,200493],[18963848000,200496],[18866547000,200486],[19193817000,200215],[18905882000,200780]],"completions":336528,"idle":[[922285000,201189],[885254000,201300],[1699344000,201099],[1143145000,200842],[1103794000,200720],[919005000,201068],[985438000,201326],[1739961000,200559],[785238000,201171],[1025536000,201301]]},{"busy":[[19162919000,200111],[18999322000,200069],[18831683000,200066],[18755058000,200064],[18900023000,200068],[18777502000,200067],[19168519000,200068],[19162430000,200070],[19240567000,200068],[19188797000,200067],[19128848000,200065],[19141832000,200065],[18690470000,200081],[18722955000,200080]],"completions":335253,"idle":[[1022377000,200564],[429315000,200676],[1218549000,200686],[1117817000,200217],[1097218000,200674],[828753000,200366],[1125674000,200795],[975917000,200718],[135140000,200681],[2152828000,200803]]},{"busy":[[19212522000,200122],[18779013000,200068],[18684367000,200066],[18773748000,200066],[18954830000,200068],[18918654000,200069],[18744102000,200062],[18717446000,200064],[18762229000,200064],[18882248000,200072],[18670574000,200069],[18686106000,200068],[18718057000,200066],[18793660000,200066]],"completions":334491,"idle":[[1114994000,200678],[859620000,200876],[1081869000,200413],[920896000,200723],[1330315000,200767],[1243420000,200681],[877579000,200681],[858598000,200734],[1051185000,200345],[1910790000,200675]]},{"busy":[[19980855000,200093],[19905876000,200078],[20340211000,200079],[19570994000,200067],[18693644000,200079],[18749458000,200082],[18825046000,200082],[18736869000,200078],[19012018000,200037],[18917707000,200076],[18631529000,200070],[18826953000,200085],[18703409000,200087],[18848039000,200069]],"completions":332432,"idle":[[4489911000,200528],[820254000,200681],[1373190000,200681],[1409351000,201026],[3478275000,200151],[3557662000,200121],[3337001000,200125],[3036662000,200072],[3130316000,200165],[3510469000,200104]]},{"busy":[[20175699000,200150],[20486108000,200105],[21106971000,200077],[21252762000,200077],[21217057000,200075],[21175007000,200069],[21162892000,200108],[21222077000,200104],[21311185000,200093],[21266859000,200102],[21533311000,200116],[21717062000,200135],[21217042000,200126],[20975234000,200115]],"completions":331496,"idle":[[4724151000,200188],[4788617000,200196],[4962756000,200160],[4638142000,200153],[4678728000,200155],[4655903000,200149],[4493786000,200191],[5015198000,200149],[4786221000,200170],[4722137000,200175]]},{"busy":[[19249661000,200138],[19126956000,200075],[18929151000,200069],[19055457000,200070],[18985926000,200071],[19155442000,200076],[20779519000,200140],[21247360000,200090],[21325726000,200083],[21243516000,200077],[21280866000,200088],[21335780000,200072],[21321438000,200078],[21422735000,200075]],"completions":332948,"idle":[[1302240000,200710],[869492000,200800],[1277339000,200691],[1560573000,200697],[986979000,200679],[782247000,200691],[1292612000,200276],[1153047000,200719],[1439547000,200806],[916227000,200430]]},{"busy":[[19359839000,200131],[18705499000,200083],[18637571000,200067],[18654537000,200068],[18753212000,200073],[19028038000,200067],[18950848000,200066],[18935498000,200065],[18938001000,200063],[18949215000,200069],[19063483000,200069],[18931790000,200065],[18931196000,200066],[18945950000,200066]],"completions":334142,"idle":[[954068000,200496],[906935000,200343],[743431000,200693],[1380162000,200731],[1466828000,200256],[881699000,200693],[1075445000,200687],[930784000,200761],[329145000,200677],[2084731000,200244]]}]},{"capability":"wai.neural.int_mlicpp","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[]},{"capability":"wai.neural.int_synth","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[25628924000,200128],[26936946000,200124],[26336178000,200133],[26951273000,200368],[27304100000,200131],[26321774000,200133],[26754125000,200256],[27418351000,200128],[27040609000,200138],[27293419000,200130],[26801715000,200127],[26888303000,200129],[26990395000,200125],[26745993000,200126]],"completions":762,"idle":[[1029701000,201366],[795415000,201113],[1246411000,201371],[1160630000,200201],[1292383000,201353],[855287000,201159],[732430000,201141],[1049476000,201131],[1207671000,201348],[1603722000,201273]]},{"busy":[[25662277000,200141],[27247493000,200170],[27089556000,200143],[26799747000,200137],[27027456000,200156],[26786533000,200132],[27069401000,200128],[26140143000,200149],[26585509000,200121],[26911068000,200128],[25908145000,200148],[26784397000,200143],[26733221000,200126],[26845109000,200121]],"completions":765,"idle":[[1420550000,200675],[934141000,200214],[1407428000,200686],[1191192000,200870],[953595000,200913],[1376271000,200262],[1032784000,200772],[1279673000,200725],[882034000,200705],[872041000,200683]]},{"busy":[[25495035000,200129],[27467162000,200142],[27321051000,200146],[26913983000,200147],[27087542000,200181],[27106692000,200132],[26947810000,200122],[26834839000,200239],[26919568000,200178],[27248682000,200137],[26733511000,200125],[27150162000,200121],[27012992000,200138],[27275491000,200266]],"completions":772,"idle":[[862458000,200777],[1363623000,200649],[1350241000,200649],[938321000,200878],[955914000,200719],[1231870000,200440],[1323220000,200485],[1366109000,200882],[603790000,200696],[1440950000,200896]]},{"busy":[[24383565000,200133],[26278396000,200139],[26915020000,200146],[26231493000,200135],[26955988000,200135],[26928310000,200217],[26690042000,200264],[27092745000,200164],[26827394000,200131],[27193296000,200128],[27211880000,200122],[27272317000,200210],[26606992000,200186],[26931240000,200124]],"completions":763,"idle":[[446999000,200720],[1427248000,200687],[1167176000,200692],[455162000,200686],[1420794000,200726],[1599389000,200131],[4997850000,200211],[1509809000,200496],[1139559000,200316],[1076117000,200633]]},{"busy":[[26316724000,200306],[27052693000,200129],[26922802000,200256],[27266748000,200223],[26971872000,200126],[27739349000,200135],[27160888000,200160],[27379413000,200302],[27891105000,200137],[27078327000,200146],[26619793000,200137],[27075474000,200138],[27430771000,200648],[27244868000,200131]],"completions":754,"idle":[[4731598000,200160],[5132684000,200152],[4616308000,200176],[4787533000,200194],[4469877000,200180],[4576622000,200180],[4784695000,200153],[4759321000,200152],[4993135000,200148],[4888892000,200164]]},{"busy":[[26252823000,200141],[27835276000,200150],[28458032000,200161],[27782056000,200266],[28127437000,200126],[27989473000,200265],[28232229000,200159],[27375735000,200561],[26587691000,200210],[27232768000,200150],[28406017000,200423],[27881539000,200136],[27464644000,200145],[28334533000,200157]],"completions":759,"idle":[[5335600000,200164],[5137170000,200162],[5094921000,200200],[5186652000,200164],[5148919000,200159],[5449944000,200107],[5307312000,200103],[5354719000,200106],[5206457000,200140],[5167564000,200108]]},{"busy":[[25442212000,200121],[27103487000,200152],[26938472000,200275],[26709419000,200130],[27218517000,200136],[26601103000,200137],[26904477000,200137],[26747915000,200143],[26817186000,200140],[27638539000,200142],[25829643000,200148],[26788272000,200181],[27297539000,200137],[26521730000,200148]],"completions":764,"idle":[[1063803000,200707],[1727297000,200706],[1029366000,200239],[839007000,200409],[782338000,200684],[1193054000,200259],[1533780000,200678],[912215000,200831],[684473000,200321],[1252743000,200448]]}]},{"capability":"wai.neural.mbt2018_mean","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mimi","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mlicpp","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mlicv2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.snac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.video_bmshj2018","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.wavtokenizer","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.pc.gpcc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.pc.vpcc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.prior.nnc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.prior.set","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.quantum.circuit","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.quantum.circuit2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.semantic.clip","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.semantic.perco2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.4d","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.int_codec","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[]},{"capability":"wai.splat.sog","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.spz","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.tensor.nnc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.ternary.tt","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.text.xz","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.text.zstd","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.av1","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.av1.lossless","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.av2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.avc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.avs3","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.evc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.evc.baseline","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.hevc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.int_hyper","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[]},{"capability":"wai.video.int_motion","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[19862834000,200103],[20043717000,200230],[19496459000,200517],[19172241000,200486],[19269634000,200502],[19204573000,200101],[19353034000,200139],[19194564000,200493],[19129137000,200494],[19061392000,200491],[19147646000,200497],[19228910000,200511],[19249065000,200499],[19097386000,200491]],"completions":11605,"idle":[[1455110000,200525],[803181000,201122],[955883000,201113],[936322000,200685],[781790000,201150],[1273677000,200733],[1392368000,200868],[1226973000,200880],[971843000,200972],[660946000,201217]]},{"busy":[[19806745000,200119],[19134860000,200083],[19007837000,200061],[19022591000,200077],[19039313000,200064],[19109425000,200067],[19056143000,200062],[18975734000,200078],[18992212000,200062],[19033242000,200064],[19175537000,200067],[19139025000,200065],[18979792000,200066],[18997812000,200060]],"completions":11545,"idle":[[1717501000,200500],[955929000,200498],[965511000,200705],[987956000,200495],[1114200000,200502],[1459763000,200952],[949201000,200216],[1110950000,200684],[1001901000,201181],[1528684000,200691]]},{"busy":[[19741242000,200112],[19471024000,200066],[19355673000,200066],[19246976000,200072],[19392811000,200074],[19286722000,200065],[19260555000,200066],[19335060000,200069],[19310051000,200066],[19264965000,200067],[19256130000,200084],[19215589000,200076],[20480219000,200086],[18914366000,200084]],"completions":11542,"idle":[[539142000,200715],[1315941000,200686],[1430011000,200903],[831774000,200783],[1091695000,200689],[1223249000,200672],[847445000,200696],[1249309000,200687],[1360236000,200783],[925108000,200499]]},{"busy":[[19490279000,200159],[19537518000,200081],[19321038000,200071],[19267329000,200070],[19300561000,200066],[19243039000,200067],[19227018000,200068],[19264171000,200073],[19406009000,200068],[19363913000,200070],[19263316000,200066],[19376882000,200067],[19470108000,200067],[19409321000,200066]],"completions":11534,"idle":[[1012902000,200731],[1026375000,200758],[943723000,200169],[850557000,200844],[1332588000,200689],[1181991000,200687],[1087148000,200685],[1215910000,200689],[1185623000,200906],[1519163000,200718]]},{"busy":[[29299662000,200139],[28968259000,200133],[29829497000,200085],[28303606000,200100],[25041292000,200113],[27365360000,200082],[26169820000,200082],[25462032000,200088],[24146059000,200087],[22363864000,200100],[22620611000,200108],[22795650000,200105],[24560376000,200080],[24633934000,200088]],"completions":11163,"idle":[[1676015000,200651],[1153673000,200761],[1263484000,200674],[3426718000,200098],[4025213000,200125],[5832523000,200224],[19218122000,200120],[23864672000,200147],[23665448000,200183],[22742034000,200132]]},{"busy":[[21129935000,200126],[21299634000,200083],[21236039000,200074],[21156132000,200068],[21356699000,200099],[21310513000,200100],[21332515000,200077],[21198550000,200068],[21257216000,200074],[21616818000,200115],[21710058000,200091],[21098687000,200066],[21247573000,200065],[21206652000,200082]],"completions":11467,"idle":[[4812495000,200164],[4984102000,200159],[4712495000,200166],[4889182000,200174],[4811854000,200182],[5098735000,200145],[5160149000,200133],[4897055000,200132],[4960605000,200145],[4877036000,200149]]},{"busy":[[19472869000,200072],[19167710000,200069],[18869004000,200066],[18948728000,200063],[18927381000,200082],[18889633000,200064],[19091648000,200067],[19377233000,200075],[20389571000,200080],[18764700000,200066],[18794865000,200063],[18999186000,200066],[18798573000,200064],[18894882000,200083]],"completions":11525,"idle":[[1209395000,200708],[1609337000,200710],[671871000,200475],[1411563000,200367],[1076986000,200882],[749900000,200748],[1528577000,200546],[1246213000,200757],[676768000,200711],[1916253000,200687]]}]},{"capability":"wai.video.j2k","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.vp8","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.vp9","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.vvc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.live","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.model","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[21038249000,200109],[20944717000,200112],[20848699000,200080],[20582524000,200085],[20722973000,200069],[21028011000,200101],[20833593000,200095],[20764277000,200096],[21065164000,200074],[20973861000,200100],[21087883000,200081],[20996198000,200077],[20969115000,200071],[21235078000,200080]],"completions":1318,"idle":[[1542218000,200688],[1120914000,200692],[1673619000,200350],[821506000,200679],[482428000,200725],[1878504000,200628],[920652000,200684],[1408649000,200712],[884354000,200678],[882233000,200661]]},{"busy":[[20883503000,200122],[20894656000,200099],[20816810000,200085],[20696241000,200073],[20920198000,200077],[20577839000,200082],[20676879000,200096],[20663894000,200074],[20838934000,200085],[20970198000,200096],[20920183000,200090],[20993299000,200092],[20976271000,200093],[20842306000,200071]],"completions":1306,"idle":[[936719000,200740],[1040870000,200733],[779425000,200713],[1161606000,200165],[1371939000,200234],[749808000,200658],[1218382000,200646],[962597000,200673],[794682000,200649],[1779876000,200647]]},{"busy":[[20254095000,200083],[20909639000,200075],[20886326000,200077],[20276478000,200075],[20442623000,200074],[20682188000,200073],[20973540000,200085],[20633485000,200075],[20919634000,200083],[20708447000,200072],[20505013000,200076],[20780572000,200074],[20660171000,200084],[20607912000,200077]],"completions":1301,"idle":[[1300378000,200890],[680110000,201223],[1526579000,201306],[947369000,201253],[1075307000,201194],[1393590000,201139],[843706000,200978],[1565318000,200271],[1046882000,200647],[1302606000,200684]]},{"busy":[[21424811000,200130],[21117529000,200083],[20722409000,200082],[20845129000,200076],[20600115000,200078],[20641587000,200084],[20362656000,200066],[20526709000,200101],[20382018000,200093],[20739315000,200078],[20961211000,200084],[21547256000,200100],[20547399000,200087],[20270971000,200107]],"completions":1298,"idle":[[1071279000,201112],[1064551000,200838],[1632667000,200711],[932340000,200732],[947323000,200680],[958432000,200687],[1014855000,200687],[1474884000,200689],[838427000,200682],[0,200687]]},{"busy":[[20895297000,200109],[20969512000,200090],[20553975000,200075],[20934068000,200076],[20773721000,200072],[20458781000,200071],[20552908000,200070],[20678023000,200097],[20659347000,200091],[20588642000,200075],[20714306000,200072],[20660934000,200071],[20473215000,200077],[20581760000,200073]],"completions":1299,"idle":[[2062729000,200600],[1072820000,201205],[405756000,200994],[757316000,201302],[2319063000,201131],[1706668000,201232],[1160753000,201311],[542559000,201284],[925627000,201158],[1425356000,201245]]},{"busy":[[20911898000,200104],[20825461000,200085],[20813209000,200091],[20868946000,200080],[20876698000,200094],[20909014000,200075],[20640778000,200083],[20835424000,200078],[20649293000,200072],[20431026000,201383],[20559926000,200092],[20571339000,200071],[20482218000,200067],[20506813000,200088]],"completions":1299,"idle":[[1077596000,200977],[943326000,200932],[909621000,200373],[1448686000,200771],[1484573000,200649],[1203963000,200687],[1231183000,200641],[1155077000,200185],[1679204000,200662],[597564000,200668]]},{"busy":[[20848775000,200080],[20964019000,200083],[20765146000,200102],[20671539000,200094],[20954162000,200092],[20891589000,200094],[20800530000,200080],[20455561000,200067],[20747127000,200087],[20706357000,200037],[20689833000,200066],[20572941000,200073],[20783731000,200070],[20681746000,200085]],"completions":1311,"idle":[[979106000,200693],[1036323000,200428],[1348609000,200692],[606506000,200818],[1570200000,200498],[891251000,200489],[1014077000,200687],[1195510000,200826],[1154817000,200594],[1280009000,200218]]}]},{"capability":"wai.world.replay","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.scene","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]}],"wai_sink_readings":1} \ No newline at end of file +{"rows":[{"capability":"wai.audio.aac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.ac4","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.avs3","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.flac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.iamf","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.int_codec","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[20311037000,200109],[20489602000,200078],[20405897000,200089],[20522590000,200096],[20502632000,200088],[20507225000,200082],[20394316000,200078],[20391890000,200086],[20599230000,200081],[20513695000,200080],[20246176000,200081],[20346665000,200085],[20395414000,200077],[20433193000,200087]],"completions":5955,"idle":[[795506000,200762],[601211000,200687],[974529000,200756],[936292000,200708],[1350134000,200246],[994242000,200682],[1381322000,200679],[1032387000,200781],[970851000,200711],[1238462000,200232]]},{"busy":[[21575758000,200114],[22038396000,200093],[21814423000,200081],[22068836000,200098],[22103593000,200082],[21844116000,200101],[21878721000,200084],[21617443000,200076],[21656793000,200074],[21885388000,200085],[21834732000,200089],[21979439000,200083],[21687554000,200121],[21887250000,200142]],"completions":5879,"idle":[[4867745000,200136],[4725036000,200488],[4578941000,200655],[4556527000,200513],[4658008000,200493],[4879387000,200535],[4798397000,200498],[4753141000,200522],[4514308000,200517],[4875496000,200497]]},{"busy":[[21772373000,200093],[22389833000,200144],[22024832000,200112],[22146926000,200103],[22004050000,200111],[22290397000,200129],[22156462000,200116],[21980003000,200122],[22026007000,200117],[22037465000,200138],[21846938000,200125],[21792940000,200123],[21864150000,200118],[21963342000,200133]],"completions":5896,"idle":[[4865104000,200167],[5260181000,200212],[5450340000,200116],[5298707000,200106],[5177009000,200122],[5214162000,200088],[5188620000,200133],[5354109000,200131],[5257556000,200099],[5555544000,200111]]},{"busy":[[19906334000,200126],[20375701000,200092],[20283162000,200083],[20406995000,200078],[20356277000,200077],[20359452000,200081],[20313860000,200090],[20018588000,200078],[20230689000,200083],[20302996000,200080],[20216362000,200081],[20060181000,200086],[20098753000,200081],[20372375000,200078]],"completions":5902,"idle":[[3138387000,200144],[5532292000,200191],[5237110000,200172],[4684953000,200222],[5434946000,200180],[5527256000,200178],[5682720000,200177],[4848245000,200169],[4815440000,200149],[1412128000,200691]]},{"busy":[[19884409000,200089],[20132809000,200085],[20232459000,200112],[20421673000,200090],[20345353000,200098],[20176859000,200077],[20399046000,200084],[20187539000,200080],[20237311000,200077],[20283650000,200086],[20240499000,200081],[20127332000,200082],[20324861000,200032],[20223716000,200085]],"completions":5910,"idle":[[191686000,200688],[1700703000,200827],[968745000,200724],[693843000,200684],[1562083000,200682],[849855000,200793],[861192000,200802],[1045357000,200223],[1155045000,200773],[1585047000,200691]]},{"busy":[[20269307000,200109],[20340043000,200082],[20118146000,200108],[20154628000,200092],[20320589000,200090],[20263006000,200086],[20296832000,200079],[20245016000,200089],[20020434000,200083],[19870783000,200092],[19964971000,200111],[20345948000,200086],[20304126000,200084],[20138607000,200078]],"completions":5899,"idle":[[1724902000,200537],[1214598000,200682],[982340000,200779],[609389000,200743],[1136889000,200715],[1462205000,200218],[1850231000,200714],[958919000,200711],[556048000,200689],[947964000,200752]]},{"busy":[[20186654000,200109],[20026491000,200097],[20471476000,200091],[20315020000,200071],[20255056000,200081],[20308505000,200082],[20114285000,200079],[20275273000,200076],[20102659000,200084],[20252844000,200079],[20402814000,200104],[20232124000,200080],[20109067000,200081],[20133679000,200092]],"completions":5917,"idle":[[1122470000,200682],[428246000,200723],[1690129000,200726],[1085378000,200700],[893112000,200794],[1060095000,200692],[1032966000,200683],[1381033000,200791],[940625000,200191],[1081777000,200709]]}]},{"capability":"wai.audio.ivas","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.mpegh","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.opus","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.opus.packet","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.scene","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.score","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.avatar.pose","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.feed.state","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.film.linear","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.film.reel","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.generate.spectral","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.haptic.mihs","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.haptic.signal","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.avif","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jp2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jpeg","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jpegai","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jxl","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.png","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.mesh.draco","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.mesh.glb","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.bmshj2018","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.cmic","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dcvc_fm","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dcvc_rt","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.elic","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.encodec32","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.glc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.int_hyper","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[19445467000,200123],[19301522000,200068],[19015161000,200070],[19006783000,200064],[18984675000,200064],[18999109000,200066],[19306665000,200065],[19100803000,200065],[19051933000,200062],[18980402000,200066],[18987223000,200063],[19207290000,200065],[19215650000,200067],[19045219000,200065]],"completions":337511,"idle":[[655041000,200701],[1673696000,200223],[1110783000,200688],[902464000,200784],[609527000,200812],[1247525000,200681],[1484389000,200687],[912734000,200762],[820453000,200685],[1055808000,200689]]},{"busy":[[20950912000,200136],[21620190000,200082],[21495562000,200095],[21739095000,200103],[21546096000,200081],[21579771000,200080],[21150426000,200089],[18900451000,200062],[20974181000,200066],[21240432000,200064],[21234269000,200063],[21160268000,200064],[21120337000,200080],[21223451000,200064]],"completions":335202,"idle":[[4676165000,200162],[4630071000,200171],[4599189000,200171],[4649280000,200168],[4631093000,200165],[4763181000,200155],[4613958000,200164],[4729095000,200126],[4836420000,200123],[4726547000,200155]]},{"busy":[[19217878000,200156],[19084386000,200058],[18991755000,200069],[18665066000,200082],[18746146000,200062],[18733208000,200062],[18696177000,200061],[18984766000,200063],[18704203000,200064],[18733101000,200081],[18701105000,200063],[18699396000,200063],[19076360000,200070],[18812061000,200062]],"completions":334734,"idle":[[1246228000,200655],[931806000,200469],[1509703000,200818],[653103000,200838],[1117725000,200787],[959240000,200678],[932798000,200770],[1979954000,200688],[1110813000,200728],[1035652000,200688]]},{"busy":[[19270671000,200140],[19023171000,200078],[18717171000,200064],[18717004000,200064],[18873475000,200063],[18682841000,200066],[18769293000,200068],[18686244000,200064],[18780553000,200065],[18933302000,200066],[18722969000,200065],[18897964000,200067],[18794438000,200066],[18732766000,200067]],"completions":335170,"idle":[[1009286000,200641],[859148000,200819],[1201445000,200730],[1102223000,200151],[1022271000,200672],[823306000,200662],[1396809000,200769],[931135000,200747],[629743000,200682],[1684636000,200681]]},{"busy":[[19479706000,200099],[18939633000,200066],[18651532000,200064],[18811573000,200065],[18711938000,200065],[18849062000,200082],[18913771000,200065],[18676265000,200063],[18742851000,200080],[18711969000,200065],[18802021000,200069],[18916548000,200064],[18694834000,200063],[18736687000,200062]],"completions":335143,"idle":[[1626747000,200699],[1014230000,200760],[356167000,200723],[1422702000,200694],[3367135000,200117],[4342275000,200172],[5843066000,200112],[4736480000,200192],[2330171000,200722],[1249630000,200220]]},{"busy":[[19412174000,200115],[18833773000,200067],[18898498000,200072],[18754493000,200065],[18729790000,200065],[18835375000,200064],[18745521000,200081],[18943982000,200066],[18769598000,200063],[18686397000,200064],[18765890000,200063],[18668789000,200063],[19116763000,200066],[19077184000,200067]],"completions":334669,"idle":[[981578000,200731],[955868000,200637],[1310128000,200774],[1171998000,200685],[928891000,200710],[1171052000,200792],[850740000,200725],[1266796000,200780],[0,200685],[1848491000,200716]]},{"busy":[[19323006000,200117],[19142793000,200065],[18659253000,200081],[18966823000,200067],[18894561000,200072],[18665691000,200077],[18656155000,200065],[18745308000,200066],[18810688000,200064],[18890884000,200068],[18777822000,200065],[18696741000,200065],[18660397000,200082],[18646817000,200065]],"completions":334972,"idle":[[789967000,200711],[876801000,200683],[831226000,200807],[1073583000,200388],[2511864000,200120],[5112162000,200110],[4086184000,200697],[1109073000,200686],[1132586000,200498],[1259457000,200680]]}]},{"capability":"wai.neural.int_mlicpp","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[]},{"capability":"wai.neural.int_synth","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[25053590000,200095],[26770603000,200149],[27001640000,200140],[27430726000,200143],[26851929000,200148],[27439239000,200130],[27134858000,200123],[26950251000,200142],[27109255000,200248],[27104815000,200126],[27017203000,200134],[27032827000,200135],[27664738000,200138],[27244059000,200134]],"completions":762,"idle":[[1357580000,200700],[1428500000,200682],[1040092000,200856],[1296183000,200735],[922864000,200677],[767371000,200492],[830401000,200492],[999506000,200489],[1410358000,200511],[980968000,201152]]},{"busy":[[25806848000,200133],[27536997000,200155],[28104229000,200256],[27751663000,200125],[27808239000,200158],[27950504000,200144],[27406740000,200140],[27992617000,200139],[27631292000,200122],[27822047000,200126],[27940831000,200124],[27758849000,200138],[27777983000,200128],[27274071000,200144]],"completions":758,"idle":[[4745665000,200133],[4692278000,200114],[3328670000,200681],[570329000,200682],[2653381000,200175],[4708436000,200183],[4655917000,200167],[4615027000,200152],[4594031000,200134],[4743086000,200144]]},{"busy":[[26975533000,200112],[27870568000,200134],[28178993000,200141],[27481093000,200414],[28233479000,200150],[28445367000,200129],[27508008000,200136],[27897910000,200144],[27628881000,200141],[27556940000,200126],[27253397000,200162],[28651000000,200125],[28020050000,200147],[27143387000,200959]],"completions":750,"idle":[[5611206000,200166],[5430307000,200162],[5646040000,200168],[4879783000,200155],[5024169000,200171],[5623733000,200164],[5105296000,200166],[4967166000,200181],[5395854000,200153],[5698650000,200163]]},{"busy":[[25710264000,200159],[26977791000,200140],[26282653000,200141],[26684381000,200133],[26263915000,200220],[26548127000,200113],[26837784000,200170],[26758245000,200143],[26516222000,200138],[26691750000,200387],[26682046000,200181],[27353093000,200154],[26971811000,200133],[26672861000,200126]],"completions":758,"idle":[[1336433000,200714],[954235000,200319],[886765000,200670],[1246746000,200525],[822879000,200718],[1375646000,200243],[675365000,200739],[1090611000,200702],[802846000,200371],[1058875000,201048]]},{"busy":[[25864767000,200128],[27207745000,200170],[27206616000,200135],[27054524000,200144],[27545863000,200135],[26986489000,200186],[26590101000,200242],[26897276000,200137],[27172148000,200137],[27189573000,200144],[26461461000,200136],[27244166000,200141],[27182905000,200127],[27278390000,200122]],"completions":775,"idle":[[794454000,200339],[1512617000,200524],[1538739000,200641],[566880000,200686],[1314111000,200715],[860612000,200185],[946042000,200672],[1127871000,200889],[951504000,200677],[1308999000,200711]]},{"busy":[[25540122000,200131],[27162582000,200126],[26309186000,200202],[27176176000,200117],[26437293000,200132],[26829377000,200187],[26579116000,200145],[26606275000,200144],[26869902000,200313],[26491184000,200147],[27152069000,200134],[27033941000,200371],[27126298000,200141],[26536836000,200269]],"completions":756,"idle":[[1009866000,200723],[888381000,200865],[858660000,200706],[945492000,200670],[1566646000,200730],[1011712000,200547],[1143816000,200432],[816639000,200686],[916273000,200685],[1721209000,200679]]},{"busy":[[25531272000,200108],[27055883000,200158],[27318960000,200133],[27243754000,200125],[26986795000,200130],[27070774000,200227],[27201901000,200176],[27383685000,201789],[26634075000,200150],[27164092000,200128],[26909894000,200215],[27272851000,200128],[27372959000,200124],[26768223000,200142]],"completions":772,"idle":[[1609734000,200428],[925963000,200672],[824725000,200707],[659329000,200841],[1682835000,200541],[1318078000,200231],[1009713000,200727],[881516000,200680],[506077000,200689],[1161958000,200731]]}]},{"capability":"wai.neural.mbt2018_mean","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mimi","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mlicpp","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mlicv2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.snac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.video_bmshj2018","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.wavtokenizer","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.pc.gpcc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.pc.vpcc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.prior.nnc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.prior.set","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.quantum.circuit","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.quantum.circuit2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.semantic.clip","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.semantic.perco2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.4d","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.int_codec","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[]},{"capability":"wai.splat.sog","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.spz","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.tensor.nnc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.ternary.tt","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.text.xz","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.text.zstd","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.av1","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.av1.lossless","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.av2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.avc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.avs3","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.evc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.evc.baseline","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.hevc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.int_hyper","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[]},{"capability":"wai.video.int_motion","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[20028948000,200141],[19422457000,200065],[19268733000,200080],[19219389000,200062],[19446260000,200064],[19266048000,200062],[19228635000,200064],[19187789000,200062],[19164415000,200064],[19312950000,200066],[19353537000,200081],[19155306000,200063],[19162339000,200062],[19112949000,200061]],"completions":11622,"idle":[[1110538000,200715],[918440000,200710],[906005000,200701],[285279000,200728],[1917244000,200228],[935895000,200503],[1490661000,200749],[660778000,200685],[1451616000,200696],[1382176000,200244]]},{"busy":[[19252392000,200181],[19601189000,200071],[19156969000,200073],[19002908000,200065],[19258327000,200066],[18952252000,200067],[18953869000,200063],[18822101000,200063],[19121829000,200062],[19161927000,200081],[19006662000,200063],[18835863000,200061],[18972377000,200080],[19009180000,200080]],"completions":11534,"idle":[[1065512000,200641],[1288141000,200883],[1507887000,200723],[1080664000,200736],[972438000,200690],[1299569000,200733],[833331000,200678],[0,200688],[1667989000,200680],[1572185000,201025]]},{"busy":[[26188922000,202833],[23880846000,200128],[27386752000,200117],[31346752000,200121],[31998849000,200606],[29518371000,201013],[29676855000,200123],[19514708000,200090],[18956051000,200066],[19051428000,200063],[19023415000,200062],[19218321000,200061],[18997263000,200061],[19098057000,200060]],"completions":9901,"idle":[[5348768000,200144],[5037734000,200140],[4936223000,200149],[4925938000,200159],[10804907000,200128],[12975769000,200130],[21356516000,200156],[25789377000,200127],[8932399000,200131],[12268943000,200153]]},{"busy":[[19413592000,200080],[19037392000,200062],[19101642000,200065],[18912032000,200059],[18886688000,200060],[18918196000,200061],[18927198000,200063],[19313027000,200063],[18937436000,200061],[18925520000,200061],[19053672000,200062],[18924024000,200060],[19086141000,200061],[18857804000,200059]],"completions":11546,"idle":[[1162370000,200712],[998514000,200693],[1358404000,200691],[968777000,200722],[851777000,200691],[1510741000,200687],[1327263000,200454],[1357626000,200817],[787328000,200685],[832187000,200774]]},{"busy":[[19277628000,200107],[19000589000,200083],[18999704000,200080],[18983226000,200067],[19272166000,200065],[18990641000,200064],[18952374000,200063],[18984201000,200064],[18896972000,200063],[19364920000,200067],[18990610000,200063],[18930494000,200063],[18988764000,200064],[18980754000,200062]],"completions":11540,"idle":[[1275065000,200636],[906219000,200730],[797932000,200635],[1070105000,200133],[1189483000,200633],[925291000,200800],[868149000,200904],[673717000,200678],[1094319000,200488],[1415104000,200791]]},{"busy":[[21089074000,200080],[21394294000,200097],[26299116000,200136],[25908512000,200153],[25596958000,200142],[26537507000,200709],[25153576000,200134],[25525322000,200828],[25767100000,200125],[26251770000,200128],[26162633000,200146],[21773426000,200109],[19043845000,200067],[18857484000,200062]],"completions":10543,"idle":[[1402989000,200714],[626966000,200734],[1035667000,200633],[781927000,200167],[1003366000,200634],[1278651000,200570],[862138000,200676],[1225156000,200714],[3502535000,200109],[5117808000,200118]]},{"busy":[[19608468000,200100],[19213316000,200083],[19011087000,200063],[18907484000,200075],[19010202000,200080],[20322298000,200077],[19104053000,200070],[18841433000,200080],[18747917000,200080],[18757926000,200080],[18817691000,200079],[19011224000,200082],[18875733000,200079],[18832812000,200062]],"completions":11537,"idle":[[1765519000,200688],[968699000,200683],[1479370000,201186],[802754000,200705],[572374000,200744],[1543743000,200515],[951260000,200838],[1045478000,200355],[761741000,200715],[807468000,200688]]}]},{"capability":"wai.video.j2k","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.vp8","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.vp9","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.vvc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.live","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.model","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[27082813000,200114],[25599857000,200092],[25448971000,200106],[28707515000,200121],[26145880000,200127],[25097365000,200132],[23560458000,200126],[23858675000,200122],[23991512000,200122],[24010263000,200117],[25764659000,200108],[25471400000,200105],[25818382000,200110],[23390148000,200118]],"completions":1256,"idle":[[1289270000,200164],[4584923000,200141],[3955133000,200132],[10120295000,200131],[23844715000,200143],[24412327000,200140],[23343656000,200164],[21044292000,200178],[18505665000,200524],[18922086000,200201]]},{"busy":[[21876951000,200117],[22115632000,200141],[22173902000,200116],[22206066000,200098],[21601315000,200122],[21982307000,200120],[22279335000,200107],[22480603000,200103],[22465651000,200123],[22156233000,200130],[22052586000,200124],[21943338000,200121],[21898511000,200116],[22410935000,200120]],"completions":1276,"idle":[[4549005000,200171],[4622335000,200168],[4790996000,200154],[4806377000,200174],[4828181000,200138],[4874122000,200148],[4751738000,200152],[5160973000,200141],[4720321000,200143],[4797375000,200150]]},{"busy":[[22326894000,200109],[22557260000,200119],[22840005000,200113],[22717056000,200112],[22615637000,200105],[22704926000,200109],[22386248000,200106],[22508312000,200110],[22994248000,200115],[22689592000,200123],[22536676000,200113],[22452895000,200127],[22403977000,200113],[22502682000,200110]],"completions":1297,"idle":[[5565172000,200126],[5124674000,200109],[5033828000,200154],[5037734000,200149],[5144723000,200157],[4971239000,200133],[4992570000,200117],[5038863000,200125],[5050535000,200115],[5170494000,200117]]},{"busy":[[21055003000,200102],[21014614000,200082],[21162709000,200077],[20747340000,200075],[20382125000,200085],[20940247000,200082],[21224518000,200079],[20954590000,200076],[20761088000,200081],[21097938000,200092],[21027263000,200072],[20930345000,200072],[20662017000,200101],[21075174000,200070]],"completions":1314,"idle":[[1775512000,200721],[1017190000,200847],[720193000,200679],[947277000,200711],[1083135000,200442],[1259777000,200682],[1246701000,200641],[710199000,200821],[857270000,200636],[942136000,200300]]},{"busy":[[20595661000,200123],[20786355000,200066],[20954666000,200080],[20922898000,200081],[20736248000,200106],[20921937000,200081],[21067850000,200075],[20678084000,200075],[20903521000,200072],[20654785000,200083],[20449184000,200079],[20731899000,200073],[20981367000,200074],[20846700000,200095]],"completions":1297,"idle":[[1046775000,200775],[1039375000,200671],[1270656000,200837],[1579981000,200469],[3559615000,200124],[5203405000,200097],[4131669000,200101],[3622188000,200820],[1186035000,200690],[1038840000,200793]]},{"busy":[[20563375000,200095],[20850408000,200101],[20931412000,200074],[20761149000,200075],[20931672000,200067],[20691755000,200091],[20884617000,200079],[20777489000,200106],[20933229000,200085],[20677702000,200075],[20754695000,200075],[20728359000,200077],[20969985000,200088],[20833456000,200080]],"completions":1304,"idle":[[814014000,200690],[1218794000,200894],[1469391000,200249],[804402000,200773],[709954000,200720],[875535000,200769],[881332000,200638],[1436312000,200884],[857256000,200647],[898711000,200803]]},{"busy":[[21008069000,200144],[21027783000,200102],[20503959000,200050],[20729962000,200072],[20602923000,200075],[20478113000,200081],[20928971000,200100],[20826636000,200071],[20642884000,200074],[20744014000,200071],[20757975000,200077],[20656784000,200071],[20698438000,200078],[20802299000,200072]],"completions":1298,"idle":[[670162000,200687],[1497557000,200694],[1012384000,200870],[1243588000,200704],[1620644000,200268],[710610000,200728],[1607675000,200692],[1009607000,200685],[1043036000,200417],[1184097000,200782]]}]},{"capability":"wai.world.replay","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.scene","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]}],"wai_sink_readings":1} \ No newline at end of file diff --git a/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/table.json b/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/table.json index 25721b19f..68d50388a 100644 --- a/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/table.json +++ b/wai/live-conformance/sink-energy/tables/linux-x86_64-32-core/table.json @@ -2,8 +2,8 @@ "wai_sink_table": 1, "signer": "5f8823017ac5f259575e1c43369a132cd44159e64c3a6dcdda69bb6b3b782c96", "procedure": "wai-sink-table/1", - "procedure_sha256": "5543efbdc3a44ad3cfb5e1a6f23fdc0f66487ca9e4d5b01a5ba89b4a8c3bda1c", - "readings_sha256": "24ed273b1b042c696809062639eb0cecfdb9b00d18f0707450c2ff92e5d820a4", + "procedure_sha256": "3f8a797d4cb19f4e65c518fb099b9fb97e9d73a649df0f6f44a0382630b99edc", + "readings_sha256": "773d265a655813fd4ecad024a2f3c3ee53f51301d198b10e2ddfd3e7f53c1fd3", "machine": { "os": "linux 7.0.0-38-generic", "arch": "x86_64", @@ -16,7 +16,7 @@ "toolchain": "rustc 1.99.0 (b940084d7 2026-09-28)", "target": "x86_64-unknown-linux-gnu" }, - "measured": "2026-10-22", + "measured": "2026-10-25", "rounds": 7, "sample_us": 200000, "counters": [ @@ -157,7 +157,7 @@ "width": null, "height": null, "fabrics": [ - "package" + "cpu" ], "coverage": "Partial", "workers": 8, @@ -165,142 +165,142 @@ { "idle": { "n": 10, - "mean_uw": 5514308, - "sd_uw": 1885172, + "mean_uw": 5122142, + "sd_uw": 1207090, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 101462312, - "sd_uw": 645969, + "mean_uw": 102120477, + "sd_uw": 477704, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801267, - "completions": 5935 + "window_us": 2801197, + "completions": 5955 }, { "idle": { "n": 10, - "mean_uw": 6079399, - "sd_uw": 1967228, + "mean_uw": 23546542, + "sd_uw": 695697, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 101100129, - "sd_uw": 955385, + "mean_uw": 109188574, + "sd_uw": 835478, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801281, - "completions": 5872 + "window_us": 2801323, + "completions": 5879 }, { "idle": { "n": 10, - "mean_uw": 5519585, - "sd_uw": 1393623, + "mean_uw": 26293826, + "sd_uw": 919859, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 101226765, - "sd_uw": 839652, + "mean_uw": 110039409, + "sd_uw": 891907, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801136, - "completions": 5911 + "window_us": 2801684, + "completions": 5896 }, { "idle": { "n": 10, - "mean_uw": 5237916, - "sd_uw": 2477984, - "live_intervals": 9, - "longest_live_run": 9 - }, - "busy": { - "n": 14, - "mean_uw": 100359320, - "sd_uw": 1497245, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2801246, - "completions": 5859 - }, - { - "idle": { - "n": 10, - "mean_uw": 23286625, - "sd_uw": 800439, + "mean_uw": 23134440, + "sd_uw": 6749647, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 107513521, - "sd_uw": 824599, + "mean_uw": 101136086, + "sd_uw": 782661, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2802144, - "completions": 5816 + "window_us": 2801194, + "completions": 5902 }, { "idle": { "n": 10, - "mean_uw": 26054217, - "sd_uw": 650712, + "mean_uw": 5288432, + "sd_uw": 2305261, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 127820691, - "sd_uw": 20377626, + "mean_uw": 101107304, + "sd_uw": 672583, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2812569, - "completions": 4885 + "window_us": 2801158, + "completions": 5910 }, { "idle": { "n": 10, - "mean_uw": 5858319, - "sd_uw": 603372, + "mean_uw": 5703795, + "sd_uw": 2150707, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 100904550, - "sd_uw": 472314, + "mean_uw": 100901582, + "sd_uw": 752633, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801988, - "completions": 5913 + "window_us": 2801269, + "completions": 5899 + }, + { + "idle": { + "n": 10, + "mean_uw": 5339857, + "sd_uw": 1614266, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 101094296, + "sd_uw": 622273, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801206, + "completions": 5917 } ], "status": "figure", "reason": null, - "median": 1, + "median": 5, "range": [ - 40553, - 58545 + 36996, + 45597 ], - "joules_micro": 45299, + "joules_micro": 45176, "energy_provenance": "OnChipCounter", "energy_uncertainty": { - "relative_ppm": 114657, - "window_us": 2801281 + "relative_ppm": 54961, + "window_us": 2801269 }, "energy_counter_filtering": "Unknown", "energy_calibration_ref": null @@ -982,7 +982,7 @@ "width": 16, "height": 16, "fabrics": [ - "package" + "cpu" ], "coverage": "Partial", "workers": 8, @@ -990,142 +990,142 @@ { "idle": { "n": 10, - "mean_uw": 5576187, - "sd_uw": 1658807, + "mean_uw": 5219634, + "sd_uw": 1717421, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 94866512, - "sd_uw": 857431, + "mean_uw": 95555297, + "sd_uw": 747921, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2807113, - "completions": 336528 + "window_us": 2800973, + "completions": 337511 }, { "idle": { "n": 10, - "mean_uw": 5035838, - "sd_uw": 2634610, + "mean_uw": 23409266, + "sd_uw": 382946, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 94919695, - "sd_uw": 1009752, + "mean_uw": 105648546, + "sd_uw": 3443411, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801009, - "completions": 335253 + "window_us": 2801129, + "completions": 335202 }, { "idle": { "n": 10, - "mean_uw": 5606274, - "sd_uw": 1597893, + "mean_uw": 5718167, + "sd_uw": 1835128, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 94001564, - "sd_uw": 723204, + "mean_uw": 94196341, + "sd_uw": 916639, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2800990, - "completions": 334491 + "window_us": 2801016, + "completions": 334734 }, { "idle": { "n": 10, - "mean_uw": 14052138, - "sd_uw": 5945798, + "mean_uw": 5312631, + "sd_uw": 1510634, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 95586085, - "sd_uw": 2849612, + "mean_uw": 94109748, + "sd_uw": 805938, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801062, - "completions": 332432 + "window_us": 2801003, + "completions": 335170 }, { "idle": { "n": 10, - "mean_uw": 23712849, - "sd_uw": 767096, + "mean_uw": 13124814, + "sd_uw": 9200114, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 105595035, - "sd_uw": 1938641, + "mean_uw": 94123854, + "sd_uw": 1040898, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801452, - "completions": 331496 + "window_us": 2800972, + "completions": 335143 }, { "idle": { "n": 10, - "mean_uw": 5771363, - "sd_uw": 1293526, - "live_intervals": 10, - "longest_live_run": 10 + "mean_uw": 5223694, + "sd_uw": 2320080, + "live_intervals": 9, + "longest_live_run": 8 }, "busy": { "n": 14, - "mean_uw": 101549066, - "sd_uw": 5604118, + "mean_uw": 94337696, + "sd_uw": 1023210, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801202, - "completions": 332948 + "window_us": 2800981, + "completions": 334669 }, { "idle": { "n": 10, - "mean_uw": 5362971, - "sd_uw": 2374919, + "mean_uw": 9371161, + "sd_uw": 7665087, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 94531545, - "sd_uw": 927650, + "mean_uw": 94086060, + "sd_uw": 1018648, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801018, - "completions": 334142 + "window_us": 2801019, + "completions": 334972 } ], "status": "figure", "reason": null, - "median": 0, + "median": 2, "range": [ - 687, - 806 + 677, + 750 ], - "joules_micro": 745, + "joules_micro": 740, "energy_provenance": "OnChipCounter", "energy_uncertainty": { - "relative_ppm": 46111, - "window_us": 2807113 + "relative_ppm": 28478, + "window_us": 2801016 }, "energy_counter_filtering": "Unknown", "energy_calibration_ref": null @@ -1163,7 +1163,7 @@ "width": 128, "height": 128, "fabrics": [ - "package" + "cpu" ], "coverage": "Partial", "workers": 8, @@ -1171,142 +1171,142 @@ { "idle": { "n": 10, - "mean_uw": 5455096, - "sd_uw": 1306145, + "mean_uw": 5498185, + "sd_uw": 1233643, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 133971609, - "sd_uw": 2333543, + "mean_uw": 134834880, + "sd_uw": 3024640, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2802176, + "window_us": 2801985, "completions": 762 }, { "idle": { "n": 10, - "mean_uw": 5656585, - "sd_uw": 1121368, + "mean_uw": 19633629, + "sd_uw": 6917369, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 133332495, - "sd_uw": 2324338, + "mean_uw": 137957963, + "sd_uw": 2828501, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801943, - "completions": 765 + "window_us": 2802032, + "completions": 758 }, { "idle": { "n": 10, - "mean_uw": 5698216, - "sd_uw": 1406491, + "mean_uw": 26669124, + "sd_uw": 1574077, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 134720541, - "sd_uw": 2340209, + "mean_uw": 138724675, + "sd_uw": 2549650, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2802203, + "window_us": 2803020, + "completions": 750 + }, + { + "idle": { + "n": 10, + "mean_uw": 5109991, + "sd_uw": 1189446, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 133085685, + "sd_uw": 1938100, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2802338, + "completions": 758 + }, + { + "idle": { + "n": 10, + "mean_uw": 5444098, + "sd_uw": 1603787, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 134857549, + "sd_uw": 2141324, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2802084, + "completions": 775 + }, + { + "idle": { + "n": 10, + "mean_uw": 5421194, + "sd_uw": 1541343, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 133395991, + "sd_uw": 2247741, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2802559, + "completions": 756 + }, + { + "idle": { + "n": 10, + "mean_uw": 5274525, + "sd_uw": 1928396, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 134789382, + "sd_uw": 2324455, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2803733, "completions": 772 - }, - { - "idle": { - "n": 10, - "mean_uw": 7603826, - "sd_uw": 6422845, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 133293067, - "sd_uw": 3658548, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2802234, - "completions": 763 - }, - { - "idle": { - "n": 10, - "mean_uw": 23850591, - "sd_uw": 984244, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 135624127, - "sd_uw": 2021214, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2802974, - "completions": 754 - }, - { - "idle": { - "n": 10, - "mean_uw": 26176193, - "sd_uw": 579893, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 138406443, - "sd_uw": 3331892, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2803050, - "completions": 759 - }, - { - "idle": { - "n": 10, - "mean_uw": 5494125, - "sd_uw": 1657567, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 133669280, - "sd_uw": 2813073, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2802127, - "completions": 764 } ], "status": "figure", "reason": null, - "median": 1, + "median": 6, "range": [ - 412303, - 470138 + 416570, + 473106 ], - "joules_micro": 465202, + "joules_micro": 467945, "energy_provenance": "OnChipCounter", "energy_uncertainty": { - "relative_ppm": 35889, - "window_us": 2801943 + "relative_ppm": 34878, + "window_us": 2803733 }, "energy_counter_filtering": "Unknown", "energy_calibration_ref": null @@ -2057,7 +2057,7 @@ "width": 96, "height": 96, "fabrics": [ - "package" + "cpu" ], "coverage": "Partial", "workers": 8, @@ -2065,142 +2065,142 @@ { "idle": { "n": 10, - "mean_uw": 5206053, - "sd_uw": 1367502, + "mean_uw": 5514081, + "sd_uw": 2340749, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 96420380, - "sd_uw": 1500176, + "mean_uw": 96511875, + "sd_uw": 1141312, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2805554, - "completions": 11605 + "window_us": 2800997, + "completions": 11622 }, { "idle": { "n": 10, - "mean_uw": 5876724, - "sd_uw": 1405013, - "live_intervals": 10, - "longest_live_run": 10 + "mean_uw": 5622342, + "sd_uw": 2392495, + "live_intervals": 9, + "longest_live_run": 7 }, "busy": { "n": 14, - "mean_uw": 95491096, - "sd_uw": 1052863, + "mean_uw": 95358973, + "sd_uw": 1008522, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2800995, - "completions": 11545 - }, - { - "idle": { - "n": 10, - "mean_uw": 5387577, - "sd_uw": 1425772, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 96938996, - "sd_uw": 1787157, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2801053, - "completions": 11542 - }, - { - "idle": { - "n": 10, - "mean_uw": 5658379, - "sd_uw": 973746, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 96727857, - "sd_uw": 491223, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2801059, + "window_us": 2801076, "completions": 11534 }, { "idle": { "n": 10, - "mean_uw": 53388722, - "sd_uw": 51113657, + "mean_uw": 56149019, + "sd_uw": 36352822, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 129064224, - "sd_uw": 12747575, + "mean_uw": 118975929, + "sd_uw": 26248648, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801390, - "completions": 11163 + "window_us": 2805404, + "completions": 9901 }, { "idle": { "n": 10, - "mean_uw": 24582848, - "sd_uw": 677840, + "mean_uw": 5558362, + "sd_uw": 1301582, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 106439462, - "sd_uw": 862506, + "mean_uw": 95075335, + "sd_uw": 821533, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801188, - "completions": 11467 + "window_us": 2800877, + "completions": 11546 }, { "idle": { "n": 10, - "mean_uw": 6028693, - "sd_uw": 2105055, + "mean_uw": 5091475, + "sd_uw": 1141810, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 95461548, - "sd_uw": 2138011, + "mean_uw": 95185781, + "sd_uw": 726281, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2800980, - "completions": 11525 + "window_us": 2800975, + "completions": 11540 + }, + { + "idle": { + "n": 10, + "mean_uw": 8403504, + "sd_uw": 7273137, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 119640751, + "sd_uw": 14185434, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2802916, + "completions": 10543 + }, + { + "idle": { + "n": 10, + "mean_uw": 5330063, + "sd_uw": 1952834, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 95342019, + "sd_uw": 2113559, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801090, + "completions": 11537 } ], "status": "figure", "reason": null, - "median": 1, + "median": 6, "range": [ - 18984, - 22210 + 17795, + 29562 ], - "joules_micro": 21734, + "joules_micro": 21847, "energy_provenance": "OnChipCounter", "energy_uncertainty": { - "relative_ppm": 42849, - "window_us": 2800995 + "relative_ppm": 155484, + "window_us": 2801090 }, "energy_counter_filtering": "Unknown", "energy_calibration_ref": null @@ -2330,7 +2330,7 @@ "width": 64, "height": 64, "fabrics": [ - "package" + "cpu" ], "coverage": "Partial", "workers": 8, @@ -2338,142 +2338,142 @@ { "idle": { "n": 10, - "mean_uw": 5789285, - "sd_uw": 2214244, + "mean_uw": 74934767, + "sd_uw": 45383139, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 104629158, - "sd_uw": 845488, + "mean_uw": 126336935, + "sd_uw": 7373244, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801230, - "completions": 1318 + "window_us": 2801620, + "completions": 1256 }, { "idle": { "n": 10, - "mean_uw": 5382701, - "sd_uw": 1593445, + "mean_uw": 23932324, + "sd_uw": 815073, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 104122349, - "sd_uw": 653095, + "mean_uw": 110521488, + "sd_uw": 1243457, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801235, - "completions": 1306 + "window_us": 2801658, + "completions": 1276 }, { "idle": { "n": 10, - "mean_uw": 5813918, - "sd_uw": 1476122, + "mean_uw": 25548291, + "sd_uw": 857401, "live_intervals": 10, "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 103260101, - "sd_uw": 1145401, + "mean_uw": 112877715, + "sd_uw": 927400, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801083, - "completions": 1301 + "window_us": 2801584, + "completions": 1297 }, { "idle": { "n": 10, - "mean_uw": 4948666, - "sd_uw": 2145987, - "live_intervals": 9, - "longest_live_run": 9 + "mean_uw": 5262623, + "sd_uw": 1570569, + "live_intervals": 10, + "longest_live_run": 10 }, "busy": { "n": 14, - "mean_uw": 103771197, - "sd_uw": 1916403, + "mean_uw": 104612541, + "sd_uw": 1124643, "live_intervals": 14, "longest_live_run": 14 }, - "window_us": 2801249, + "window_us": 2801146, + "completions": 1314 + }, + { + "idle": { + "n": 10, + "mean_uw": 11816123, + "sd_uw": 7929676, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 103967964, + "sd_uw": 858401, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801163, + "completions": 1297 + }, + { + "idle": { + "n": 10, + "mean_uw": 4965587, + "sd_uw": 1366827, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 103988523, + "sd_uw": 594911, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801168, + "completions": 1304 + }, + { + "idle": { + "n": 10, + "mean_uw": 5781311, + "sd_uw": 1691568, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 103675234, + "sd_uw": 827105, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801138, "completions": 1298 - }, - { - "idle": { - "n": 10, - "mean_uw": 6155246, - "sd_uw": 3166611, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 103349564, - "sd_uw": 813677, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2801119, - "completions": 1299 - }, - { - "idle": { - "n": 10, - "mean_uw": 5846235, - "sd_uw": 1572520, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 103439219, - "sd_uw": 961481, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2802463, - "completions": 1299 - }, - { - "idle": { - "n": 10, - "mean_uw": 5522209, - "sd_uw": 1330991, - "live_intervals": 10, - "longest_live_run": 10 - }, - "busy": { - "n": 14, - "mean_uw": 103720668, - "sd_uw": 697517, - "live_intervals": 14, - "longest_live_run": 14 - }, - "window_us": 2801110, - "completions": 1311 } ], "status": "figure", "reason": null, - "median": 0, + "median": 4, "range": [ - 208943, - 212616 + 114293, + 212064 ], - "joules_micro": 209435, + "joules_micro": 198411, "energy_provenance": "OnChipCounter", "energy_uncertainty": { - "relative_ppm": 7647, - "window_us": 2801230 + "relative_ppm": 142251, + "window_us": 2801163 }, "energy_counter_filtering": "Unknown", "energy_calibration_ref": null @@ -2525,5 +2525,5 @@ "energy_calibration_ref": null } ], - "sig": "d5cf3549f288ef3f3e6de7e4829b6d8e4083a34e97dd67fb670e46aa5fba668502c5da505e9b896852ef1438a0fb96680b65c75fade139aecdb0690ec5254b09" + "sig": "d8c7e703182bec5c87b8649a0b9c81785e1f08718d3c54d617088f05cb29798c24cf7b9e8d3e1cb6c4e1db7fb1166248196d428b5ec87e36f6269ce5c4f22609" } diff --git a/wai/live-conformance/sink_energy_verify.py b/wai/live-conformance/sink_energy_verify.py index 284b6fcc8..e1df185fa 100644 --- a/wai/live-conformance/sink_energy_verify.py +++ b/wai/live-conformance/sink_energy_verify.py @@ -19,8 +19,8 @@ live_metering_verify.py (live-metering §3.6, §3.5). Everything else is here, f `N = 0`), the table formula against the held table, the estimator `estimator-cpu-time/1` (energy-measurement §9.4), and the chain (§5.3); - the session receipt (§6, step 6): each part, the display estimator - `estimator-display/1` (energy-measurement §9.5), a hop's share against its `WMH1` - record, the streams against the segment receipts, and the sink-decode part's figure, + `estimator-display/1` (energy-measurement §9.5), a hop's figure under the sole-user + rule against its `WMH1` record, the streams against the segment receipts, and the sink-decode part's figure, class and declaration ⌈Σ(Fᵢ·páµ¢)/ΣFᵢ⌉ (§6.3); all with Python's integers and no floating point. @@ -78,8 +78,8 @@ MINIMUM_US = {"macos-task": 1000000, "macos-gpu": 1000000, "linux-rapl": 1000000 CPU_EST = "estimator-cpu-time/1" DISPLAY_EST = "estimator-display/1" PROFILES = frozenset(MINIMUM_US) | {CPU_EST, DISPLAY_EST} # §2 *Profile* -FABRICS = {"macos-task": ["cpu"], CPU_EST: ["cpu"], "macos-gpu": ["gpu"], "linux-rapl": ["package"], - "linux-nvml": ["board"]} # §4.2 +# §4.2, in energy-measurement §9.1's fabrics. +FABRICS = {"macos-task": ["cpu"], "linux-rapl": ["cpu"], CPU_EST: ["cpu"], "macos-gpu": ["gpu"], "linux-nvml": ["gpu"]} # energy-measurement §2, strongest first. CLASSES = ("CalibratedInstrument", "OnChipCounter", "HwShunt", "ModelBased", "Estimator") RANK = {c: i for i, c in enumerate(CLASSES)} @@ -93,7 +93,8 @@ ROW_REASONS = ABSENT_REASONS + ROUND_RULES + ("noisy",) # §8 *row reasons* UNMETERED_REASONS = ("no_counter", "rail_not_live", "window_short", "no_marginal", "below_attribution_floor", "figure_zero", "not_declarable", "not_reported") # §8 *unmetered reasons* PARTS = ("origin", "relay", "sink_decode", "display") # §7 step 6's order -PART_REASONS = {"origin": ("not_reported", "hop_unmetered"), "relay": ("not_reported", "hop_unmetered"), +PART_REASONS = {"origin": ("not_reported", "hop_unmetered", "hop_shared"), + "relay": ("not_reported", "hop_unmetered", "hop_shared"), "sink_decode": ("unmetered",), "display": ("display_undeclared", "unmetered")} PART_COVERAGE = {"origin": "Partial", "relay": "Partial", "display": "Whole"} # §6.2 FILTER_BYTE = {0: "Unknown", 1: "Off", 2: "On"} # energy-measurement §2.7 @@ -229,7 +230,8 @@ def jcs(v): # ---------------------------------------------------------------- §7 step 1: shape -INT, STR, HEX32, HEX64, DECODER, DATE = "int", "str", "hex32", "hex64", "decoder", "date" +INT, STR, HEX32, HEX64, DECODER, DATE, FABRIC_LIST = "int", "str", "hex32", "hex64", "decoder", "date", "fabrics" +FABRIC_TERMS = ("cpu", "gpu", "media", "neural") # energy-measurement §9.1 def nul(s): @@ -305,6 +307,9 @@ def fits(v, s): return type(v) is str and decoder_ok(v) if s == DATE: return type(v) is str and date_ok(v) + if s == FABRIC_LIST: + # §7 step 1: every member one of energy-measurement §9.1's fabrics, none twice. + return type(v) is list and all(type(x) is str and x in FABRIC_TERMS for x in v) and len(set(v)) == len(v) tag = s[0] if tag == "null": return v is None or fits(v, s[1]) @@ -334,7 +339,7 @@ COVERAGE = nul(enum("Whole", "Partial")) UNIT = enum("image", "frame", "content-ms", "byte") PATH = enum("software", "hardware") ROW = obj(dict({ - "capability": STR, "path": PATH, "decoder": DECODER, "profile": nul(enum(*MINIMUM_US)), "fabrics": arr(STR), + "capability": STR, "path": PATH, "decoder": DECODER, "profile": nul(enum(*MINIMUM_US)), "fabrics": FABRIC_LIST, "coverage": COVERAGE, "unit": UNIT, "units": INT, "width": nul(INT), "height": nul(INT), "workers": INT, "status": enum("figure", "relative", "unmetered", "absent"), "reason": nul(enum(*ROW_REASONS)), "rounds": arr(obj({"idle": SUMMARY, "busy": SUMMARY, "window_us": INT, "completions": INT})), @@ -355,7 +360,7 @@ SCHEMA = { "prev": nul(HEX32), "object": HEX32, "capability": STR, "path": PATH, "decoder": DECODER, "unit": UNIT, "units": INT, "width": nul(INT), "height": nul(INT), "media_us": INT, "method": enum("measured", "table", "estimator", "unmetered"), "profile": nul(enum(*PROFILES)), - "fabrics": arr(STR), "coverage": COVERAGE, + "fabrics": FABRIC_LIST, "coverage": COVERAGE, "bracket": nul(obj({"idle": SUMMARY, "busy": SUMMARY, "window_us": INT, "sample_us": INT})), "table": nul(obj({"id": HEX32, "row": INT, "per_completion_uj": INT, "units_per_completion": INT})), "estimator": nul(obj({"method": const(CPU_EST), "cpu_us": INT, "active_mw": INT, "basis": STR, @@ -371,7 +376,7 @@ SCHEMA = { "relative_ppm": INT})), "split": obj({p: obj(dict({ "status": enum("figure", "absent"), - "reason": nul(enum("not_reported", "hop_unmetered", "unmetered", "display_undeclared")), + "reason": nul(enum("not_reported", "hop_unmetered", "hop_shared", "unmetered", "display_undeclared")), "coverage": COVERAGE, "source": nul(obj({"kind": enum("live-metering-hop", "other"), "record": HEX32, "hop_joules": INT, "hop_class": STR, "share_num": INT, "share_den": INT})), @@ -899,18 +904,20 @@ def expected_decode(segments): def check_hop(name, part, held_hops): - """§6.4 for the `origin` or `relay` part (`split_hop_mismatch`).""" + """§6.4 for the `origin` or `relay` part (`split_hop_mismatch`): the sole-user rule, the + figure, the class and evidence, and, holding the record, the record.""" if part["status"] != "figure": return src = part["source"] hj, num, den = src["hop_joules"], src["share_num"], src["share_den"] need(hj >= 1 and 1 <= num <= den, "split_hop_mismatch", "%s: hop_joules or the share is out of range" % name) + need(100 * num >= 99 * den, "split_hop_mismatch", + "%s: %d of %d is below the sole user's 99%%; a shared hop is absent for hop_shared (§6.4)" % (name, num, den)) j = share_of(hj, num, den) need(j >= 1 and part["joules_micro"] == j, "split_hop_mismatch", - "%s: joules_micro %d, the share gives %d" % (name, part["joules_micro"], j)) - want = src["hop_class"] if num == den else "Estimator" - need(part["energy_provenance"] == want, "split_hop_mismatch", - "%s: class %s, the class rule gives %s" % (name, part["energy_provenance"], want)) + "%s: joules_micro %d, §6.4 gives %d" % (name, part["joules_micro"], j)) + need(part["energy_provenance"] == src["hop_class"], "split_hop_mismatch", + "%s: class %s, the hop's is %s (§6.4)" % (name, part["energy_provenance"], src["hop_class"])) if src["kind"] != "live-metering-hop" or src["record"] not in held_hops: return b = held_hops[src["record"]] @@ -922,18 +929,17 @@ def check_hop(name, part, held_hops): need(x["joules"] == hj, "split_hop_mismatch", "%s: the record's joules %d, hop_joules %d" % (name, x["joules"], hj)) label = LMV.LABELS.get(x["class"][0]) if x["class"] not in (None, LMV.INVALID) else None need(label == src["hop_class"], "split_hop_mismatch", "%s: the record's class is %s" % (name, label)) + c = x["class"] + if c[0] == 4: + ev = ({"relative_ppm": c[1], "window_us": c[2]}, FILTER_BYTE[c[3]], None) + elif c[0] == 5: + ev = ({"relative_ppm": c[1], "window_us": c[2]}, None, c[3]) + else: + ev = (None, None, None) + got = (part["energy_uncertainty"], part["energy_counter_filtering"], part["energy_calibration_ref"]) + need(got == ev, "split_hop_mismatch", "%s: the part's evidence is not the record's" % name) need(sum(x["sub"]) == den, "split_hop_mismatch", "%s: share_den %d, the record's subscriber-microseconds sum to %d" % (name, den, sum(x["sub"]))) - if num == den: - c = x["class"] - if c[0] == 4: - ev = ({"relative_ppm": c[1], "window_us": c[2]}, FILTER_BYTE[c[3]], None) - elif c[0] == 5: - ev = ({"relative_ppm": c[1], "window_us": c[2]}, None, c[3]) - else: - ev = (None, None, None) - got = (part["energy_uncertainty"], part["energy_counter_filtering"], part["energy_calibration_ref"]) - need(got == ev, "split_hop_mismatch", "%s: a whole share whose evidence is not the record's" % name) def verify_session(rec, segments, tables, held_hops): @@ -1530,16 +1536,19 @@ def mutations(doc, tables): notes.append("no reason is refused by exactly one case (the coverage mutation needs one)") # 28–29. the measured tables - if tables: - sink, files = tables[0] - if files.get("readings.json") and files.get("procedure.txt"): - t2 = copy.deepcopy(tables) - t2[0][1]["readings.json"] = files["readings.json"].replace(b"]]", b"] ]", 1) if b"]]" in files["readings.json"] \ - else files["readings.json"] + b"\n" - out.append(("a measured table's readings", doc, t2, None, None)) - t3 = copy.deepcopy(tables) - t3[0][1]["procedure.txt"] = bytes([files["procedure.txt"][0] ^ 1]) + files["procedure.txt"][1:] - out.append(("a measured table's procedure", doc, t3, None, None)) + # on the first measured table that holds, alone, so that the defect is what fails + good = next((t for t in tables if not check_tables([t], quiet=True)[0]), None) + if good is not None: + sink, files = good + t2 = [(sink, dict(files))] + t2[0][1]["readings.json"] = files["readings.json"].replace(b"]]", b"] ]", 1) if b"]]" in files["readings.json"] \ + else files["readings.json"] + b"\n" + out.append(("a measured table's readings", doc, t2, None, None)) + t3 = [(sink, dict(files))] + t3[0][1]["procedure.txt"] = bytes([files["procedure.txt"][0] ^ 1]) + files["procedure.txt"][1:] + out.append(("a measured table's procedure", doc, t3, None, None)) + elif tables: + notes.append("no measured table holds, so the two table mutations have nothing to mutate") return out, notes @@ -1675,7 +1684,9 @@ def main(argv): tfails, lines = check_tables(tables) for line in lines: print(line) - print("%s %d measured tables accepted with their readings and procedures" % ("PASS" if not tfails else "FAIL", len(tables))) + n_bad = sum(1 for t in tables if check_tables([t], quiet=True)[0]) + print("%s %d of %d measured tables accepted with their readings and procedures" + % ("PASS" if not tfails else "FAIL", len(tables) - n_bad, len(tables))) fail += bool(tfails) muts, notes = mutations(doc, tables) for n in notes: diff --git a/wai/wai-rs/src/sink_energy.rs b/wai/wai-rs/src/sink_energy.rs index a0e870f41..60a786d2b 100644 --- a/wai/wai-rs/src/sink_energy.rs +++ b/wai/wai-rs/src/sink_energy.rs @@ -462,7 +462,7 @@ fn picture_shape(v: &Value, what: &str) -> R<()> { ensure(decoder_ok(d), Reason::RecordMalformed, || format!("{what}.decoder {d:?} does not follow its grammar"))?; let c = v["capability"].as_str().unwrap_or(""); ensure(registered(c), Reason::RecordMalformed, || format!("{what}.capability {c:?} is not registered"))?; - let f = v["fabrics"].as_array().map(|a| a.iter().all(|x| fits(x, Ty::Term(FABRICS)))).unwrap_or(false); + let f = v["fabrics"].as_array().is_some_and(|a| a.iter().all(|x| fits(x, Ty::Term(FABRICS))) && (1..a.len()).all(|i| !a[..i].contains(&a[i]))); ensure(f, Reason::RecordMalformed, || format!("{what}.fabrics is not a list of fabrics")) } diff --git a/wai/wai-rs/src/sink_energy/tests.rs b/wai/wai-rs/src/sink_energy/tests.rs index 8d1548e42..5c89124f2 100644 --- a/wai/wai-rs/src/sink_energy/tests.rs +++ b/wai/wai-rs/src/sink_energy/tests.rs @@ -14,7 +14,7 @@ fn a_bracket_summary_is_its_intervals_in_integers() { let x = Summary::of_intervals(&iv).unwrap(); assert_eq!((x.n, x.mean_uw, x.sd_uw, x.live_intervals, x.longest_live_run), (3, 4_000_000, 2_000_000, 3, 3)); // 1, 2 µW: mean 1.5 rounds up to 2; sd √0.5 = 0.707… rounds up to 1. - let y = Summary::of_intervals(&[(1, 2000), (2, 2000)]).unwrap(); + let y = Summary::of_intervals(&[(1, 1000), (2, 1000)]).unwrap(); assert_eq!((y.mean_uw, y.sd_uw), (2, 1)); // An interval with no advance breaks the run; one of no length is no reading. let z = Summary::of_intervals(&[(5, 10), (0, 10), (5, 10), (5, 10)]).unwrap(); -- 2.54.0 (Apple Git-157) From fe7038d9564bd8e4d8689440547decea2a6e126a Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:19:56 -0400 Subject: [PATCH 061/242] sink-energy: the session's split is the stream log's hop table The session receipt carries hop entries by the roles a stream log's hop entries use (1 origin, 2 relay, 3 sink decode, 4 display): a figure with its own class, the unmetered marker with its reason where the sink knows the hop took place and has no figure, and no entry where the sink states nothing. A segment receipt is the sink-decode hop's figure for its segment. Co-Authored-By: Claude Opus 5.5 --- wai/CHANGELOG.md | 15 +- wai/SPEC.md | 11 +- wai/extensions/sink-energy.md | 155 +- wai/live-conformance/sink-energy/vectors.json | 2184 +++++++++++++++-- wai/wai-rs/src/sink_energy.rs | 133 +- wai/wai-rs/src/sink_energy/corpus.rs | 95 +- 6 files changed, 2216 insertions(+), 377 deletions(-) diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index a67498f99..13ba94260 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -39,12 +39,15 @@ wire format may change and such changes are recorded here. `measured` over a bounded window (exactly the segment's decode, so no completion-count term), taken from the sink's own table (`ModelBased`) or another key's (`Estimator`), estimated, or `unmetered` with its reason; - - a **session receipt** (§6): the per-hop split into `origin`, `relay`, `sink_decode` and - `display`, each with its own class. A relay part restates a live-metering per-hop record - (`WMH1`) with its class only where the session was the hop's sole user (at least 99% of - its subscriber time); a hop shared with other sessions is absent as `hop_shared`, never - divided by time. A missing part is absent with its reason, never a zero, and no total is - stated across classes. + - a **session receipt** (§6): the per-hop split over the hop roles a stream log uses + (stream-termination §2 and §3.5: 1 origin, 2 relay, 3 sink decode, 4 display), one table + with its hop entries. Each hop is a figure with its own class, the unmetered marker where + the sink knows it took place and has no figure (with the reason), or absent where the sink + states nothing. An origin or relay entry restates a hop's record (a live-metering `WMH1` + for a relay) with its class only where the session was the hop's sole user, at least 99% + of its busy time; a shared hop is the marker `hop_shared`, never divided by time. The + sink-decode entry is composed from the segment receipts, each of which is that hop's + figure for its segment, and no total is stated across classes. - **Corpus and verifiers.** `live-conformance/sink-energy/` holds accepted and refused tables, segments, chains and sessions (every reason of §8 refuses at least one) and the measured tables of the reference sinks under `tables/`, each with its readings and its procedure. diff --git a/wai/SPEC.md b/wai/SPEC.md index a5077f780..4ae88628d 100644 --- a/wai/SPEC.md +++ b/wai/SPEC.md @@ -1041,11 +1041,12 @@ record and every counter reading in a readings file it binds by digest; a **segm receipt** per segment of each stream it decodes, chained, whose figure is measured over the segment's own decode, taken from the sink's own table (`ModelBased`) or another key's (`Estimator`), estimated from processor time, or stated as unmetered with its reason; and a -**session receipt** that splits one viewing session into its origin, its relay (a hop's -per-hop record, restated only where the session was the hop's sole user), its decode and its -display (an estimate from the panel's declared -on-mode power and its luminance setting), each part with its own acquisition class and a -missing part named as absent, never a zero. The counters, their scopes, their idle baselines +**session receipt** that splits one viewing session over the four hops a stream log names — +its origin, its relay (a hop's per-hop record, restated only where the session was the hop's +sole user), its decode and its display (an estimate from the panel's declared on-mode power +and its luminance setting) — each hop a figure with its own acquisition class, the unmetered +marker where it took place unmeasured, or absent where the sink states nothing, never a +zero. The counters, their scopes, their idle baselines and their windows are the sink profiles of energy-measurement §9: [`extensions/sink-energy.md`](extensions/sink-energy.md). diff --git a/wai/extensions/sink-energy.md b/wai/extensions/sink-energy.md index 3e06f4694..63906b7bd 100644 --- a/wai/extensions/sink-energy.md +++ b/wai/extensions/sink-energy.md @@ -8,8 +8,10 @@ > capability, decode path and sink energy profile, produced by a pinned procedure and > committed with its raw readings; a **segment receipt** (§5), the decode energy of one > segment of one stream, measured, taken from the sink's own table, estimated, or stated -> as unmetered; and a **session receipt** (§6), the per-hop split, each part with its own -> class and a missing part absent and named as such. The counters, scopes, idle baselines +> as unmetered; and a **session receipt** (§6), the per-hop split over the four hop roles a +> stream log uses (origin, relay, sink decode, display), each hop a figure with its own +> class, the unmetered marker where the hop took place unmeasured, or absent where the sink +> states nothing about it. The counters, scopes, idle baselines > and windows are energy-measurement §9's sink profiles. No new transport, no change to > the wire format or to SPEC §4's algorithm. The reference implementation is the > `sink_energy` module of `wai-rs` (feature `sink_energy`), whose readers are in @@ -49,10 +51,14 @@ Relationship to other documents: and `filtering` the row's class and evidence, `coverage` and `fabrics` the row's. - **[jwp-receipts](jwp-receipts.md) §2.1.** A segment receipt names what it decoded by that section's content hash. -- **[live-metering](live-metering.md) §3.** A relay's part of the split is read from a - hop's per-hop session record, `WMH1`. +- **[live-metering](live-metering.md) §3.** A relay's hop entry is read from a hop's + per-hop session record, `WMH1`. +- **stream-termination §2, §3.5 and §4.6.** The hop roles, the hop entries' figure, marker + and absence, and each role's figure for one segment of a stream log. This extension's + hops are that table (§6.2), not a second one, and a segment receipt here is the + sink-decode hop's figure for its segment. - **[staged-measurement](staged-measurement.md) §8.** A decode figure never includes a - display (its paragraph 8.4). The display is a part of its own in the split (§6.3), always an estimate. + display (its paragraph 8.4). The display is a hop of its own (§6.3), always an estimate. Relationship to the core spec: `wai.sink.energy` is a **record** ([SPEC](../SPEC.md) §5). It names no payload format and is not dispatchable. @@ -361,44 +367,69 @@ stopped decoding has a last segment; it has no receipt after it. | `media_us` | integer | the sum of the segments' `media_us` | | `presented_us` | integer | the time the session's content was on screen, in microseconds | | `display` | object or null | the display's declaration (§6.3), or `null` | -| `split` | object | exactly the four parts `origin`, `relay`, `sink_decode` and `display` (§6.2) | +| `hops` | array | the session's hop entries (§6.2), in strictly ascending order of `role`, each role at most once | -### 6.2 A part +### 6.2 The hops, and a hop entry + +The hops are the four roles of a delivery's energy, and they are one table with a stream +log's hop entries (stream-termination §2, *Hop*, and §3.5): **1** origin, **2** relay, +**3** sink decode, **4** display. A hop is in one of three states, by the same rule: + +- **a figure**: an entry with a figure and its own class; +- **the unmetered marker**: an entry with the marker (energy-measurement §2), for a hop the + sink knows took place and has no figure for; it says the hop happened and was not + measured, and it is never a figure of zero joules; +- **absent**: no entry, for a hop the sink states nothing about. Absence is not the marker: + it says nothing, and in particular not that the hop cost nothing. | member | type | meaning | |---|---|---| -| `status` | term | `figure` or `absent` | -| `reason` | term or null | for `absent`: `not_reported`, `hop_unmetered`, `hop_shared`, `unmetered` or `display_undeclared`; `null` for `figure` | -| `joules_micro`, `energy_provenance`, `energy_uncertainty`, `energy_counter_filtering`, `energy_calibration_ref` | — | the part's figure and its own class (§2) | -| `coverage` | term or null | `null` when `absent`; for a figure, `Whole` for `display` (the estimate is of the whole panel's draw), `Partial` for `origin` and `relay` (a hop's record does not state what its figure covers), and `sink_decode`'s by §6.3 | -| `source` | object or null | for `origin` and `relay` with `figure`: the hop record the part restates (§6.4); otherwise `null` | +| `role` | integer | 1 origin, 2 relay, 3 sink decode, 4 display | +| `joules_micro`, `energy_provenance`, `energy_uncertainty`, `energy_counter_filtering`, `energy_calibration_ref` | — | the hop's figure and its own class (§2), or the marker | +| `unmetered` | term or null | for the marker, why the hop has no figure: `not_reported`, `hop_unmetered`, `hop_shared`, `unmetered` or `display_undeclared`; `null` for a figure | +| `coverage` | term or null | `null` for the marker; for a figure, `Whole` for the display (the estimate is of the whole panel's draw), `Partial` for the origin and the relay (a hop's record does not state what its figure covers), and the sink decode's by §6.3 | +| `source` | object or null | for the origin and the relay with a figure: the hop record the entry restates (§6.4); otherwise `null` | -A part is a figure or it is absent, and an absent part is named with its reason: it -carries the unmetered marker and is never a figure of zero (`split_part_invalid`). A -`figure` part's figure is at least 1 and its class coheres with it (energy-measurement -§2). Each part carries its own class: the receipt states no total over its parts, and a -reader **MUST NOT** add parts of different classes into one figure, nor add the display -to any decode figure (energy-measurement §9.5). +Which hops have an entry: -### 6.3 The parts +- the **sink decode** always has one: a session's sink decoded its segments; +- the **display** has one exactly when `presented_us` is above 0, and none when nothing was + presented; +- the **origin** always has one: the session's segments came from one; +- the **relay** has one where the sink knows its segments came through one, and none + otherwise. -**`sink_decode`.** Read from the session's segment receipts (§5). With the metered -segments those whose `method` is not `unmetered`: +A figure entry's figure is at least 1 and its class coheres with it (energy-measurement +§2); a marker entry has `coverage` and `source` `null` and a reason its role may have: +`not_reported` (the sink holds no figure for the hop), `hop_unmetered` (the hop's own +record carries the marker) or `hop_shared` (§6.4) for the origin and the relay; +`unmetered` for the sink decode; `display_undeclared` or `unmetered` for the display +(`split_part_invalid`). Each hop carries its own class: the receipt states no total over +its hops, and a reader **MUST NOT** add hops of different classes into one figure, nor add +the display to any decode figure (energy-measurement §9.5). -- every segment unmetered: the part is `absent`, reason `unmetered`; +### 6.3 The figure of each hop + +**Sink decode (3).** Read from the session's segment receipts (§5), each of which is the +sink-decode hop's figure for its segment, the figure a stream log's segment receipt states +for role 3 (stream-termination §4.6). With the metered segments those whose `method` is +not `unmetered`: + +- every segment unmetered: the entry is the marker, `unmetered`; - otherwise `joules_micro` is the sum of the metered segments' figures, and the class is the weakest among theirs in energy-measurement §2's order. Where every metered segment - is `CalibratedInstrument` with one calibration reference, the part is that class with - that reference. Where every metered segment is a hardware class otherwise, the part is + is `CalibratedInstrument` with one calibration reference, the entry is that class with + that reference. Where every metered segment is a hardware class otherwise, the entry is `OnChipCounter`, its filtering `On` if any segment's is `On`, `Off` if every metered - segment is `OnChipCounter` with `Off`, and `Unknown` otherwise. A hardware part declares `window_us` the longest of - the segments' windows and `relative_ppm` at least `⌈Σ(Fáµ¢ · páµ¢) / ΣFᵢ⌉` over the - metered segments' figures and declarations: linear in the parts, so it assumes their - errors fully correlated, as jwp-receipts §2.4.1 composes a group's; + segment is `OnChipCounter` with `Off`, and `Unknown` otherwise. A hardware entry + declares `window_us` the longest of the segments' windows and `relative_ppm` at least + `⌈Σ(Fáµ¢ · páµ¢) / ΣFᵢ⌉` over the metered segments' figures and declarations: linear in the + parts, so it assumes their errors fully correlated, as jwp-receipts §2.4.1 composes a + group's; - `coverage` is `Whole` only when no segment is unmetered and every metered segment is `Whole`. -**`display`.** Energy-measurement §9.5's `estimator-display/1` over the session's +**Display (4).** Energy-measurement §9.5's `estimator-display/1` over the session's `presented_us`, with the declaration ``` @@ -407,15 +438,15 @@ segments those whose `method` is not `unmetered`: ``` `on_mode_mw`, `reference_nits` and `relative_ppm` at least 1, `nits` at least 0 and -`basis` non-empty. The part is `figure` with class `Estimator` when the declaration is -present and its `E` is at least 1; `absent` with reason `display_undeclared` when -`display` is `null`, and with `unmetered` when `E` is 0. A reader presents it with the +`basis` non-empty. The entry is a figure with class `Estimator` when the declaration is +present and its `E` is at least 1; the marker `display_undeclared` when `display` is +`null`, and the marker `unmetered` when `E` is 0. A reader presents the figure with the stated uncertainty `display.relative_ppm`, as an estimate, apart. -**`origin`** and **`relay`.** A hop's own signed figure, where this session was the -hop's sole user (§6.4), or `absent`: `not_reported` when the sink holds no such figure, -`hop_unmetered` when the hop's record carries the unmetered marker, `hop_shared` when the -session shared the hop with other sessions. +**Origin (1)** and **relay (2).** A hop's own signed figure, where this session was the +hop's sole user (§6.4), or the marker: `not_reported` when the sink holds no such figure, +`hop_unmetered` when the hop's record carries the marker, `hop_shared` when the session +shared the hop with other sessions. ### 6.4 A hop's figure, where the session was its sole user @@ -432,7 +463,7 @@ time every user of the hop accrued over the hop's span, `1 ≤ share_num ≤ sha A hop's figure is about every session it carried, and it is not divided among them by the time each kept it busy: busy time does not apportion energy (energy-measurement -§9.1). The part restates the hop's figure only where this session was the hop's sole +§9.1). The entry restates the hop's figure only where this session was the hop's sole user, at least 99% of its busy time: ``` @@ -442,8 +473,8 @@ joules_micro = ⌊(2 · hop_joules · share_num + share_den) / (2 · share_den) and then `joules_micro` is at least 1, its attribution is `rail` (energy-measurement §9.1), and its class and evidence are the hop's: `energy_provenance` is `hop_class`. A -hop this session shared below that gives no part: it is `absent`, with reason -`hop_shared`, and the sink states no figure for it. A `HwShunt` hop gives +hop this session shared below that gives no figure: its entry is the marker, with reason +`hop_shared`. A `HwShunt` hop gives `hwshunt_forbidden`. For `live-metering-hop` the record is a per-hop session record `WMH1` (live-metering @@ -452,7 +483,7 @@ statement of its own subscriber-microseconds on that hop, which the record does (live-metering §3 keeps no per-viewer entry). A verifier holding the record's bytes requires: they read as a per-hop record of the room their own `room` field names, its signature verified (live-metering §3.6); their digest is `record`; its -`joules` is `hop_joules`, its class label is `hop_class` and its evidence the part's; +`joules` is `hop_joules`, its class label is `hop_class` and its evidence the entry's; and `share_den` is its subscriber-microseconds' sum (`split_hop_mismatch`). For `other`, the arithmetic and the class rule are checked from the receipt alone. @@ -462,7 +493,7 @@ A verifier holding the session's segment receipts requires (`session_streams_mis that each verifies (§7), names this `session` and `signer`, and that per stream they form a chain (§5.3) whose count is `segments` and whose last receipt's identity is `last`, with no stream left out; and that `media_us` is the sum of their `media_us`. It then requires -`sink_decode` to be what §6.3 gives (`split_decode_mismatch`). +the sink-decode entry to be what §6.3 gives (`split_decode_mismatch`). ## 7. Verification @@ -484,7 +515,8 @@ refusal. (energy-measurement §9.1), none twice; - `measured` is a date `YYYY-MM-DD`; - a `source` (§6.4) and a `display` (§6.3) have exactly their members, of their types; - - `split` has exactly its four parts, each with exactly the members of §6.2; + - each of `hops` has exactly the members of §6.2, its `role` from 1 to 4, the entries in + strictly ascending order of `role`; - a `capability` is one `registry/capabilities.json` lists; - a table's `counters` are in ascending order of (`profile`, `detail`), and its `machine` and `build` have exactly their members; @@ -494,8 +526,8 @@ refusal. - `hop_class` is a string. 2. **Signature** (`signature_invalid`): `sig` over the body under `signer` (§3). 3. **Classes.** For each class the record carries, in this order — a table's rows in - order; the segment's; a session's parts in the order `origin`, `relay`, `sink_decode`, - `display`, each part's class and then its `source`'s `hop_class` — three rules in + order; the segment's; a session's hop entries in the order of their role, each entry's + class and then its `source`'s `hop_class` — three rules in turn: a `HwShunt` label is refused (`hwshunt_forbidden`); then a class beside a `joules_micro` of 0, or a non-zero `joules_micro` beside no class, is refused (`unmetered_as_zero`); then a label energy-measurement §2 does not name, a class whose @@ -553,22 +585,21 @@ refusal. - Holding the stream's receipts: §5.3 (`segment_chain_broken`), after each receipt has passed every step above. 6. **A session receipt**, in order: - - each part, in the order `origin`, `relay`, `sink_decode`, `display`, as §6.2 reads - it (`split_part_invalid`): a `figure` part has `reason` `null`, a figure of at least - 1 and the `coverage` §6.2 gives it (for `sink_decode`, any of the two here, its value - checked against §6.3 below), and a `source` exactly when it is `origin` or - `relay`; an `absent` part has the unmetered marker, `coverage` and `source` `null`, - and a reason its part may have: `not_reported`, `hop_unmetered` or `hop_shared` for - `origin` and `relay`, `unmetered` for `sink_decode`, `display_undeclared` or `unmetered` for - `display`; - - the display part against §6.3, its declaration's ranges included + - the hop entries as §6.2 reads them (`split_part_invalid`): the sink decode and the + origin have an entry, the display has one exactly when `presented_us` is above 0; + then, in the order of their role, a figure entry has `unmetered` `null`, a figure of + at least 1, the `coverage` §6.2 gives it (for the sink decode, either of the two here, + its value checked against §6.3 below) and a `source` exactly when its role is the + origin's or the relay's; a marker entry has `coverage` and `source` `null` and a + reason its role may have; + - the display entry against §6.3, its declaration's ranges included (`split_display_mismatch`); - - the `origin` then the `relay` part against §6.4: the sole-user rule, the figure, the - class and evidence, and, holding the record, the record (`split_hop_mismatch`); + - the origin's then the relay's entry, where they have a figure, against §6.4: the + sole-user rule, the figure, the class and evidence, and, holding the record, the record + (`split_hop_mismatch`); - holding the segment receipts (a verifier given none holds none), §6.5: each receipt - verifies (steps 1–5) and the streams - agree (`session_streams_mismatch`), then `sink_decode` is what §6.3 gives - (`split_decode_mismatch`). + verifies (steps 1–5) and the streams agree (`session_streams_mismatch`), then the + sink-decode entry is what §6.3 gives (`split_decode_mismatch`). An accepted record establishes that its signer stated it. The arithmetic between its values is checked, and so are its readings, its table and its hop records where the @@ -600,10 +631,10 @@ record that invents every value consistently passes every check. | `segment_table_mismatch` | a table segment's figure, class or row disagrees with its table | | `segment_estimator_mismatch` | an estimated segment's figure or inputs are not the method's | | `segment_chain_broken` | a stream's receipts do not form its chain | -| `split_part_invalid` | a part is neither a figure nor a named absence | -| `split_display_mismatch` | the display part is not the estimator's over the declaration | -| `split_hop_mismatch` | an origin or relay part's share, class or record disagrees | -| `split_decode_mismatch` | the sink-decode part is not what the segment receipts give | +| `split_part_invalid` | a hop entry is missing where §6.2 requires one, or is neither a figure nor a marker with its reason | +| `split_display_mismatch` | the display entry is not the estimator's over the declaration | +| `split_hop_mismatch` | an origin or relay entry's busy time, class or record disagrees | +| `split_decode_mismatch` | the sink-decode entry is not what the segment receipts give | | `session_streams_mismatch` | the streams, counts, last receipts or `media_us` disagree with the segment receipts | The **row reasons** a table row states are §4.5's: `no_decoder`, `no_work_item`, diff --git a/wai/live-conformance/sink-energy/vectors.json b/wai/live-conformance/sink-energy/vectors.json index 5c906ab96..f8ac35a4e 100644 --- a/wai/live-conformance/sink-energy/vectors.json +++ b/wai/live-conformance/sink-energy/vectors.json @@ -26722,10 +26722,10 @@ "basis": "https://label.example/display/model-x", "relative_ppm": 300000 }, - "split": { - "origin": { - "status": "absent", - "reason": "not_reported", + "hops": [ + { + "role": 1, + "unmetered": "not_reported", "coverage": null, "source": null, "joules_micro": 0, @@ -26734,9 +26734,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "relay": { - "status": "absent", - "reason": "hop_shared", + { + "role": 2, + "unmetered": "hop_shared", "coverage": null, "source": null, "joules_micro": 0, @@ -26745,9 +26745,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "sink_decode": { - "status": "figure", - "reason": null, + { + "role": 3, + "unmetered": null, "coverage": "Partial", "source": null, "joules_micro": 2338254, @@ -26756,9 +26756,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "display": { - "status": "figure", - "reason": null, + { + "role": 4, + "unmetered": null, "coverage": "Whole", "source": null, "joules_micro": 2880000000, @@ -26767,8 +26767,8 @@ "energy_counter_filtering": null, "energy_calibration_ref": null } - }, - "sig": "c630579878ecc4f1a19a27524c319c7b9a81fc1b3f757ee8df78fc2970995800dcc13d9a1703f0707aeede0bdf98d0eef9f159c4bbfce67ec604a763c7289701" + ], + "sig": "300c887fb008556c5640f4c1f820940d67aec687f400bf5a56e1e2802135582b202b0b79b9152e08dabfd99b9418b1850ed9c6fc1b9e150e01ce2a4b599e6203" }, "readings": null, "tables": [ @@ -28476,7 +28476,7 @@ "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f" ], "expect": "accept", - "identity": "a67d65abe23ccc175fb87178d72d9ac800687e43d029d95e5c6328526c619726" + "identity": "edd9cbc7949af8579f668a6ecfd33348f14742df6fc98106b2724ca8cf825f70" }, { "name": "session, the relay's whole figure", @@ -28495,10 +28495,10 @@ "media_us": 2000000, "presented_us": 4000000, "display": null, - "split": { - "origin": { - "status": "absent", - "reason": "not_reported", + "hops": [ + { + "role": 1, + "unmetered": "not_reported", "coverage": null, "source": null, "joules_micro": 0, @@ -28507,9 +28507,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "relay": { - "status": "figure", - "reason": null, + { + "role": 2, + "unmetered": null, "coverage": "Partial", "source": { "kind": "live-metering-hop", @@ -28528,9 +28528,9 @@ "energy_counter_filtering": "Unknown", "energy_calibration_ref": null }, - "sink_decode": { - "status": "figure", - "reason": null, + { + "role": 3, + "unmetered": null, "coverage": "Partial", "source": null, "joules_micro": 6000000, @@ -28542,9 +28542,9 @@ "energy_counter_filtering": "Unknown", "energy_calibration_ref": null }, - "display": { - "status": "absent", - "reason": "display_undeclared", + { + "role": 4, + "unmetered": "display_undeclared", "coverage": null, "source": null, "joules_micro": 0, @@ -28553,8 +28553,8 @@ "energy_counter_filtering": null, "energy_calibration_ref": null } - }, - "sig": "dfdef8fe88cef0df007ca18e4f3a93e0ded88805190fb66cf3336710503ca59fc7f3c51c2f9fe388efe4ba7d8480dc022a80da6795bcb76f37450a434f7f1006" + ], + "sig": "64fad04d50973f36c87d19e0d3a00b229220862ca94d15162aab722e0238f30e521347449697d636aae6fd92841789a99b8d825ef40d393b8ea7dcfb44d2df08" }, "readings": null, "tables": [], @@ -28617,7 +28617,7 @@ "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f" ], "expect": "accept", - "identity": "532338930699763ff93a0f68b2951bb4362d683d998153b535658e80e8a90289" + "identity": "5634b02eb0ff190cde101d6e3e2d317ba2c0b91cdb53b274d920412a2da33c53" }, { "name": "session, the relay at 99.5% of its busy time", @@ -28636,10 +28636,10 @@ "media_us": 2000000, "presented_us": 4000000, "display": null, - "split": { - "origin": { - "status": "absent", - "reason": "not_reported", + "hops": [ + { + "role": 1, + "unmetered": "not_reported", "coverage": null, "source": null, "joules_micro": 0, @@ -28648,9 +28648,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "relay": { - "status": "figure", - "reason": null, + { + "role": 2, + "unmetered": null, "coverage": "Partial", "source": { "kind": "live-metering-hop", @@ -28669,9 +28669,9 @@ "energy_counter_filtering": "Unknown", "energy_calibration_ref": null }, - "sink_decode": { - "status": "figure", - "reason": null, + { + "role": 3, + "unmetered": null, "coverage": "Partial", "source": null, "joules_micro": 6000000, @@ -28683,9 +28683,9 @@ "energy_counter_filtering": "Unknown", "energy_calibration_ref": null }, - "display": { - "status": "absent", - "reason": "display_undeclared", + { + "role": 4, + "unmetered": "display_undeclared", "coverage": null, "source": null, "joules_micro": 0, @@ -28694,8 +28694,8 @@ "energy_counter_filtering": null, "energy_calibration_ref": null } - }, - "sig": "c49022feb1135218583b5c0d85e285ba2d40aa4834ee3976b9259692cbab81e00bdeaf9bd93843de12ede0841082d81edc8e3a1486018dcfe0c0380a187dc000" + ], + "sig": "351373862e6529849fe422b6ed0989e65b3468326bf9367e41a52bba08ad7f54176d0e6cc0c7c906aeb8dafbad0e3f83676158c7a9da1020549d447947e43406" }, "readings": null, "tables": [], @@ -28758,7 +28758,7 @@ "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f" ], "expect": "accept", - "identity": "f173d6c33c422d53a73e0860bc0b3a0552ee027dc7114d08f5225dd19d04aace" + "identity": "2d37ca2a6c0131f166738380628d328a03a7949aad9e2b02a1e575cc4e7bbdcc" }, { "name": "session, without its hop record", @@ -28790,10 +28790,10 @@ "basis": "https://label.example/display/model-x", "relative_ppm": 300000 }, - "split": { - "origin": { - "status": "absent", - "reason": "not_reported", + "hops": [ + { + "role": 1, + "unmetered": "not_reported", "coverage": null, "source": null, "joules_micro": 0, @@ -28802,9 +28802,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "relay": { - "status": "absent", - "reason": "hop_shared", + { + "role": 2, + "unmetered": "hop_shared", "coverage": null, "source": null, "joules_micro": 0, @@ -28813,9 +28813,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "sink_decode": { - "status": "figure", - "reason": null, + { + "role": 3, + "unmetered": null, "coverage": "Partial", "source": null, "joules_micro": 2338254, @@ -28824,9 +28824,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "display": { - "status": "figure", - "reason": null, + { + "role": 4, + "unmetered": null, "coverage": "Whole", "source": null, "joules_micro": 2880000000, @@ -28835,8 +28835,8 @@ "energy_counter_filtering": null, "energy_calibration_ref": null } - }, - "sig": "c630579878ecc4f1a19a27524c319c7b9a81fc1b3f757ee8df78fc2970995800dcc13d9a1703f0707aeede0bdf98d0eef9f159c4bbfce67ec604a763c7289701" + ], + "sig": "300c887fb008556c5640f4c1f820940d67aec687f400bf5a56e1e2802135582b202b0b79b9152e08dabfd99b9418b1850ed9c6fc1b9e150e01ce2a4b599e6203" }, "readings": null, "tables": [ @@ -30542,7 +30542,7 @@ ], "hops": [], "expect": "accept", - "identity": "a67d65abe23ccc175fb87178d72d9ac800687e43d029d95e5c6328526c619726" + "identity": "edd9cbc7949af8579f668a6ecfd33348f14742df6fc98106b2724ca8cf825f70" }, { "name": "session, an origin's figure from another kind of record", @@ -30574,10 +30574,10 @@ "basis": "https://label.example/display/model-x", "relative_ppm": 300000 }, - "split": { - "origin": { - "status": "figure", - "reason": null, + "hops": [ + { + "role": 1, + "unmetered": null, "coverage": "Partial", "source": { "kind": "other", @@ -30593,9 +30593,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "relay": { - "status": "absent", - "reason": "hop_shared", + { + "role": 2, + "unmetered": "hop_shared", "coverage": null, "source": null, "joules_micro": 0, @@ -30604,9 +30604,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "sink_decode": { - "status": "figure", - "reason": null, + { + "role": 3, + "unmetered": null, "coverage": "Partial", "source": null, "joules_micro": 2338254, @@ -30615,9 +30615,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "display": { - "status": "figure", - "reason": null, + { + "role": 4, + "unmetered": null, "coverage": "Whole", "source": null, "joules_micro": 2880000000, @@ -30626,8 +30626,8 @@ "energy_counter_filtering": null, "energy_calibration_ref": null } - }, - "sig": "24fd8ca438242852617053e4abff12d27ff299b11a341c61e6d079cd8274959a2cbb0d714f2dd08191d891e465d8b07b41a073a028fd4894b0e2c27cf5c88008" + ], + "sig": "c38277204e037e2e9cd816d8d827cd162e6b83139d1053e759e3c86c0c7ceaaf656214cf4f37278499e2568f1923bf6f5a8a59a3bed6a96c7fb38cf1fead5507" }, "readings": null, "tables": [ @@ -32335,7 +32335,7 @@ "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f" ], "expect": "accept", - "identity": "8edc73a3d90c805708da4288808b769a9eee9d988091dd0a2c61050bf73a1321" + "identity": "f18c3eccdd1e8e89ebe54a6e4e039e90ef7dac64d9f092a733c12dcc5892c8ce" }, { "name": "session, decode read from a calibrated instrument", @@ -32354,10 +32354,10 @@ "media_us": 4000000, "presented_us": 5000000, "display": null, - "split": { - "origin": { - "status": "absent", - "reason": "not_reported", + "hops": [ + { + "role": 1, + "unmetered": "not_reported", "coverage": null, "source": null, "joules_micro": 0, @@ -32366,20 +32366,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "relay": { - "status": "absent", - "reason": "not_reported", - "coverage": null, - "source": null, - "joules_micro": 0, - "energy_provenance": null, - "energy_uncertainty": null, - "energy_counter_filtering": null, - "energy_calibration_ref": null - }, - "sink_decode": { - "status": "figure", - "reason": null, + { + "role": 3, + "unmetered": null, "coverage": "Partial", "source": null, "joules_micro": 6200000, @@ -32391,9 +32380,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": "https://calibration.example/certificate/7" }, - "display": { - "status": "absent", - "reason": "display_undeclared", + { + "role": 4, + "unmetered": "display_undeclared", "coverage": null, "source": null, "joules_micro": 0, @@ -32402,8 +32391,8 @@ "energy_counter_filtering": null, "energy_calibration_ref": null } - }, - "sig": "5da8db3082051bdb0f489d0aff38f6476c5e798ded30622b125cbb8d4b13d058c21a0107ff063ea46817784e55466a1bdd4371c06a6f98bb19c7f4a8f2b4b10f" + ], + "sig": "8f6905ad5159af71888438e15d733aa5008f4336cb7336c82e36e21ea81a927daafa082ead2b0843035cbe57a388cf68fc8c25177b852cfa8e38b2f0b6e0950a" }, "readings": null, "tables": [], @@ -32517,10 +32506,10 @@ ], "hops": [], "expect": "accept", - "identity": "f6acf6af93977838762e6ccad5ddae985e61bfcb1de81e2ca1f5c8e0fbca1554" + "identity": "18152b3f1a159530ec5e278dde06b1b5374374a3017d52f3293f56503c456d73" }, { - "name": "session, an origin absent as unmetered", + "name": "session, the display left out though content was presented", "subject": "session", "record": { "wai_sink_session": 1, @@ -32549,10 +32538,10 @@ "basis": "https://label.example/display/model-x", "relative_ppm": 300000 }, - "split": { - "origin": { - "status": "absent", - "reason": "unmetered", + "hops": [ + { + "role": 1, + "unmetered": "not_reported", "coverage": null, "source": null, "joules_micro": 0, @@ -32561,9 +32550,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "relay": { - "status": "absent", - "reason": "hop_shared", + { + "role": 2, + "unmetered": "hop_shared", "coverage": null, "source": null, "joules_micro": 0, @@ -32572,9 +32561,1784 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "sink_decode": { - "status": "figure", - "reason": null, + { + "role": 3, + "unmetered": null, + "coverage": "Partial", + "source": null, + "joules_micro": 2338254, + "energy_provenance": "Estimator", + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } + ], + "sig": "a1a876f1a1d835aa9b4283e46f0ccdd4a446fd679b29ea92bb0d11b2f1d924cbca240e2ded1d766b326fb516fe9808dc865532427d5936c8e3a4c9fe35d8370a" + }, + "readings": null, + "tables": [ + { + "wai_sink_table": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "procedure": "wai-sink-table/1", + "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", + "machine": { + "os": "corpus", + "arch": "corpus", + "processor": "synthetic", + "cores": 16, + "memory_gib": 64 + }, + "build": { + "profile": "release", + "toolchain": null, + "target": null + }, + "measured": "2026-10-22", + "rounds": 7, + "sample_us": 200000, + "counters": [ + { + "profile": "macos-gpu", + "status": "frozen", + "detail": "Energy Model/GPU Energy" + }, + { + "profile": "macos-task", + "status": "live", + "detail": "proc_pid_rusage V6" + } + ], + "rows": [ + { + "capability": "wai.audio.flac", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "content-ms", + "units": 10000, + "width": null, + "height": null, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 50 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 53 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 56 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 59 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 62 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 65 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 68 + } + ], + "status": "unmetered", + "reason": "rail_not_live", + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_hardware_path", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2144400, + "sd_uw": 356790, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2652643, + "sd_uw": 391226, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 400 + }, + { + "idle": { + "n": 10, + "mean_uw": 2212000, + "sd_uw": 397991, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2606000, + "sd_uw": 332414, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 403 + }, + { + "idle": { + "n": 10, + "mean_uw": 2066400, + "sd_uw": 307198, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2586286, + "sd_uw": 342938, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 406 + }, + { + "idle": { + "n": 10, + "mean_uw": 1885700, + "sd_uw": 332615, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2577429, + "sd_uw": 398088, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 409 + }, + { + "idle": { + "n": 10, + "mean_uw": 2074500, + "sd_uw": 452434, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2637429, + "sd_uw": 386990, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 412 + }, + { + "idle": { + "n": 10, + "mean_uw": 2040300, + "sd_uw": 384214, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2626143, + "sd_uw": 418958, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 415 + }, + { + "idle": { + "n": 10, + "mean_uw": 1851400, + "sd_uw": 287343, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2489429, + "sd_uw": 369320, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 418 + } + ], + "status": "unmetered", + "reason": "below_attribution_floor", + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 1890800, + "sd_uw": 173618, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3406857, + "sd_uw": 291462, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3000 + }, + { + "idle": { + "n": 10, + "mean_uw": 2003700, + "sd_uw": 258242, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3502000, + "sd_uw": 238914, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3003 + }, + { + "idle": { + "n": 10, + "mean_uw": 2141600, + "sd_uw": 275043, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3416357, + "sd_uw": 204354, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3006 + }, + { + "idle": { + "n": 10, + "mean_uw": 2028200, + "sd_uw": 196893, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3475500, + "sd_uw": 226251, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3009 + }, + { + "idle": { + "n": 10, + "mean_uw": 2040400, + "sd_uw": 214134, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3434357, + "sd_uw": 223672, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3012 + }, + { + "idle": { + "n": 10, + "mean_uw": 2030100, + "sd_uw": 307907, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3404571, + "sd_uw": 219703, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3015 + }, + { + "idle": { + "n": 10, + "mean_uw": 2050600, + "sd_uw": 244479, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3408571, + "sd_uw": 225896, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3018 + } + ], + "status": "relative", + "reason": null, + "median": 4, + "range": [ + 1186, + 1413 + ], + "joules_micro": 1294, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 64810, + "window_us": 2800000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-gpu", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_counter", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 1987200, + "sd_uw": 15719, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5002143, + "sd_uw": 17502, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1000 + }, + { + "idle": { + "n": 10, + "mean_uw": 2002700, + "sd_uw": 19682, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5004857, + "sd_uw": 20184, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1003 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000800, + "sd_uw": 18850, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001143, + "sd_uw": 17629, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1006 + }, + { + "idle": { + "n": 10, + "mean_uw": 2002500, + "sd_uw": 18549, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 4999571, + "sd_uw": 16787, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1009 + }, + { + "idle": { + "n": 10, + "mean_uw": 1997400, + "sd_uw": 20824, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5002429, + "sd_uw": 18559, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1012 + }, + { + "idle": { + "n": 10, + "mean_uw": 1996900, + "sd_uw": 17445, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001000, + "sd_uw": 19415, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1015 + }, + { + "idle": { + "n": 10, + "mean_uw": 2008700, + "sd_uw": 13777, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001214, + "sd_uw": 17215, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1018 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 8199, + 8408 + ], + "joules_micro": 8284, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 7284, + "window_us": 2800000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vvc", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": null, + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } + ], + "sig": "b5f0d2478d875281ff282f24d8a7759d07e99d094d986f92ffd0141baaca354cee62c411dc82e55800f39dcafedd7ea17b6d713bef58ca9dd9c6516abc26600b" + }, + { + "wai_sink_table": 1, + "signer": "849e713d6bb5b2daf8790b7d6ed3779d08d3a71fa30fe14b230a5539ff54e6a9", + "procedure": "wai-sink-table/1", + "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", + "machine": { + "os": "corpus", + "arch": "corpus", + "processor": "synthetic", + "cores": 16, + "memory_gib": 64 + }, + "build": { + "profile": "release", + "toolchain": null, + "target": null + }, + "measured": "2026-10-22", + "rounds": 7, + "sample_us": 200000, + "counters": [ + { + "profile": "macos-gpu", + "status": "frozen", + "detail": "Energy Model/GPU Energy" + }, + { + "profile": "macos-task", + "status": "live", + "detail": "proc_pid_rusage V6" + } + ], + "rows": [ + { + "capability": "wai.audio.flac", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "content-ms", + "units": 10000, + "width": null, + "height": null, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 50 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 53 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 56 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 59 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 62 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 65 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 68 + } + ], + "status": "unmetered", + "reason": "rail_not_live", + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_hardware_path", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2144400, + "sd_uw": 356790, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2652643, + "sd_uw": 391226, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 400 + }, + { + "idle": { + "n": 10, + "mean_uw": 2212000, + "sd_uw": 397991, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2606000, + "sd_uw": 332414, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 403 + }, + { + "idle": { + "n": 10, + "mean_uw": 2066400, + "sd_uw": 307198, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2586286, + "sd_uw": 342938, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 406 + }, + { + "idle": { + "n": 10, + "mean_uw": 1885700, + "sd_uw": 332615, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2577429, + "sd_uw": 398088, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 409 + }, + { + "idle": { + "n": 10, + "mean_uw": 2074500, + "sd_uw": 452434, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2637429, + "sd_uw": 386990, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 412 + }, + { + "idle": { + "n": 10, + "mean_uw": 2040300, + "sd_uw": 384214, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2626143, + "sd_uw": 418958, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 415 + }, + { + "idle": { + "n": 10, + "mean_uw": 1851400, + "sd_uw": 287343, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2489429, + "sd_uw": 369320, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 418 + } + ], + "status": "unmetered", + "reason": "below_attribution_floor", + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 1890800, + "sd_uw": 173618, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3406857, + "sd_uw": 291462, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3000 + }, + { + "idle": { + "n": 10, + "mean_uw": 2003700, + "sd_uw": 258242, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3502000, + "sd_uw": 238914, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3003 + }, + { + "idle": { + "n": 10, + "mean_uw": 2141600, + "sd_uw": 275043, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3416357, + "sd_uw": 204354, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3006 + }, + { + "idle": { + "n": 10, + "mean_uw": 2028200, + "sd_uw": 196893, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3475500, + "sd_uw": 226251, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3009 + }, + { + "idle": { + "n": 10, + "mean_uw": 2040400, + "sd_uw": 214134, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3434357, + "sd_uw": 223672, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3012 + }, + { + "idle": { + "n": 10, + "mean_uw": 2030100, + "sd_uw": 307907, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3404571, + "sd_uw": 219703, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3015 + }, + { + "idle": { + "n": 10, + "mean_uw": 2050600, + "sd_uw": 244479, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3408571, + "sd_uw": 225896, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3018 + } + ], + "status": "relative", + "reason": null, + "median": 4, + "range": [ + 1186, + 1413 + ], + "joules_micro": 1294, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 64810, + "window_us": 2800000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-gpu", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_counter", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 1987200, + "sd_uw": 15719, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5002143, + "sd_uw": 17502, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1000 + }, + { + "idle": { + "n": 10, + "mean_uw": 2002700, + "sd_uw": 19682, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5004857, + "sd_uw": 20184, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1003 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000800, + "sd_uw": 18850, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001143, + "sd_uw": 17629, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1006 + }, + { + "idle": { + "n": 10, + "mean_uw": 2002500, + "sd_uw": 18549, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 4999571, + "sd_uw": 16787, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1009 + }, + { + "idle": { + "n": 10, + "mean_uw": 1997400, + "sd_uw": 20824, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5002429, + "sd_uw": 18559, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1012 + }, + { + "idle": { + "n": 10, + "mean_uw": 1996900, + "sd_uw": 17445, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001000, + "sd_uw": 19415, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1015 + }, + { + "idle": { + "n": 10, + "mean_uw": 2008700, + "sd_uw": 13777, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001214, + "sd_uw": 17215, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1018 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 8199, + 8408 + ], + "joules_micro": 8284, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 7284, + "window_us": 2800000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vvc", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": null, + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } + ], + "sig": "80d62d05c16342e453409ec2b4cd034c8863029734bf8f1e3fcbdf2c1a969aba26b518f1e6239e427d810d75f612972f78982cb150f45e13ba1bbeb3f825d508" + } + ], + "segments": [ + { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", + "segment": 0, + "prev": null, + "object": "904fcef85345a6ab4cc7388f8a9d6a52700713dd5f0afab36deb9b859b0d5bf2", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "measured", + "profile": "macos-task", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 40000, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 10, + "mean_uw": 2900000, + "sd_uw": 60000, + "live_intervals": 10, + "longest_live_run": 10 + }, + "window_us": 2000000, + "sample_us": 200000 + }, + "table": null, + "estimator": null, + "reason": null, + "joules_micro": 1800000, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 25338, + "window_us": 2000000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null, + "sig": "3b05fcb31aaea9e0574691dd6ccaab9cf4754581b14f6d89e14f417d92217f294bf175ba14189ddcdfa003e400b8f4e9252cefc0478cd4ad178c5c4cec84de0e" + }, + { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", + "segment": 1, + "prev": "2299cf3c7fb5fc13ac56cad34b0f656487dbfdde2f30ad1e9f6ed7a754eb238a", + "object": "2ec96fd7e1b71588a9b60b1cdf1dc4bc7aea8f2ab33eb3cffd9d987b1ef87698", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "table", + "profile": "macos-task", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": null, + "table": { + "id": "f156a7cc646f6b1ae43887ca7b553fe4d37a2a96f9c06f4d2159ec47a63b15a9", + "row": 6, + "per_completion_uj": 8284, + "units_per_completion": 60 + }, + "estimator": null, + "reason": null, + "joules_micro": 6627, + "energy_provenance": "ModelBased", + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null, + "sig": "a48191a9277ce5d62fe4f30653dfbaf8f309e47fe8e52abdc6791e6957c9d853cac33ea48204c0c7cd3bbc9c7054ffd9090b3133b9a6814efcb115445be83f04" + }, + { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", + "segment": 2, + "prev": "3742462a645481049de2042b3f2c498015fd91393d9bce6fa46d86d07d4d9f07", + "object": "f1a80a9983fe3cfaf0623a97bac977ebf34eefd400e892db5e4ddde9535f3188", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "unmetered", + "profile": null, + "fabrics": [], + "coverage": null, + "bracket": null, + "table": null, + "estimator": null, + "reason": "not_reported", + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null, + "sig": "117485aa23543d0f3f6889adb8ed04262700e16edcab1fc70bc7fde3c9bc2c7dd58712375b5a52716d3ae0c7d626263a8e875dd8b767a3a8d994b887da43e309" + }, + { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", + "segment": 0, + "prev": null, + "object": "46a5ce125bbfa822b69b37a2d86527fba30be66ae6d4d464e36a14a9a3c0190d", + "capability": "wai.audio.opus", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "content-ms", + "units": 2000, + "width": null, + "height": null, + "media_us": 2000000, + "method": "estimator", + "profile": "estimator-cpu-time/1", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": null, + "table": null, + "estimator": { + "method": "estimator-cpu-time/1", + "cpu_us": 150000, + "active_mw": 3500, + "basis": "https://power.example/device-class/phone-a", + "relative_ppm": 400000 + }, + "reason": null, + "joules_micro": 525000, + "energy_provenance": "Estimator", + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null, + "sig": "c72bf30af2dfe67c3fb20d934f48e4b3e34fab7fe89ff796a952f41f7efde4dde8fa9f6a90737edbb56d42cfbe76b5f93dcc8aaa42585ebad97db30ac270b702" + }, + { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", + "segment": 1, + "prev": "350fa7be2744dc21b7cc5e4cf37fb9462a311266fb3c015a4ab00e454e8280a4", + "object": "ba2aed82898351ea19b5244df36506e731296164a2a7b7bcc52c60128a144071", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "table", + "profile": "macos-task", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": null, + "table": { + "id": "bd74e55dbd7822aec58d2097175e05c974e5d6f93c9b048c3350f94c346dfaff", + "row": 6, + "per_completion_uj": 8284, + "units_per_completion": 60 + }, + "estimator": null, + "reason": null, + "joules_micro": 6627, + "energy_provenance": "Estimator", + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null, + "sig": "c173f9db9a7efd77482a1367d4096f6f94ddf08251306b45775e893f2679f1296005289ea126b66285a83c7c02c97ce7839367309b869c9205df3babf64b1308" + } + ], + "hops": [ + "d68dd8730f022904670b43e860f538c0a650152a20359ecc50c84110bd63194f" + ], + "expect": "split_part_invalid", + "identity": null + }, + { + "name": "session, an origin's marker with the decode's reason", + "subject": "session", + "record": { + "wai_sink_session": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "streams": [ + { + "stream": "4828ab08ddf3764023f23ad57c0422791deb32b155ab37bb7e92d5ae4141c18d", + "segments": 3, + "last": "9ca9c2eff1d8b74b8cf7f08758f611900f78387c091eb54e5ce4717378716ed0" + }, + { + "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", + "segments": 2, + "last": "83e0d157261d6a7272d72dbba17a9c319ab7938615a2562f6eebed9222147624" + } + ], + "media_us": 10000000, + "presented_us": 120000000, + "display": { + "method": "estimator-display/1", + "panel_class": "backlit", + "on_mode_mw": 30000, + "reference_nits": 250, + "nits": 200, + "basis": "https://label.example/display/model-x", + "relative_ppm": 300000 + }, + "hops": [ + { + "role": 1, + "unmetered": "unmetered", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "role": 2, + "unmetered": "hop_shared", + "coverage": null, + "source": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "role": 3, + "unmetered": null, "coverage": "Partial", "source": null, "joules_micro": 2338254, @@ -32583,9 +34347,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "display": { - "status": "figure", - "reason": null, + { + "role": 4, + "unmetered": null, "coverage": "Whole", "source": null, "joules_micro": 2880000000, @@ -32594,8 +34358,8 @@ "energy_counter_filtering": null, "energy_calibration_ref": null } - }, - "sig": "a429824ebd4192d0eb5db022c5130c1d7e965fd4fa46da98214936812f92c0f6f0718c72a3f01b32dfc582dbe8a0f2e8a3955b510255b28b01f89843279b7003" + ], + "sig": "c5753d9565e0718d82f1052acb1234c307bb13c7c54070290870af4cd89083ef20bd84f7510e3fb068221471ebb4187f6bca672f05c7b14782f57e11a26c4402" }, "readings": null, "tables": [ @@ -34335,10 +36099,10 @@ "basis": "https://label.example/display/model-x", "relative_ppm": 300000 }, - "split": { - "origin": { - "status": "absent", - "reason": "not_reported", + "hops": [ + { + "role": 1, + "unmetered": "not_reported", "coverage": null, "source": null, "joules_micro": 0, @@ -34347,9 +36111,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "relay": { - "status": "absent", - "reason": "hop_shared", + { + "role": 2, + "unmetered": "hop_shared", "coverage": null, "source": null, "joules_micro": 0, @@ -34358,9 +36122,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "sink_decode": { - "status": "figure", - "reason": null, + { + "role": 3, + "unmetered": null, "coverage": "Partial", "source": null, "joules_micro": 2338254, @@ -34369,9 +36133,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "display": { - "status": "figure", - "reason": null, + { + "role": 4, + "unmetered": null, "coverage": "Whole", "source": null, "joules_micro": 2880000001, @@ -34380,8 +36144,8 @@ "energy_counter_filtering": null, "energy_calibration_ref": null } - }, - "sig": "b8da3502fe77bfc7365a8a7894e07faf38cc6bb1103c90817a699a47366e2961b7887bc15d49c881b849c91e1e86cae7e58c25f550de3ee630097947156ae403" + ], + "sig": "f01886fe353ee2b65cacdc32f67289f1b9f57a194f5cda7580ab2854696542bc3e13b19092910cb06c2d99da3fc7fb7a6625d62a2b45261bf87a7f14251daa06" }, "readings": null, "tables": [ @@ -36108,10 +37872,10 @@ "media_us": 2000000, "presented_us": 4000000, "display": null, - "split": { - "origin": { - "status": "absent", - "reason": "not_reported", + "hops": [ + { + "role": 1, + "unmetered": "not_reported", "coverage": null, "source": null, "joules_micro": 0, @@ -36120,9 +37884,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "relay": { - "status": "figure", - "reason": null, + { + "role": 2, + "unmetered": null, "coverage": "Partial", "source": { "kind": "live-metering-hop", @@ -36141,9 +37905,9 @@ "energy_counter_filtering": "Unknown", "energy_calibration_ref": null }, - "sink_decode": { - "status": "figure", - "reason": null, + { + "role": 3, + "unmetered": null, "coverage": "Partial", "source": null, "joules_micro": 6000000, @@ -36155,9 +37919,9 @@ "energy_counter_filtering": "Unknown", "energy_calibration_ref": null }, - "display": { - "status": "absent", - "reason": "display_undeclared", + { + "role": 4, + "unmetered": "display_undeclared", "coverage": null, "source": null, "joules_micro": 0, @@ -36166,8 +37930,8 @@ "energy_counter_filtering": null, "energy_calibration_ref": null } - }, - "sig": "6a150e55f27a9ddaa8b2b03843f9fb61cb13c114161192aaba869219ad620e9995734570f9372c2a02dfd7645a7c6a8ed3fde763d4169cb934c0459e14e7d80e" + ], + "sig": "bb063f167247cb159d31e3d173f02b4ca993e09f840b44b69da2688e692967986fbc58b3e74a898a278be0d58bf06ae24376e1c1a90d98ba7130e0d3af4d7401" }, "readings": null, "tables": [], @@ -36249,10 +38013,10 @@ "media_us": 2000000, "presented_us": 4000000, "display": null, - "split": { - "origin": { - "status": "absent", - "reason": "not_reported", + "hops": [ + { + "role": 1, + "unmetered": "not_reported", "coverage": null, "source": null, "joules_micro": 0, @@ -36261,9 +38025,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "relay": { - "status": "figure", - "reason": null, + { + "role": 2, + "unmetered": null, "coverage": "Partial", "source": { "kind": "live-metering-hop", @@ -36282,9 +38046,9 @@ "energy_counter_filtering": "Unknown", "energy_calibration_ref": null }, - "sink_decode": { - "status": "figure", - "reason": null, + { + "role": 3, + "unmetered": null, "coverage": "Partial", "source": null, "joules_micro": 6000000, @@ -36296,9 +38060,9 @@ "energy_counter_filtering": "Unknown", "energy_calibration_ref": null }, - "display": { - "status": "absent", - "reason": "display_undeclared", + { + "role": 4, + "unmetered": "display_undeclared", "coverage": null, "source": null, "joules_micro": 0, @@ -36307,8 +38071,8 @@ "energy_counter_filtering": null, "energy_calibration_ref": null } - }, - "sig": "757d3861999a2dfffa462541ac45910ae20e8c804afeb4b9aa6a4bdf05cf32668107cb4a0026758a8d1b158c9e57b2bf2e05200022fb6b503a8b9822f7083100" + ], + "sig": "03c9ae7be16d15c9529cfc9534f714b2ea8ab98320f36ab939b0a4af03792cacc45b6cbbd7959dbb202651fd84a0ab884c0ce3c00cfac4ce10f476119324990b" }, "readings": null, "tables": [], @@ -36390,10 +38154,10 @@ "media_us": 2000000, "presented_us": 4000000, "display": null, - "split": { - "origin": { - "status": "absent", - "reason": "not_reported", + "hops": [ + { + "role": 1, + "unmetered": "not_reported", "coverage": null, "source": null, "joules_micro": 0, @@ -36402,9 +38166,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "relay": { - "status": "figure", - "reason": null, + { + "role": 2, + "unmetered": null, "coverage": "Partial", "source": { "kind": "live-metering-hop", @@ -36423,9 +38187,9 @@ "energy_counter_filtering": "Unknown", "energy_calibration_ref": null }, - "sink_decode": { - "status": "figure", - "reason": null, + { + "role": 3, + "unmetered": null, "coverage": "Partial", "source": null, "joules_micro": 6000000, @@ -36437,9 +38201,9 @@ "energy_counter_filtering": "Unknown", "energy_calibration_ref": null }, - "display": { - "status": "absent", - "reason": "display_undeclared", + { + "role": 4, + "unmetered": "display_undeclared", "coverage": null, "source": null, "joules_micro": 0, @@ -36448,8 +38212,8 @@ "energy_counter_filtering": null, "energy_calibration_ref": null } - }, - "sig": "438a2431b44ae72c14d901b4a311b56a8155c1e522df21ed2dfa6b884698fd1681773c2efb45f0e8033b3ff6a73817e31b8529081d43873e794f161109068a06" + ], + "sig": "516d8f02a011ba46d05e7e2a664bfc20e64716fe86e984cff76f65db4cf6170abd87d0cc58553f1395e28ad8f67588a73bd06d17d552b7c82af0e947decc3708" }, "readings": null, "tables": [], @@ -36544,10 +38308,10 @@ "basis": "https://label.example/display/model-x", "relative_ppm": 300000 }, - "split": { - "origin": { - "status": "absent", - "reason": "not_reported", + "hops": [ + { + "role": 1, + "unmetered": "not_reported", "coverage": null, "source": null, "joules_micro": 0, @@ -36556,9 +38320,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "relay": { - "status": "absent", - "reason": "hop_shared", + { + "role": 2, + "unmetered": "hop_shared", "coverage": null, "source": null, "joules_micro": 0, @@ -36567,9 +38331,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "sink_decode": { - "status": "figure", - "reason": null, + { + "role": 3, + "unmetered": null, "coverage": "Partial", "source": null, "joules_micro": 2338255, @@ -36578,9 +38342,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "display": { - "status": "figure", - "reason": null, + { + "role": 4, + "unmetered": null, "coverage": "Whole", "source": null, "joules_micro": 2880000000, @@ -36589,8 +38353,8 @@ "energy_counter_filtering": null, "energy_calibration_ref": null } - }, - "sig": "6e646cb47b627686a3133f4feeb3d21749f70ee1ec88949db639957d5cbd1dae79db5feeb3aa4f32a5eaf1b07a4e06be83725c3d5bb7ae369794b0b1f8686205" + ], + "sig": "5be3f9912c3ea3e544b9109dd3998e67897c36c0ad26a92644a76288aee500413e5a01d941b5c0139bc737f688f1417c0d98f9b4ee19d08ef39b2e3c4a4eff0e" }, "readings": null, "tables": [ @@ -38330,10 +40094,10 @@ "basis": "https://label.example/display/model-x", "relative_ppm": 300000 }, - "split": { - "origin": { - "status": "absent", - "reason": "not_reported", + "hops": [ + { + "role": 1, + "unmetered": "not_reported", "coverage": null, "source": null, "joules_micro": 0, @@ -38342,9 +40106,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "relay": { - "status": "absent", - "reason": "hop_shared", + { + "role": 2, + "unmetered": "hop_shared", "coverage": null, "source": null, "joules_micro": 0, @@ -38353,9 +40117,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "sink_decode": { - "status": "figure", - "reason": null, + { + "role": 3, + "unmetered": null, "coverage": "Partial", "source": null, "joules_micro": 2338254, @@ -38364,9 +40128,9 @@ "energy_counter_filtering": null, "energy_calibration_ref": null }, - "display": { - "status": "figure", - "reason": null, + { + "role": 4, + "unmetered": null, "coverage": "Whole", "source": null, "joules_micro": 2880000000, @@ -38375,8 +40139,8 @@ "energy_counter_filtering": null, "energy_calibration_ref": null } - }, - "sig": "111ac3236fd441984a06f9a69ddb3fb30610c4421d47858951da1ca398d30ff8dab4a28f9557e0ce800324c06d9b28c264e4839789d96d6479d107b47f4ac70b" + ], + "sig": "dbd4637197261e76500c0bd8a0f633089bdeb91940167f416dda0921fe514f41aeced4afb928e663ba828c13bb3460f5590859ee4defeb6fa1a866bc1bea330c" }, "readings": null, "tables": [ diff --git a/wai/wai-rs/src/sink_energy.rs b/wai/wai-rs/src/sink_energy.rs index 60a786d2b..7fb3d5b7a 100644 --- a/wai/wai-rs/src/sink_energy.rs +++ b/wai/wai-rs/src/sink_energy.rs @@ -385,12 +385,15 @@ const MACHINE_PROFILES: &[&str] = &["linux-nvml", "linux-rapl", "macos-gpu", "ma const SEGMENT_PROFILES: &[&str] = &["linux-nvml", "linux-rapl", "macos-gpu", "macos-task", ESTIMATOR_CPU]; const COUNTER_STATUSES: &[&str] = &["live", "frozen", "needs-privilege", "no-counter", "not-implemented", "implausible"]; const FABRICS: &[&str] = &["cpu", "gpu", "media", "neural"]; -const PART_STATUSES: &[&str] = &["figure", "absent"]; const PART_REASONS: &[&str] = &["not_reported", "hop_unmetered", "hop_shared", "unmetered", "display_undeclared"]; const HOP_KINDS: &[&str] = &["live-metering-hop", "other"]; const PANEL_CLASSES: &[&str] = &["backlit", "emissive"]; const BUILD_PROFILES: &[&str] = &["release", "debug"]; -const PARTS: [&str; 4] = ["origin", "relay", "sink_decode", "display"]; +/// The hop roles (§6.2, stream-termination §2): 1 origin, 2 relay, 3 sink decode, 4 display. +pub const ORIGIN: u64 = 1; +pub const RELAY: u64 = 2; +pub const SINK_DECODE: u64 = 3; +pub const DISPLAY: u64 = 4; const ENERGY: [(&str, Ty); 5] = [ ("joules_micro", Ty::Int), @@ -607,7 +610,7 @@ fn session_shape(s: &Value) -> R<()> { ("media_us", Ty::Int), ("presented_us", Ty::Int), ("display", Ty::ObjOrNull), - ("split", Ty::Obj), + ("hops", Ty::Arr), ("sig", Ty::Sig), ], )?; @@ -635,17 +638,19 @@ fn session_shape(s: &Value) -> R<()> { ], )?; } - members(&s["split"], "session.split", &PARTS.map(|p| (p, Ty::Obj)))?; - let part_spec = with_energy(&[ - ("status", Ty::Term(PART_STATUSES)), - ("reason", Ty::TermOrNull(PART_REASONS)), + let entry_spec = with_energy(&[ + ("role", Ty::Int), + ("unmetered", Ty::TermOrNull(PART_REASONS)), ("coverage", Ty::TermOrNull(COVERAGES)), ("source", Ty::ObjOrNull), ]); - for p in PARTS { - let part = &s["split"][p]; - let what = format!("session.split.{p}"); - members(part, &what, &part_spec)?; + let mut last = 0u64; + for (i, part) in s["hops"].as_array().expect("typed").iter().enumerate() { + let what = format!("session.hops[{i}]"); + members(part, &what, &entry_spec)?; + let role = int(&part["role"]).expect("typed"); + ensure((1..=4).contains(&role) && role > last, Reason::RecordMalformed, || format!("{what}.role is not 1 to 4 in strictly ascending order"))?; + last = role; energy_shape(part, &what)?; if !part["source"].is_null() { members( @@ -1424,66 +1429,86 @@ fn check_chain(receipts: &[Value], ids: &[[u8; 32]]) -> R<()> { // §7 step 6: a session receipt // --------------------------------------------------------------------------------------- +/// A session's hop entries by role (§6.2). +fn entries(s: &Value) -> BTreeMap { + s["hops"].as_array().map(|a| a.iter().filter_map(|e| Some((int(&e["role"])?, e))).collect()).unwrap_or_default() +} + +fn role_name(role: u64) -> &'static str { + match role { + ORIGIN => "origin", + RELAY => "relay", + SINK_DECODE => "sink decode", + _ => "display", + } +} + /// §7 steps 1–3 and 6 for a session receipt, with the segment receipts, the decode tables /// and the hop records (by digest) the verifier holds. Its identity. pub fn verify_session(s: &Value, segments: &[Value], tables: &[Value], hops: &BTreeMap<[u8; 32], Vec>) -> R<[u8; 32]> { session_shape(s)?; check_signature(Kind::Session, s)?; + let by_role = entries(s); let mut classes = BTreeMap::new(); - for p in PARTS { - let part = &s["split"][p]; - classes.insert(p, read_class(part, &format!("split.{p}"))?); - if let Some(h) = part["source"]["hop_class"].as_str() { - read_hop_class(h, &format!("split.{p}.source.hop_class"))?; + for (&role, e) in &by_role { + let what = format!("hop {}", role_name(role)); + classes.insert(role, read_class(e, &what)?); + if let Some(h) = e["source"]["hop_class"].as_str() { + read_hop_class(h, &format!("{what}.source.hop_class"))?; } } - // Each part as §6.2 reads it. - for p in PARTS { - let part = &s["split"][p]; - let bad = |w: &str| refuse(Reason::SplitPartInvalid, format!("split.{p}: {w}")); - let hop = p == "origin" || p == "relay"; - if part["status"] == "figure" { - let cov_ok = match p { - "display" => part["coverage"] == "Whole", - "sink_decode" => !part["coverage"].is_null(), - _ => part["coverage"] == "Partial", + // The entries as §6.2 reads them. + let presented = int(&s["presented_us"]).unwrap() > 0; + let bad = |w: String| refuse(Reason::SplitPartInvalid, w); + if !by_role.contains_key(&SINK_DECODE) || !by_role.contains_key(&ORIGIN) { + return bad("the sink decode and the origin always have an entry".into()); + } + if by_role.contains_key(&DISPLAY) != presented { + return bad("the display has an entry exactly when something was presented".into()); + } + for (&role, e) in &by_role { + let hop = role == ORIGIN || role == RELAY; + if classes[&role].is_some() { + let cov_ok = match role { + DISPLAY => e["coverage"] == "Whole", + SINK_DECODE => !e["coverage"].is_null(), + _ => e["coverage"] == "Partial", }; - if !(part["reason"].is_null() && int(&part["joules_micro"]).unwrap_or(0) >= 1 && cov_ok && part["source"].is_null() != hop) { - return bad("not a well-formed figure"); + if !(e["unmetered"].is_null() && int(&e["joules_micro"]).unwrap_or(0) >= 1 && cov_ok && e["source"].is_null() != hop) { + return bad(format!("hop {}: not a well-formed figure", role_name(role))); } } else { - let allowed: &[&str] = match p { - "origin" | "relay" => &["not_reported", "hop_unmetered", "hop_shared"], - "sink_decode" => &["unmetered"], + let allowed: &[&str] = match role { + ORIGIN | RELAY => &["not_reported", "hop_unmetered", "hop_shared"], + SINK_DECODE => &["unmetered"], _ => &["display_undeclared", "unmetered"], }; - let ok = classes[p].is_none() - && int(&part["joules_micro"]) == Some(0) - && part["coverage"].is_null() - && part["source"].is_null() - && part["reason"].as_str().is_some_and(|r| allowed.contains(&r)); + let ok = e["coverage"].is_null() && e["source"].is_null() && e["unmetered"].as_str().is_some_and(|r| allowed.contains(&r)); if !ok { - return bad("not a named absence"); + return bad(format!("hop {}: not the unmetered marker with its reason", role_name(role))); } } } - check_display(s, classes["display"].as_ref())?; - for p in ["origin", "relay"] { - check_hop(&s["split"][p], classes[p].as_ref(), hops, p)?; + if let Some(d) = by_role.get(&DISPLAY) { + check_display(s, d, classes[&DISPLAY].as_ref())?; + } + for role in [ORIGIN, RELAY] { + if let (Some(e), Some(Some(c))) = (by_role.get(&role), classes.get(&role)) { + check_hop(e, c, hops, role_name(role))?; + } } if !segments.is_empty() { check_streams(s, segments, tables)?; - check_decode(&s["split"]["sink_decode"], classes["sink_decode"].as_ref(), segments)?; + check_decode(by_role[&SINK_DECODE], classes[&SINK_DECODE].as_ref(), segments)?; } identity(Kind::Session, s).ok_or_else(|| Refusal { reason: Reason::RecordMalformed, why: "no identity".into() }) } -fn check_display(s: &Value, class: Option<&Class>) -> R<()> { - let part = &s["split"]["display"]; +fn check_display(s: &Value, part: &Value, class: Option<&Class>) -> R<()> { let bad = |w: &str| refuse(Reason::SplitDisplayMismatch, w.to_string()); let d = &s["display"]; if d.is_null() { - return if part["reason"] == "display_undeclared" { Ok(()) } else { bad("no declaration, and not display_undeclared") }; + return if part["unmetered"] == "display_undeclared" { Ok(()) } else { bad("no declaration, and not the marker display_undeclared") }; } let g = |k: &str| int(&d[k]).unwrap(); if g("on_mode_mw") < 1 || g("reference_nits") < 1 || g("relative_ppm") < 1 || d["basis"].as_str().unwrap_or("").is_empty() { @@ -1491,20 +1516,17 @@ fn check_display(s: &Value, class: Option<&Class>) -> R<()> { } let e = display_estimate(g("on_mode_mw"), g("nits"), int(&s["presented_us"]).unwrap(), g("reference_nits")).unwrap_or(u128::MAX); if e == 0 { - return if part["reason"] == "unmetered" { Ok(()) } else { bad("the estimate is 0, and the part is not unmetered") }; + return if part["unmetered"] == "unmetered" { Ok(()) } else { bad("the estimate is 0, and the entry is not the marker unmetered") }; } - if part["status"] == "figure" && *class.unwrap_or(&Class::Model) == Class::Estimator && int(&part["joules_micro"]).map(|x| x as u128) == Some(e) { + if *class.unwrap_or(&Class::Model) == Class::Estimator && int(&part["joules_micro"]).map(|x| x as u128) == Some(e) { Ok(()) } else { - bad(&format!("the display part is not the estimate {e}")) + bad(&format!("the display entry is not the estimate {e}")) } } -fn check_hop(part: &Value, class: Option<&Class>, hops: &BTreeMap<[u8; 32], Vec>, p: &str) -> R<()> { - if part["status"] != "figure" { - return Ok(()); - } - let bad = |w: String| Refusal { reason: Reason::SplitHopMismatch, why: format!("split.{p}: {w}") }; +fn check_hop(part: &Value, class: &Class, hops: &BTreeMap<[u8; 32], Vec>, p: &str) -> R<()> { + let bad = |w: String| Refusal { reason: Reason::SplitHopMismatch, why: format!("hop {p}: {w}") }; let src = &part["source"]; let g = |k: &str| int(&src[k]).unwrap(); let (hj, num, den) = (g("hop_joules"), g("share_num"), g("share_den")); @@ -1520,7 +1542,6 @@ fn check_hop(part: &Value, class: Option<&Class>, hops: &BTreeMap<[u8; 32], Vec< if want == 0 || int(&part["joules_micro"]).map(|x| x as u128) != Some(want) { return Err(bad(format!("the figure is not the hop's {want}"))); } - let class = class.expect("a figure has a class"); if class.label() != src["hop_class"].as_str().unwrap() { return Err(bad("the class is not the hop's".into())); } @@ -1645,13 +1666,13 @@ pub fn compose_decode(segments: &[Value]) -> Option<(u64, Class, &'static str)> } fn check_decode(part: &Value, class: Option<&Class>, segments: &[Value]) -> R<()> { - let bad = |w: String| Refusal { reason: Reason::SplitDecodeMismatch, why: format!("split.sink_decode: {w}") }; + let bad = |w: String| Refusal { reason: Reason::SplitDecodeMismatch, why: format!("hop sink decode: {w}") }; match compose_decode(segments) { None => { - if part["status"] == "absent" && part["reason"] == "unmetered" { Ok(()) } else { Err(bad("every segment is unmetered".into())) } + if class.is_none() && part["unmetered"] == "unmetered" { Ok(()) } else { Err(bad("every segment is unmetered".into())) } } Some((total, want, coverage)) => { - let got = class.ok_or_else(|| bad("absent, and the segments are metered".into()))?; + let got = class.ok_or_else(|| bad("the marker, and the segments are metered".into()))?; if int(&part["joules_micro"]) != Some(total) || part["coverage"] != coverage { return Err(bad(format!("figure or coverage; the segments give {total} {coverage}"))); } diff --git a/wai/wai-rs/src/sink_energy/corpus.rs b/wai/wai-rs/src/sink_energy/corpus.rs index db369bbc6..90adb5bb9 100644 --- a/wai/wai-rs/src/sink_energy/corpus.rs +++ b/wai/wai-rs/src/sink_energy/corpus.rs @@ -247,32 +247,35 @@ fn hop_record(key: &SigningKey) -> HopRecord { .sign(key) } -fn part_absent(reason: &str) -> Value { +/// A hop entry with the unmetered marker and its reason (§6.2). +fn hop_marker(role: u64, reason: &str) -> Value { let mut m = Map::new(); - m.insert("status".into(), "absent".into()); - m.insert("reason".into(), reason.into()); + m.insert("role".into(), role.into()); + m.insert("unmetered".into(), reason.into()); m.insert("coverage".into(), Value::Null); m.insert("source".into(), Value::Null); put_energy(&mut m, 0, None); Value::Object(m) } -fn part_figure(joules: u64, class: &Class, coverage: &str, source: Value) -> Value { +/// A hop entry with a figure and its class (§6.2). +fn hop_figure(role: u64, joules: u64, class: &Class, coverage: &str, source: Value) -> Value { let mut m = Map::new(); - m.insert("status".into(), "figure".into()); - m.insert("reason".into(), Value::Null); + m.insert("role".into(), role.into()); + m.insert("unmetered".into(), Value::Null); m.insert("coverage".into(), coverage.into()); m.insert("source".into(), source); put_energy(&mut m, joules, Some(class)); Value::Object(m) } -/// The relay part for a session that accrued `share_num` of the hop's busy time: the hop's -/// figure where the session was its sole user (at least 99%), and `hop_shared` otherwise. +/// The relay's entry for a session that accrued `share_num` of the hop's busy time: the hop's +/// figure where the session was its sole user (at least 99%), and the marker `hop_shared` +/// otherwise. fn hop_part(rec: &HopRecord, share_num: u64) -> Value { let den: u64 = rec.subscriber_us.iter().sum(); if 100 * share_num < 99 * den { - return part_absent("hop_shared"); + return hop_marker(RELAY, "hop_shared"); } let class = match rec.class.as_ref().unwrap() { EnergyProvenance::OnChipCounter { uncertainty, .. } => { @@ -282,10 +285,12 @@ fn hop_part(rec: &HopRecord, share_num: u64) -> Value { }; let source = json!({ "kind": "live-metering-hop", "record": hex(&rec.digest()), "hop_joules": rec.joules, "hop_class": "OnChipCounter", "share_num": share_num, "share_den": den }); - part_figure(share_figure(rec.joules, share_num, den).unwrap() as u64, &class, "Partial", source) + hop_figure(RELAY, share_figure(rec.joules, share_num, den).unwrap() as u64, &class, "Partial", source) } -fn session_body(sink: &SigningKey, segments: &[Value], display: Option, presented_us: u64, origin: Value, relay: Value) -> Value { +/// A session over `segments`: the origin's entry as given, the relay's where given, the sink +/// decode composed from the segments, and the display's where anything was presented. +fn session_body(sink: &SigningKey, segments: &[Value], display: Option, presented_us: u64, origin: Value, relay: Option) -> Value { let mut by: BTreeMap> = BTreeMap::new(); for s in segments { by.entry(s["stream"].as_str().unwrap().into()).or_default().push(s); @@ -295,20 +300,24 @@ fn session_body(sink: &SigningKey, segments: &[Value], display: Option, p .map(|(k, v)| json!({ "stream": k, "segments": v.len(), "last": hex(&identity(Kind::Segment, v.last().unwrap()).unwrap()) })) .collect(); let media: u64 = segments.iter().map(|s| s["media_us"].as_u64().unwrap()).sum(); - let decode = match compose_decode(segments) { - None => part_absent("unmetered"), - Some((j, c, cov)) => part_figure(j, &c, cov, Value::Null), - }; - let disp = match &display { - None => part_absent("display_undeclared"), - Some(d) => { - let g = |k: &str| d[k].as_u64().unwrap(); - match display_estimate(g("on_mode_mw"), g("nits"), presented_us, g("reference_nits")).unwrap() { - 0 => part_absent("unmetered"), - e => part_figure(e as u64, &Class::Estimator, "Whole", Value::Null), + let mut hops = vec![origin]; + hops.extend(relay); + hops.push(match compose_decode(segments) { + None => hop_marker(SINK_DECODE, "unmetered"), + Some((j, c, cov)) => hop_figure(SINK_DECODE, j, &c, cov, Value::Null), + }); + if presented_us > 0 { + hops.push(match &display { + None => hop_marker(DISPLAY, "display_undeclared"), + Some(d) => { + let g = |k: &str| d[k].as_u64().unwrap(); + match display_estimate(g("on_mode_mw"), g("nits"), presented_us, g("reference_nits")).unwrap() { + 0 => hop_marker(DISPLAY, "unmetered"), + e => hop_figure(DISPLAY, e as u64, &Class::Estimator, "Whole", Value::Null), + } } - } - }; + }); + } json!({ "wai_sink_session": 1, "signer": pubhex(sink), @@ -317,10 +326,15 @@ fn session_body(sink: &SigningKey, segments: &[Value], display: Option, p "media_us": media, "presented_us": presented_us, "display": display, - "split": { "origin": origin, "relay": relay, "sink_decode": decode, "display": disp }, + "hops": hops, }) } +/// The entry of `role` in a session record's `hops`. +fn hop_of(v: &mut Value, role: u64) -> &mut Value { + v["hops"].as_array_mut().unwrap().iter_mut().find(|e| e["role"] == role).expect("the corpus's session has the entry") +} + /// Re-sign `record` after `f` changes it, so its defect reaches its own rule. fn mutate(kind: Kind, record: &Value, key: &SigningKey, f: impl FnOnce(&mut Value)) -> Value { let mut v = record.clone(); @@ -371,7 +385,7 @@ pub fn generate() -> String { let all = [s0.clone(), s1.clone(), s2.clone(), b0.clone(), b1.clone()]; let session = sign( Kind::Session, - session_body(&sink, &all, Some(display.clone()), 120_000_000, part_absent("not_reported"), hop_part(&hop, 120_000_000)), + session_body(&sink, &all, Some(display.clone()), 120_000_000, hop_marker(ORIGIN, "not_reported"), Some(hop_part(&hop, 120_000_000))), &sink, ); // A session of one measured segment, the relay's whole figure, no display declared. @@ -379,7 +393,7 @@ pub fn generate() -> String { w0.insert("session".into(), digest_hex("session 1").into()); measured(&mut w0, 1_500_000, 4_000_000); let w0 = sign(Kind::Segment, Value::Object(w0), &sink); - let whole = sign(Kind::Session, session_body(&sink, std::slice::from_ref(&w0), None, 4_000_000, part_absent("not_reported"), hop_part(&hop, 3_600_000_000)), &sink); + let whole = sign(Kind::Session, session_body(&sink, std::slice::from_ref(&w0), None, 4_000_000, hop_marker(ORIGIN, "not_reported"), Some(hop_part(&hop, 3_600_000_000))), &sink); let mut cases = Vec::new(); let t = &table; @@ -474,14 +488,14 @@ pub fn generate() -> String { let hops = [hop_hex.clone()]; cases.push(case("session, four parts", "session", Some(&session), None, &tabs, &segs, &hops, "accept")); cases.push(case("session, the relay's whole figure", "session", Some(&whole), None, &[], &[&w0], &hops, "accept")); - let nearly = sign(Kind::Session, session_body(&sink, std::slice::from_ref(&w0), None, 4_000_000, part_absent("not_reported"), hop_part(&hop, 3_582_000_000)), &sink); + let nearly = sign(Kind::Session, session_body(&sink, std::slice::from_ref(&w0), None, 4_000_000, hop_marker(ORIGIN, "not_reported"), Some(hop_part(&hop, 3_582_000_000))), &sink); cases.push(case("session, the relay at 99.5% of its busy time", "session", Some(&nearly), None, &[], &[&w0], &hops, "accept")); cases.push(case("session, without its hop record", "session", Some(&session), None, &tabs, &segs, &[], "accept")); // An origin that served this session alone, from a record of another kind, checked from the // receipt: its whole figure and its class. let other = json!({ "kind": "other", "record": digest_hex("origin record"), "hop_joules": 9_000_000, "hop_class": "ModelBased", "share_num": 2_000_000, "share_den": 2_000_000 }); let with_origin = sign(Kind::Session, session_body(&sink, &all, Some(display.clone()), 120_000_000, - part_figure(9_000_000, &Class::Model, "Partial", other), hop_part(&hop, 120_000_000)), &sink); + hop_figure(ORIGIN, 9_000_000, &Class::Model, "Partial", other), Some(hop_part(&hop, 120_000_000))), &sink); cases.push(case("session, an origin's figure from another kind of record", "session", Some(&with_origin), None, &tabs, &segs, &hops, "accept")); let cal: Vec = { let mut out: Vec = Vec::new(); @@ -493,29 +507,34 @@ pub fn generate() -> String { } out }; - let calibrated = sign(Kind::Session, session_body(&sink, &cal, None, 5_000_000, part_absent("not_reported"), part_absent("not_reported")), &sink); + let calibrated = sign(Kind::Session, session_body(&sink, &cal, None, 5_000_000, hop_marker(ORIGIN, "not_reported"), None), &sink); cases.push(case("session, decode read from a calibrated instrument", "session", Some(&calibrated), None, &[], &[&cal[0], &cal[1]], &[], "accept")); let ms = |f: &dyn Fn(&mut Value)| mutate(Kind::Session, &session, &sink, |v| f(v)); - cases.push(case("session, an origin absent as unmetered", "session", Some(&ms(&|v| { v["split"]["origin"]["reason"] = "unmetered".into(); })), None, &tabs, &segs, &hops, "split_part_invalid")); + cases.push(case("session, the display left out though content was presented", "session", Some(&ms(&|v| { + v["hops"].as_array_mut().unwrap().retain(|e| e["role"] != DISPLAY); + })), None, &tabs, &segs, &hops, "split_part_invalid")); + cases.push(case("session, an origin's marker with the decode's reason", "session", Some(&ms(&|v| { hop_of(v, ORIGIN)["unmetered"] = "unmetered".into(); })), None, &tabs, &segs, &hops, "split_part_invalid")); cases.push(case("session, a display estimate one microjoule high", "session", Some(&ms(&|v| { - let j = v["split"]["display"]["joules_micro"].as_u64().unwrap(); - v["split"]["display"]["joules_micro"] = (j + 1).into(); + let d = hop_of(v, DISPLAY); + let j = d["joules_micro"].as_u64().unwrap(); + d["joules_micro"] = (j + 1).into(); })), None, &tabs, &segs, &hops, "split_display_mismatch")); let mw = |f: &dyn Fn(&mut Value)| mutate(Kind::Session, &whole, &sink, |v| f(v)); cases.push(case("session, the relay's busy time over another denominator", "session", Some(&mw(&|v| { - let r = &mut v["split"]["relay"]["source"]; + let r = &mut hop_of(v, RELAY)["source"]; r["share_num"] = 3_000_000_000u64.into(); r["share_den"] = 3_000_000_000u64.into(); })), None, &[], &[&w0], &hops, "split_hop_mismatch")); cases.push(case("session, a shared hop stated as the session's", "session", Some(&mw(&|v| { - let r = &mut v["split"]["relay"]; + let r = hop_of(v, RELAY); r["source"]["share_num"] = 1_200_000_000u64.into(); r["joules_micro"] = (share_figure(50_000_000, 1_200_000_000, 3_600_000_000).unwrap() as u64).into(); })), None, &[], &[&w0], &hops, "split_hop_mismatch")); - cases.push(case("session, a relay part sealed HwShunt", "session", Some(&mw(&|v| { v["split"]["relay"]["source"]["hop_class"] = "HwShunt".into(); })), None, &[], &[&w0], &hops, "hwshunt_forbidden")); + cases.push(case("session, a relay part sealed HwShunt", "session", Some(&mw(&|v| { hop_of(v, RELAY)["source"]["hop_class"] = "HwShunt".into(); })), None, &[], &[&w0], &hops, "hwshunt_forbidden")); cases.push(case("session, a decode part one microjoule high", "session", Some(&ms(&|v| { - let j = v["split"]["sink_decode"]["joules_micro"].as_u64().unwrap(); - v["split"]["sink_decode"]["joules_micro"] = (j + 1).into(); + let d = hop_of(v, SINK_DECODE); + let j = d["joules_micro"].as_u64().unwrap(); + d["joules_micro"] = (j + 1).into(); })), None, &tabs, &segs, &hops, "split_decode_mismatch")); cases.push(case("session, media time one microsecond long", "session", Some(&ms(&|v| { let j = v["media_us"].as_u64().unwrap(); -- 2.54.0 (Apple Git-157) From 54488c9db84bc9bda66dcbb0ebd60134bb89b3dd Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:22:28 -0400 Subject: [PATCH 062/242] sink-energy: a display declaration is held to its ranges with or without a display entry Co-Authored-By: Claude Opus 5.5 --- wai/extensions/sink-energy.md | 4 +- wai/live-conformance/sink_energy_verify.py | 133 ++++++++++++--------- wai/wai-rs/src/sink_energy.rs | 15 ++- 3 files changed, 92 insertions(+), 60 deletions(-) diff --git a/wai/extensions/sink-energy.md b/wai/extensions/sink-energy.md index 63906b7bd..dc5400ea3 100644 --- a/wai/extensions/sink-energy.md +++ b/wai/extensions/sink-energy.md @@ -592,8 +592,8 @@ refusal. its value checked against §6.3 below) and a `source` exactly when its role is the origin's or the relay's; a marker entry has `coverage` and `source` `null` and a reason its role may have; - - the display entry against §6.3, its declaration's ranges included - (`split_display_mismatch`); + - the display declaration, where there is one, within its ranges (§6.3), and the + display entry, where there is one, against §6.3 (`split_display_mismatch`); - the origin's then the relay's entry, where they have a figure, against §6.4: the sole-user rule, the figure, the class and evidence, and, holding the record, the record (`split_hop_mismatch`); diff --git a/wai/live-conformance/sink_energy_verify.py b/wai/live-conformance/sink_energy_verify.py index e1df185fa..79d817bde 100644 --- a/wai/live-conformance/sink_energy_verify.py +++ b/wai/live-conformance/sink_energy_verify.py @@ -18,7 +18,7 @@ live_metering_verify.py (live-metering §3.6, §3.5). Everything else is here, f - the segment receipt (§5, step 5): its method, the bounded bracket (`Wn = Wd = 1`, `N = 0`), the table formula against the held table, the estimator `estimator-cpu-time/1` (energy-measurement §9.4), and the chain (§5.3); -- the session receipt (§6, step 6): each part, the display estimator +- the session receipt (§6, step 6): its hop entries, the display estimator `estimator-display/1` (energy-measurement §9.5), a hop's figure under the sole-user rule against its `WMH1` record, the streams against the segment receipts, and the sink-decode part's figure, class and declaration ⌈Σ(Fᵢ·páµ¢)/ΣFᵢ⌉ (§6.3); @@ -92,11 +92,16 @@ ROUND_RULES = ("too_few_samples", "rail_not_live", "window_short", "empty_burst" ROW_REASONS = ABSENT_REASONS + ROUND_RULES + ("noisy",) # §8 *row reasons* UNMETERED_REASONS = ("no_counter", "rail_not_live", "window_short", "no_marginal", "below_attribution_floor", "figure_zero", "not_declarable", "not_reported") # §8 *unmetered reasons* -PARTS = ("origin", "relay", "sink_decode", "display") # §7 step 6's order +ORIGIN, RELAY, SINK_DECODE, DISPLAY = 1, 2, 3, 4 # §6.2's roles +ROLE_NAME = {ORIGIN: "origin", RELAY: "relay", SINK_DECODE: "sink decode", DISPLAY: "display"} +MARKER_REASONS = ("not_reported", "hop_unmetered", "hop_shared", "unmetered", "display_undeclared") PART_REASONS = {"origin": ("not_reported", "hop_unmetered", "hop_shared"), "relay": ("not_reported", "hop_unmetered", "hop_shared"), "sink_decode": ("unmetered",), "display": ("display_undeclared", "unmetered")} PART_COVERAGE = {"origin": "Partial", "relay": "Partial", "display": "Whole"} # §6.2 +ROLE_REASONS = {ORIGIN: PART_REASONS["origin"], RELAY: PART_REASONS["relay"], SINK_DECODE: PART_REASONS["sink_decode"], + DISPLAY: PART_REASONS["display"]} +ROLE_COVERAGE = {ORIGIN: "Partial", RELAY: "Partial", DISPLAY: "Whole"} FILTER_BYTE = {0: "Unknown", 1: "Off", 2: "On"} # energy-measurement §2.7 # Unicode White_Space, for a calibration reference "not empty once trimmed" (jwp-receipts §2.4.5). WHITE_SPACE = frozenset([0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x20, 0x85, 0xA0, 0x1680, 0x2028, 0x2029, 0x202F, 0x205F, @@ -374,13 +379,13 @@ SCHEMA = { "display": nul(obj({"method": const(DISPLAY_EST), "panel_class": enum("backlit", "emissive"), "on_mode_mw": INT, "reference_nits": INT, "nits": INT, "basis": STR, "relative_ppm": INT})), - "split": obj({p: obj(dict({ - "status": enum("figure", "absent"), - "reason": nul(enum("not_reported", "hop_unmetered", "hop_shared", "unmetered", "display_undeclared")), + "hops": arr(obj(dict({ + "role": INT, + "unmetered": nul(enum(*MARKER_REASONS)), "coverage": COVERAGE, "source": nul(obj({"kind": enum("live-metering-hop", "other"), "record": HEX32, "hop_joules": INT, "hop_class": STR, "share_num": INT, "share_den": INT})), - }, **CLASS_MEMBERS)) for p in PARTS}), + }, **CLASS_MEMBERS))), "sig": HEX64, }), } @@ -433,6 +438,9 @@ def shape(rec, kind): keys = [s["stream"] for s in rec["streams"]] need(len(keys) >= 1 and all(a < b for a, b in zip(keys, keys[1:])), "record_malformed", "`streams` is empty or not in strictly ascending order of `stream` (§6.1)") + roles = [h["role"] for h in rec["hops"]] + need(all(1 <= r <= 4 for r in roles) and all(a < b for a, b in zip(roles, roles[1:])), "record_malformed", + "a hop's `role` is not 1 to 4, or the entries are not in strictly ascending order of `role` (§6.1, §7 step 1)") # ---------------------------------------------------------------- §3: signature and identity @@ -906,7 +914,7 @@ def expected_decode(segments): def check_hop(name, part, held_hops): """§6.4 for the `origin` or `relay` part (`split_hop_mismatch`): the sole-user rule, the figure, the class and evidence, and, holding the record, the record.""" - if part["status"] != "figure": + if part["energy_provenance"] is None: # the marker: no figure to check return src = part["source"] hj, num, den = src["hop_joules"], src["share_num"], src["share_den"] @@ -946,36 +954,44 @@ def verify_session(rec, segments, tables, held_hops): """§7 steps 1–3 and 6 for a session receipt; its identity.""" shape(rec, SESSION) check_signature(rec, SESSION) - split = rec["split"] - for name in PARTS: - part = split[name] - check_class(part, "split.%s" % name) - if part["source"] is not None: - check_hop_class(part["source"]["hop_class"], "split.%s.source" % name) - for name in PARTS: - p = split[name] - if p["status"] == "figure": - cov = p["coverage"] in ("Whole", "Partial") if name == "sink_decode" else p["coverage"] == PART_COVERAGE[name] - ok = p["reason"] is None and p["joules_micro"] >= 1 and cov and (p["source"] is not None) == (name in ("origin", "relay")) + for h in rec["hops"]: + where = "the %s entry" % ROLE_NAME[h["role"]] + check_class(h, where) + if h["source"] is not None: + check_hop_class(h["source"]["hop_class"], where + "'s source") + by_role = {h["role"]: h for h in rec["hops"]} + need(SINK_DECODE in by_role and ORIGIN in by_role, "split_part_invalid", + "the sink decode and the origin always have an entry (§6.2)") + need((DISPLAY in by_role) == (rec["presented_us"] > 0), "split_part_invalid", + "the display has an entry exactly when presented_us is above 0 (§6.2); presented_us %d" % rec["presented_us"]) + for h in rec["hops"]: + role = h["role"] + if h["energy_provenance"] is not None: # a figure (step 3: a class beside a figure of at least 1) + cov = h["coverage"] in ("Whole", "Partial") if role == SINK_DECODE else h["coverage"] == ROLE_COVERAGE[role] + ok = h["unmetered"] is None and h["joules_micro"] >= 1 and cov and (h["source"] is not None) == (role in (ORIGIN, RELAY)) else: - ok = marker(p) and p["coverage"] is None and p["source"] is None and p["reason"] in PART_REASONS[name] - need(ok, "split_part_invalid", "split.%s is neither a figure nor a named absence (§6.2)" % name) - d, dp = rec["display"], split["display"] - if d is None: - need(dp["status"] == "absent" and dp["reason"] == "display_undeclared", "split_display_mismatch", - "no display declared, and the part is not absent for display_undeclared (§6.3)") - else: + ok = marker(h) and h["coverage"] is None and h["source"] is None and h["unmetered"] in ROLE_REASONS[role] + need(ok, "split_part_invalid", "the %s entry is neither a figure nor a marker with its reason (§6.2)" % ROLE_NAME[role]) + d = rec["display"] + if d is not None: need(d["on_mode_mw"] >= 1 and d["reference_nits"] >= 1 and d["relative_ppm"] >= 1 and len(d["basis"]) > 0, "split_display_mismatch", "the display's declaration is out of range (§6.3)") - e = estimate_display(d["on_mode_mw"], d["nits"], rec["presented_us"], d["reference_nits"]) - if e >= 1: - need(dp["status"] == "figure" and dp["joules_micro"] == e and dp["energy_provenance"] == "Estimator", - "split_display_mismatch", "the display part is not the Estimator figure %d (energy-measurement §9.5)" % e) + dp = by_role.get(DISPLAY) + if dp is not None: + if d is None: + need(dp["energy_provenance"] is None and dp["unmetered"] == "display_undeclared", "split_display_mismatch", + "no display declared, and the entry is not the marker display_undeclared (§6.3)") else: - need(dp["status"] == "absent" and dp["reason"] == "unmetered", "split_display_mismatch", - "E is 0, and the display part is not absent for unmetered (§6.3)") - for name in ("origin", "relay"): - check_hop(name, split[name], held_hops) + e = estimate_display(d["on_mode_mw"], d["nits"], rec["presented_us"], d["reference_nits"]) + if e >= 1: + need(dp["energy_provenance"] == "Estimator" and dp["joules_micro"] == e, "split_display_mismatch", + "the display entry is not the Estimator figure %d (energy-measurement §9.5)" % e) + else: + need(dp["energy_provenance"] is None and dp["unmetered"] == "unmetered", "split_display_mismatch", + "E is 0, and the display entry is not the marker unmetered (§6.3)") + for role in (ORIGIN, RELAY): + if role in by_role: + check_hop(ROLE_NAME[role], by_role[role], held_hops) if segments: ids = [] for k, s in enumerate(segments): @@ -997,11 +1013,11 @@ def verify_session(rec, segments, tables, held_hops): "session_streams_mismatch", "stream %s: its count or last identity" % stream) total = sum(s["media_us"] for s in segments) need(rec["media_us"] == total, "session_streams_mismatch", "media_us %d, the receipts sum to %d" % (rec["media_us"], total)) - want, got = expected_decode(segments), split["sink_decode"] + want, got = expected_decode(segments), by_role[SINK_DECODE] if want["status"] == "absent": - ok = got["status"] == "absent" and got["reason"] == "unmetered" + ok = got["energy_provenance"] is None and got["unmetered"] == "unmetered" else: - ok = (got["status"] == "figure" and got["joules_micro"] == want["joules_micro"] + ok = (got["energy_provenance"] is not None and got["joules_micro"] == want["joules_micro"] and got["energy_provenance"] == want["energy_provenance"] and got["energy_counter_filtering"] == want["energy_counter_filtering"] and got["energy_calibration_ref"] == want["energy_calibration_ref"] @@ -1009,7 +1025,7 @@ def verify_session(rec, segments, tables, held_hops): if ok and want["window_us"] is not None: ok = (got["energy_uncertainty"]["window_us"] == want["window_us"] and got["energy_uncertainty"]["relative_ppm"] >= want["bound"]) - need(ok, "split_decode_mismatch", "sink_decode is not what §6.3 gives: %r" % (want,)) + need(ok, "split_decode_mismatch", "the sink-decode entry is not what §6.3 gives: %r" % (want,)) return identity(rec, SESSION) @@ -1458,37 +1474,35 @@ def mutations(doc, tables): put("a chain's prev", r18, chain_prev, "segment_chain_broken", r18) # 19–20. a hop's share, a hop's record - def held_hop(c): - if c["subject"] != "session": - return False - for p in ("origin", "relay"): - src = c["record"]["split"][p]["source"] - if src is not None and src["kind"] == "live-metering-hop" and src["record"] in (c.get("hops") or []): - return True - return False + def entry(c, role): + return next((h for h in c["record"]["hops"] if h["role"] == role), None) def hop_part(x): - return next(p for p in ("origin", "relay") if x["record"]["split"][p]["source"] is not None - and x["record"]["split"][p]["source"]["kind"] == "live-metering-hop" - and x["record"]["split"][p]["source"]["record"] in (x.get("hops") or [])) + """The origin's or relay's figure entry whose live-metering record the case holds.""" + return next((h for h in x["record"]["hops"] if h["role"] in (ORIGIN, RELAY) and h["source"] is not None + and h["source"]["kind"] == "live-metering-hop" and h["source"]["record"] in (x.get("hops") or [])), None) + + def held_hop(c): + return c["subject"] == "session" and hop_part(c) is not None r19 = find(held_hop, True) def share_den(x, d): - x["record"]["split"][hop_part(x)]["source"]["share_den"] += 1 + hop_part(x)["source"]["share_den"] += 1 resign(x["record"], SESSION) put("a hop's share", r19, share_den, "split_hop_mismatch", r19) c20 = find(held_hop) def hop_byte(x, d): - dg = x["record"]["split"][hop_part(x)]["source"]["record"] + dg = hop_part(x)["source"]["record"] d["hops"][dg] = flip_hex(d["hops"][dg], 86) put("a hop record's byte", c20, hop_byte, "split_hop_mismatch") # 21. the display's figure - r21 = find(lambda c: c["subject"] == "session" and c["record"]["split"]["display"]["status"] == "figure", True) + r21 = find(lambda c: c["subject"] == "session" and entry(c, DISPLAY) is not None + and entry(c, DISPLAY)["energy_provenance"] is not None, True) def display_plus(x, d): - x["record"]["split"]["display"]["joules_micro"] += 1 + entry(x, DISPLAY)["joules_micro"] += 1 resign(x["record"], SESSION) put("the display's figure", r21, display_plus, "split_display_mismatch", r21) @@ -1502,12 +1516,21 @@ def mutations(doc, tables): put("a session's media_us", r22, media_plus, "session_streams_mismatch", r22) c23 = find(holding) put("a session's segment left out", c23, lambda x, d: x["segments"].pop(), "session_streams_mismatch") - r24 = find(lambda c: holding(c) and c["record"]["split"]["sink_decode"]["status"] == "figure", True) + r24 = find(lambda c: holding(c) and entry(c, SINK_DECODE) is not None + and entry(c, SINK_DECODE)["energy_provenance"] is not None, True) def decode_plus(x, d): - x["record"]["split"]["sink_decode"]["joules_micro"] += 1 + entry(x, SINK_DECODE)["joules_micro"] += 1 resign(x["record"], SESSION) - put("the sink-decode part's figure", r24, decode_plus, "split_decode_mismatch", r24) + put("the sink-decode entry's figure", r24, decode_plus, "split_decode_mismatch", r24) + + # 24b. the display entry left out of a session that presented content + r24b = find(lambda c: c["subject"] == "session" and entry(c, DISPLAY) is not None, True) + + def display_out(x, d): + x["record"]["hops"] = [h for h in x["record"]["hops"] if h["role"] != DISPLAY] + resign(x["record"], SESSION) + put("the display entry left out", r24b, display_out, "split_part_invalid", r24b) # 25–27. the corpus's own statements c25 = find(lambda c: is_hex(c.get("identity"), 32)) diff --git a/wai/wai-rs/src/sink_energy.rs b/wai/wai-rs/src/sink_energy.rs index 7fb3d5b7a..7465a9751 100644 --- a/wai/wai-rs/src/sink_energy.rs +++ b/wai/wai-rs/src/sink_energy.rs @@ -1489,6 +1489,7 @@ pub fn verify_session(s: &Value, segments: &[Value], tables: &[Value], hops: &BT } } } + check_display_ranges(s)?; if let Some(d) = by_role.get(&DISPLAY) { check_display(s, d, classes[&DISPLAY].as_ref())?; } @@ -1504,6 +1505,17 @@ pub fn verify_session(s: &Value, segments: &[Value], tables: &[Value], hops: &BT identity(Kind::Session, s).ok_or_else(|| Refusal { reason: Reason::RecordMalformed, why: "no identity".into() }) } +/// §6.3: a display declaration's ranges, wherever there is one. +fn check_display_ranges(s: &Value) -> R<()> { + let d = &s["display"]; + if d.is_null() { + return Ok(()); + } + let g = |k: &str| int(&d[k]).unwrap(); + let ok = g("on_mode_mw") >= 1 && g("reference_nits") >= 1 && g("relative_ppm") >= 1 && !d["basis"].as_str().unwrap_or("").is_empty(); + ensure(ok, Reason::SplitDisplayMismatch, || "the display declaration is outside its ranges".into()) +} + fn check_display(s: &Value, part: &Value, class: Option<&Class>) -> R<()> { let bad = |w: &str| refuse(Reason::SplitDisplayMismatch, w.to_string()); let d = &s["display"]; @@ -1511,9 +1523,6 @@ fn check_display(s: &Value, part: &Value, class: Option<&Class>) -> R<()> { return if part["unmetered"] == "display_undeclared" { Ok(()) } else { bad("no declaration, and not the marker display_undeclared") }; } let g = |k: &str| int(&d[k]).unwrap(); - if g("on_mode_mw") < 1 || g("reference_nits") < 1 || g("relative_ppm") < 1 || d["basis"].as_str().unwrap_or("").is_empty() { - return bad("the declaration is outside its ranges"); - } let e = display_estimate(g("on_mode_mw"), g("nits"), int(&s["presented_us"]).unwrap(), g("reference_nits")).unwrap_or(u128::MAX); if e == 0 { return if part["unmetered"] == "unmetered" { Ok(()) } else { bad("the estimate is 0, and the entry is not the marker unmetered") }; -- 2.54.0 (Apple Git-157) From 9a88ae45c19a419f5de9580705f783c47a9adbae Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:25:29 -0400 Subject: [PATCH 063/242] sink-energy: clippy notes in the new files resolved Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/bin/wai_sink_table.rs | 18 +++++++++++------- wai/wai-rs/src/sink_energy.rs | 10 +++++----- wai/wai-rs/src/sink_energy/corpus.rs | 12 +++++++----- wai/wai-rs/src/sink_meter.rs | 4 ++++ 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/wai/wai-rs/src/bin/wai_sink_table.rs b/wai/wai-rs/src/bin/wai_sink_table.rs index faa64d84a..924e74fca 100644 --- a/wai/wai-rs/src/bin/wai_sink_table.rs +++ b/wai/wai-rs/src/bin/wai_sink_table.rs @@ -214,7 +214,7 @@ fn picture(w: u32, h: u32, seed: u64) -> Vec { } /// The codec wrappers' inputs, written from fixed seeds and encoded once. -#[allow(unused_variables)] +#[allow(unused_variables, clippy::ptr_arg)] // without `codecs` it adds nothing fn codec_cases(cases: &mut Vec, cpu: &[&'static str]) { #[cfg(feature = "codecs")] { @@ -306,6 +306,7 @@ fn codec_cases(cases: &mut Vec, cpu: &[&'static str]) { } /// The platform media tool's clips: each decoded in software and on the hardware decoder. +#[allow(clippy::too_many_arguments)] fn platform_cases(cases: &mut Vec, absent: &mut Vec, tool: Option, work_dir: &Path, children: &Children, cpu: &[&'static str], gpu: &[&'static str], notes: &mut Vec) { let Some(tool) = tool.filter(|t| t.exists() && cfg!(target_os = "macos")) else { notes.push("platform media tool: none on this sink; no hardware decode path is reachable from the procedure".into()); @@ -406,6 +407,9 @@ fn decodes(cap: &str) -> bool { (cfg!(feature = "neural_int") && int.contains(&cap)) || (cfg!(feature = "codecs") && classic.contains(&cap)) } +/// One read of every counter, with the instant of each read. +type Reads = Vec<(Option, Instant)>; + /// One round of one case: the readings of every counter. fn round(case: &Case, counters: &mut [Box], workers: usize) -> Vec { std::thread::sleep(SETTLE); @@ -425,8 +429,8 @@ fn round(case: &Case, counters: &mut [Box], workers: usize) -> Vec< }) .collect(); let mut out: Vec = counters.iter().map(|_| RoundReadings::default()).collect(); - let mut read_all = |counters: &mut [Box]| -> Vec<(Option, Instant)> { counters.iter_mut().map(|c| (c.read_nj(), Instant::now())).collect() }; - let bracket = |counters: &mut [Box], n: usize, read_all: &mut dyn FnMut(&mut [Box]) -> Vec<(Option, Instant)>| -> Vec> { + let mut read_all = |counters: &mut [Box]| -> Reads { counters.iter_mut().map(|c| (c.read_nj(), Instant::now())).collect() }; + let bracket = |counters: &mut [Box], n: usize, read_all: &mut dyn FnMut(&mut [Box]) -> Reads| -> Vec> { let mut last = read_all(counters); let mut iv: Vec> = counters.iter().map(|_| Vec::new()).collect(); for _ in 0..n { @@ -525,10 +529,10 @@ fn main() { eprintln!("round {}/{rounds}: {} {} ({:.0} s)", r + 1, cases[i].capability, cases[i].path, started.elapsed().as_secs_f64()); let got = round(&cases[i], &mut counters, workers); for (pi, p) in plans.iter().enumerate() { - if p.case == i { - if let Some(ci) = p.counter { - readings[pi].push(got[ci].clone()); - } + if p.case == i + && let Some(ci) = p.counter + { + readings[pi].push(got[ci].clone()); } } } diff --git a/wai/wai-rs/src/sink_energy.rs b/wai/wai-rs/src/sink_energy.rs index 7465a9751..de67d9a56 100644 --- a/wai/wai-rs/src/sink_energy.rs +++ b/wai/wai-rs/src/sink_energy.rs @@ -257,7 +257,7 @@ pub fn hex(b: &[u8]) -> String { } fn unhex(s: &str) -> Option> { - if s.len() % 2 != 0 || !s.bytes().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) { + if !s.len().is_multiple_of(2) || !s.bytes().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) { return None; } (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok()).collect() @@ -1190,10 +1190,10 @@ fn check_row(i: usize, row: &Value, class: Option<&Class>, sample_us: u64, live: if profile_name.is_some() != (reason != Some("no_decoder")) { return Err(st("profile is null exactly for no_decoder".into())); } - if let Some(p) = profile_name { - if live.contains(&p) == (status == "absent" && reason == Some("no_counter")) { - return Err(st(format!("profile {p}'s survey status does not fit the row"))); - } + if let Some(p) = profile_name + && live.contains(&p) == (status == "absent" && reason == Some("no_counter")) + { + return Err(st(format!("profile {p}'s survey status does not fit the row"))); } if status == "absent" { let ok = reason.is_some_and(|r| ABSENT_REASONS.contains(&r)) diff --git a/wai/wai-rs/src/sink_energy/corpus.rs b/wai/wai-rs/src/sink_energy/corpus.rs index 90adb5bb9..1d80c71a0 100644 --- a/wai/wai-rs/src/sink_energy/corpus.rs +++ b/wai/wai-rs/src/sink_energy/corpus.rs @@ -66,6 +66,7 @@ struct SynthRow { readings: Vec, } +#[allow(clippy::too_many_arguments)] fn synth(cap: &str, path: &'static str, profile: Option<&'static str>, unit: &'static str, units: u64, wh: Option<(u64, u64)>, plan: Result<(i64, i64, i64, u64), &'static str>, base: u64) -> SynthRow { let (readings, rounds) = match plan { Err(why) => (Vec::new(), Err(why)), @@ -94,7 +95,7 @@ fn synth(cap: &str, path: &'static str, profile: Option<&'static str>, unit: &'s /// The corpus's reference table and its readings, signed by `signer`. fn reference_table(signer: &SigningKey) -> (Value, Value) { - let mut rows = vec![ + let mut rows = [ // clean: 3 W over a 2 W idle with 30 mW of jitter synth("wai.video.av1", "software", Some("macos-task"), "frame", 60, Some((1280, 720)), Ok((2000, 3000, 30, 1000)), 100), // relative: 1.4 W over the idle with 200 mW of jitter @@ -343,6 +344,7 @@ fn mutate(kind: Kind, record: &Value, key: &SigningKey, f: impl FnOnce(&mut Valu sign(kind, v, key) } +#[allow(clippy::too_many_arguments)] fn case(name: &str, subject: &str, record: Option<&Value>, readings: Option<&Value>, tables: &[&Value], segments: &[&Value], hops: &[String], expect: &str) -> Value { json!({ "name": name, "subject": subject, "record": record, "readings": readings, @@ -603,10 +605,10 @@ pub fn verify(dir: &Path) -> Vec { } if code != c["expect"].as_str().unwrap_or("") { fails.push(format!("{}: expected {}, got {code} ({:?})", c["name"], c["expect"], got.err().map(|r| r.why))); - } else if let Ok(id) = got { - if c["identity"].as_str() != Some(hex(&id).as_str()) { - fails.push(format!("{}: identity", c["name"])); - } + } else if let Ok(id) = got + && c["identity"].as_str() != Some(hex(&id).as_str()) + { + fails.push(format!("{}: identity", c["name"])); } } for r in Reason::ALL { diff --git a/wai/wai-rs/src/sink_meter.rs b/wai/wai-rs/src/sink_meter.rs index 13cfaa43c..76d89a63d 100644 --- a/wai/wai-rs/src/sink_meter.rs +++ b/wai/wai-rs/src/sink_meter.rs @@ -263,6 +263,8 @@ struct IoFns { const UTF8: u32 = 0x0800_0100; impl IoFns { + // Each pointer becomes the field's own function type, which the struct states. + #[allow(clippy::missing_transmute_annotations)] fn load() -> Option { let cf = open_lib("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")?; let io = open_lib("/usr/lib/libIOReport.dylib")?; @@ -471,6 +473,8 @@ pub struct Nvml { unsafe impl Send for Nvml {} impl Nvml { + // Each pointer becomes the field's own function type, which the struct states. + #[allow(clippy::missing_transmute_annotations)] pub fn open() -> Option { let h = open_lib("libnvidia-ml.so.1")?; unsafe { -- 2.54.0 (Apple Git-157) From 4e1644725dd5e9181c4a7e8655c4c72f2244f165 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:26:10 -0400 Subject: [PATCH 064/242] sink-energy: npu, and no bracket attribution: a shared counter's figure is the whole decode's Settled with the per-fabric record: the fabric term is npu; task is the operating system's account of the sink's own processes; rail is a sole user's whole rail. A counter every user shares gives the sink no attribution: its figure is the bracket over the whole decode, not a fabric's share, and like a Partial figure it carries no division by fabric. Co-Authored-By: Claude Opus 5.5 --- wai/CHANGELOG.md | 11 +++++---- wai/extensions/energy-measurement.md | 37 +++++++++++++++++----------- wai/extensions/sink-energy.md | 2 +- wai/wai-rs/src/sink_energy.rs | 4 +-- 4 files changed, 31 insertions(+), 23 deletions(-) diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index 13ba94260..ae7c4bc65 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -10,11 +10,12 @@ wire format may change and such changes are recorded here. - **Sink energy profiles** ([energy-measurement](extensions/energy-measurement.md) §9). For each kind of platform: the counter a sink reads for its decode, the fabrics it covers (`cpu`, - `gpu`, `media`, `neural`) and what else it sums, how the reading is the sink's (`task`: the - operating system's account of the sink's own processes; `bracket`: §3's marginal over an - idle bracket on a counter every user shares), and the shortest window. No counter is ever - divided among its users by their busy time; a whole rail is one user's only where that user - was its sole user, at least 99% of the busy time (`rail`). `macos-task` is the kernel's + `gpu`, `media`, `npu`) and what else it sums, whether the reading is the sink's own (`task`: + the operating system's account of the sink's processes, `ri_energy_nj + ri_billed_energy − + ri_serviced_energy` on macOS) or a counter every user shares, from which the figure is §3's + bracket over the whole decode and no fabric's share, and the shortest window. No counter is + ever divided among its users by their busy time; a whole rail is one user's only where that + user was its sole user, at least 99% of the busy time (`rail`). `macos-task` is the kernel's per-process energy with the work billed to the process (`proc_pid_rusage`, `RUSAGE_INFO_V6`); `macos-gpu` is IOReport's `GPU Energy`; `linux-rapl` is the powercap package zone; `linux-nvml` is the NVIDIA total energy counter behind a plausibility rule. diff --git a/wai/extensions/energy-measurement.md b/wai/extensions/energy-measurement.md index ec61d0caa..019c0eb1f 100644 --- a/wai/extensions/energy-measurement.md +++ b/wai/extensions/energy-measurement.md @@ -1110,24 +1110,31 @@ term in its heading. | item | meaning | |---|---| | counter | the interface and the channel read, its unit, and where it wraps | -| fabrics | the kinds of processor its reading covers — `cpu`, `gpu`, `media` (a hardware video engine), `neural` (a neural-network engine) — and what else it sums with them | -| attribution | how the reading is the sink's: `task` or `bracket` (below) | +| fabrics | the kinds of processor its reading covers — `cpu`, `gpu`, `media` (a hardware video engine), `npu` (a neural-network engine) — and what else it sums with them | +| attribution | `task` where the reading is the sink's own (below); none where the counter is every user's and the figure is §3's bracket over the whole decode | | idle baseline | how the energy present without the decode is taken off | | sampling interval and minimum window | the time between reads, and the shortest busy window a figure may come from | | class | the class a reading under the profile is sealed with (§2) | -The attributions: +Attribution: - **`task`**: the operating system's account of the energy the sink's own processes spent - on the fabric, which other work on the host does not enter. -- **`bracket`**: the counter counts every user of its fabrics, and the sink's figure is - §3's marginal over an idle bracket, with §4's ratio deciding whether it is one. + on the fabric, which other work on the host does not enter: on macOS `ri_energy_nj + + ri_billed_energy − ri_serviced_energy` (§9.2). +- **`rail`**: a fabric's whole rail, attributed to one user only where that user was its + sole user, at least 99% of the busy time every user accrued over the span, the rail and + the busy times sampled every 200 ms through it; a span in which a user's account vanishes + or a difference goes negative is refused. No sink profile of this revision establishes + that for a processor; §6.4 of [sink-energy](sink-energy.md) applies the rule to a hop. +- **No attribution.** A counter every user shares, and that no rule above attributes, gives + the sink no share of its own. The sink's figure is then §3's marginal over an idle + bracket, over the whole decode, with §4's ratio deciding whether it is one. It is a + figure of the whole decode and not one fabric's share, so it carries no division of the + figure by fabric; neither does a `Partial` figure, whose fabrics are not all metered. - A counter is never divided among its users by the time each kept it busy: busy time does not apportion energy (a fabric busy at a low clock and at a high clock accrues the same - time and draws very differently). A whole rail is attributed to one user only where that - user was its sole user, at least 99% of the busy time every user accrued over the span - (attribution `rail`); no sink profile of this revision establishes that, and §6.4 of - [sink-energy](sink-energy.md) applies the rule to a hop. + time and draws very differently). A reading that is missing or frozen is no reading, and + is never sealed as a share of zero. Every profile shares these rules: @@ -1135,8 +1142,8 @@ Every profile shares these rules: idle bracket read with the decode not running, and a busy bracket read over it. Under `task` attribution the idle bracket is the sink's own processes with their decode workers parked, so its mean is the sink's own overhead (the reads of the counter among - it). Under `bracket` attribution it is the whole machine's draw on the counter, and §4's - ratio decides whether the decode rose clear of it. + it). With no attribution it is the whole machine's draw on the counter, and §4's ratio + decides whether the decode rose clear of it. - **Liveness and the window.** Both brackets are read at the profile's sampling interval, and §3.1's liveness rule applies to each. A busy window shorter than the profile's minimum window gives no figure. @@ -1187,7 +1194,7 @@ read without privileges. | item | value | |---|---| | fabrics | `gpu`: the graphics processor's whole rail | -| attribution | `bracket` | +| attribution | none: §3's bracket over the whole decode | | idle baseline | the machine's graphics draw before the burst (§3) | | sampling interval | 200 ms | | minimum window | 1 s, five sampling intervals | @@ -1218,7 +1225,7 @@ reference sinks. | item | value | |---|---| | fabrics | `cpu`: the processor package, which also sums its uncore and, where the package holds one, its integrated graphics | -| attribution | `bracket` | +| attribution | none: §3's bracket over the whole decode | | idle baseline | the machine's package draw before the burst (§3) | | sampling interval | 200 ms | | minimum window | 1 s, five sampling intervals | @@ -1238,7 +1245,7 @@ paragraph 8.6). | item | value | |---|---| | fabrics | `gpu`: the accelerator board | -| attribution | `bracket` | +| attribution | none: §3's bracket over the whole decode | | idle baseline | the board's draw before the burst (§3) | | sampling interval | 1 s | | minimum window | 5 s, five sampling intervals | diff --git a/wai/extensions/sink-energy.md b/wai/extensions/sink-energy.md index dc5400ea3..06d55b025 100644 --- a/wai/extensions/sink-energy.md +++ b/wai/extensions/sink-energy.md @@ -511,7 +511,7 @@ refusal. - `width` and `height` are integers of at least 1 for `image` and `frame`, and `null` for the other units; `units` is at least 1; - `decoder` follows its grammar (§4.2); - - every member of a `fabrics` array is one of `cpu`, `gpu`, `media` and `neural` + - every member of a `fabrics` array is one of `cpu`, `gpu`, `media` and `npu` (energy-measurement §9.1), none twice; - `measured` is a date `YYYY-MM-DD`; - a `source` (§6.4) and a `display` (§6.3) have exactly their members, of their types; diff --git a/wai/wai-rs/src/sink_energy.rs b/wai/wai-rs/src/sink_energy.rs index de67d9a56..c1922605c 100644 --- a/wai/wai-rs/src/sink_energy.rs +++ b/wai/wai-rs/src/sink_energy.rs @@ -56,7 +56,7 @@ pub const ESTIMATOR_CPU: &str = "estimator-cpu-time/1"; pub const ESTIMATOR_DISPLAY: &str = "estimator-display/1"; /// The machine profiles of energy-measurement §9.2 and §9.3: name, the fabric its reading -/// covers (the fabric vocabulary `cpu`, `gpu`, `media`, `neural`), its minimum window in +/// covers (the fabric vocabulary `cpu`, `gpu`, `media`, `npu`), its minimum window in /// microseconds. In ascending order of name. pub const PROFILES: [(&str, &str, u64); 4] = [ ("linux-nvml", "gpu", 5_000_000), @@ -384,7 +384,7 @@ const METHODS: &[&str] = &["measured", "table", "estimator", "unmetered"]; const MACHINE_PROFILES: &[&str] = &["linux-nvml", "linux-rapl", "macos-gpu", "macos-task"]; const SEGMENT_PROFILES: &[&str] = &["linux-nvml", "linux-rapl", "macos-gpu", "macos-task", ESTIMATOR_CPU]; const COUNTER_STATUSES: &[&str] = &["live", "frozen", "needs-privilege", "no-counter", "not-implemented", "implausible"]; -const FABRICS: &[&str] = &["cpu", "gpu", "media", "neural"]; +const FABRICS: &[&str] = &["cpu", "gpu", "media", "npu"]; const PART_REASONS: &[&str] = &["not_reported", "hop_unmetered", "hop_shared", "unmetered", "display_undeclared"]; const HOP_KINDS: &[&str] = &["live-metering-hop", "other"]; const PANEL_CLASSES: &[&str] = &["backlit", "emissive"]; -- 2.54.0 (Apple Git-157) From 3d79462a0981f13f10936c4b30d25d4d1c248ff4 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:26:51 -0400 Subject: [PATCH 065/242] sink_energy_verify.py: the measured tables are required Co-Authored-By: Claude Opus 5.5 --- wai/live-conformance/sink_energy_verify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wai/live-conformance/sink_energy_verify.py b/wai/live-conformance/sink_energy_verify.py index 79d817bde..3ceb81629 100644 --- a/wai/live-conformance/sink_energy_verify.py +++ b/wai/live-conformance/sink_energy_verify.py @@ -1160,7 +1160,7 @@ def load_tables(corpus_dir): # §9 has the corpus hold the reference sinks' measured tables. While they are being taken, a # corpus without them is a NOTE, not a failure; set this to True once they are committed. -REQUIRE_TABLES = False +REQUIRE_TABLES = True def check_tables(tables, quiet=False, prefix="tables/"): -- 2.54.0 (Apple Git-157) From b4da2ed71d891134b2793d191deb909c7183ac8e Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:29:37 -0400 Subject: [PATCH 066/242] sink-energy corpus: another key's table sealed ModelBased is refused Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/sink_energy/corpus.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wai/wai-rs/src/sink_energy/corpus.rs b/wai/wai-rs/src/sink_energy/corpus.rs index 1d80c71a0..7e7f15fce 100644 --- a/wai/wai-rs/src/sink_energy/corpus.rs +++ b/wai/wai-rs/src/sink_energy/corpus.rs @@ -468,6 +468,7 @@ pub fn generate() -> String { v["table"]["per_completion_uj"] = pc.into(); v["joules_micro"] = (table_figure(pc, units, 60).unwrap() as u64).into(); })), None, &[t], &[], &[], "segment_table_mismatch")); + cases.push(case("segment, another key's table sealed ModelBased", "segment", Some(&m(&b1, &|v| { v["energy_provenance"] = "ModelBased".into(); })), None, &[&foreign], &[], &[], "segment_table_mismatch")); cases.push(case("segment, an estimate one microjoule high", "segment", Some(&m(&b0, &|v| { let j = v["joules_micro"].as_u64().unwrap(); v["joules_micro"] = (j + 1).into(); -- 2.54.0 (Apple Git-157) From 85e7363577f0156d10f79323e9d8fd1c22da04d9 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:30:01 -0400 Subject: [PATCH 067/242] sink-energy: corpus regenerated with the case Co-Authored-By: Claude Opus 5.5 --- wai/live-conformance/sink-energy/vectors.json | 798 ++++++++++++++++++ 1 file changed, 798 insertions(+) diff --git a/wai/live-conformance/sink-energy/vectors.json b/wai/live-conformance/sink-energy/vectors.json index f8ac35a4e..61cfa1312 100644 --- a/wai/live-conformance/sink-energy/vectors.json +++ b/wai/live-conformance/sink-energy/vectors.json @@ -23963,6 +23963,804 @@ "expect": "segment_table_mismatch", "identity": null }, + { + "name": "segment, another key's table sealed ModelBased", + "subject": "segment", + "record": { + "wai_sink_segment": 1, + "signer": "5a1d4b2485a4bd4553d8d6883caa333a1f68ace2d540c4be0255943c315c4455", + "session": "41baa58320a8c76be0b7ec3a98cf7bb021d434f8e4604780adb1b2f2eba84be7", + "stream": "b5f81ab803675c255d930f136aba10537b86be108053deb696b04fbe9ee2dbc2", + "segment": 1, + "prev": "350fa7be2744dc21b7cc5e4cf37fb9462a311266fb3c015a4ab00e454e8280a4", + "object": "ba2aed82898351ea19b5244df36506e731296164a2a7b7bcc52c60128a144071", + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "unit": "frame", + "units": 48, + "width": 1280, + "height": 720, + "media_us": 2000000, + "method": "table", + "profile": "macos-task", + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "bracket": null, + "table": { + "id": "bd74e55dbd7822aec58d2097175e05c974e5d6f93c9b048c3350f94c346dfaff", + "row": 6, + "per_completion_uj": 8284, + "units_per_completion": 60 + }, + "estimator": null, + "reason": null, + "joules_micro": 6627, + "energy_provenance": "ModelBased", + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null, + "sig": "ecb621d2b116a88ca5798b1bd4e0ef770c58d79e3183123373cab8085a22e0c845b57d0360d5ee12015ea1b6bf8c440895c134a8b20ed53750bb97241840d707" + }, + "readings": null, + "tables": [ + { + "wai_sink_table": 1, + "signer": "849e713d6bb5b2daf8790b7d6ed3779d08d3a71fa30fe14b230a5539ff54e6a9", + "procedure": "wai-sink-table/1", + "procedure_sha256": "117a840520b82253417049926d17efcc2f9fdf201842fdd53cca97786bd571ff", + "readings_sha256": "aeab6dc44257cc418228a2a014d149de1762577019b40b8790a66c5c08e1e835", + "machine": { + "os": "corpus", + "arch": "corpus", + "processor": "synthetic", + "cores": 16, + "memory_gib": 64 + }, + "build": { + "profile": "release", + "toolchain": null, + "target": null + }, + "measured": "2026-10-22", + "rounds": 7, + "sample_us": 200000, + "counters": [ + { + "profile": "macos-gpu", + "status": "frozen", + "detail": "Energy Model/GPU Energy" + }, + { + "profile": "macos-task", + "status": "live", + "detail": "proc_pid_rusage V6" + } + ], + "rows": [ + { + "capability": "wai.audio.flac", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "content-ms", + "units": 10000, + "width": null, + "height": null, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 50 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 53 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 56 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 59 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 62 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 65 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000000, + "sd_uw": 0, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2900000, + "sd_uw": 0, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 68 + } + ], + "status": "unmetered", + "reason": "rail_not_live", + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_hardware_path", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 1920, + "height": 1080, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2144400, + "sd_uw": 356790, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2652643, + "sd_uw": 391226, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 400 + }, + { + "idle": { + "n": 10, + "mean_uw": 2212000, + "sd_uw": 397991, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2606000, + "sd_uw": 332414, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 403 + }, + { + "idle": { + "n": 10, + "mean_uw": 2066400, + "sd_uw": 307198, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2586286, + "sd_uw": 342938, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 406 + }, + { + "idle": { + "n": 10, + "mean_uw": 1885700, + "sd_uw": 332615, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2577429, + "sd_uw": 398088, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 409 + }, + { + "idle": { + "n": 10, + "mean_uw": 2074500, + "sd_uw": 452434, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2637429, + "sd_uw": 386990, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 412 + }, + { + "idle": { + "n": 10, + "mean_uw": 2040300, + "sd_uw": 384214, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2626143, + "sd_uw": 418958, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 415 + }, + { + "idle": { + "n": 10, + "mean_uw": 1851400, + "sd_uw": 287343, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 2489429, + "sd_uw": 369320, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 418 + } + ], + "status": "unmetered", + "reason": "below_attribution_floor", + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "image", + "units": 1, + "width": 256, + "height": 256, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "hardware", + "decoder": "platform:media-framework", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 1890800, + "sd_uw": 173618, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3406857, + "sd_uw": 291462, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3000 + }, + { + "idle": { + "n": 10, + "mean_uw": 2003700, + "sd_uw": 258242, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3502000, + "sd_uw": 238914, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3003 + }, + { + "idle": { + "n": 10, + "mean_uw": 2141600, + "sd_uw": 275043, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3416357, + "sd_uw": 204354, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3006 + }, + { + "idle": { + "n": 10, + "mean_uw": 2028200, + "sd_uw": 196893, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3475500, + "sd_uw": 226251, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3009 + }, + { + "idle": { + "n": 10, + "mean_uw": 2040400, + "sd_uw": 214134, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3434357, + "sd_uw": 223672, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3012 + }, + { + "idle": { + "n": 10, + "mean_uw": 2030100, + "sd_uw": 307907, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3404571, + "sd_uw": 219703, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3015 + }, + { + "idle": { + "n": 10, + "mean_uw": 2050600, + "sd_uw": 244479, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 3408571, + "sd_uw": 225896, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 3018 + } + ], + "status": "relative", + "reason": null, + "median": 4, + "range": [ + 1186, + 1413 + ], + "joules_micro": 1294, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 64810, + "window_us": 2800000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-gpu", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_counter", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": "macos-task", + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 1987200, + "sd_uw": 15719, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5002143, + "sd_uw": 17502, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1000 + }, + { + "idle": { + "n": 10, + "mean_uw": 2002700, + "sd_uw": 19682, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5004857, + "sd_uw": 20184, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1003 + }, + { + "idle": { + "n": 10, + "mean_uw": 2000800, + "sd_uw": 18850, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001143, + "sd_uw": 17629, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1006 + }, + { + "idle": { + "n": 10, + "mean_uw": 2002500, + "sd_uw": 18549, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 4999571, + "sd_uw": 16787, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1009 + }, + { + "idle": { + "n": 10, + "mean_uw": 1997400, + "sd_uw": 20824, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5002429, + "sd_uw": 18559, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1012 + }, + { + "idle": { + "n": 10, + "mean_uw": 1996900, + "sd_uw": 17445, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001000, + "sd_uw": 19415, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1015 + }, + { + "idle": { + "n": 10, + "mean_uw": 2008700, + "sd_uw": 13777, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 5001214, + "sd_uw": 17215, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2800000, + "completions": 1018 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 8199, + 8408 + ], + "joules_micro": 8284, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 7284, + "window_us": 2800000 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vvc", + "path": "software", + "decoder": "builtin:wai@0.0.0-corpus", + "profile": null, + "unit": "frame", + "units": 60, + "width": 1280, + "height": 720, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } + ], + "sig": "80d62d05c16342e453409ec2b4cd034c8863029734bf8f1e3fcbdf2c1a969aba26b518f1e6239e427d810d75f612972f78982cb150f45e13ba1bbeb3f825d508" + } + ], + "segments": [], + "hops": [], + "expect": "segment_table_mismatch", + "identity": null + }, { "name": "segment, an estimate one microjoule high", "subject": "segment", -- 2.54.0 (Apple Git-157) From 56ee1469d2f801f29159f72048aa33f9a1282fbe Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:31:05 -0400 Subject: [PATCH 068/242] wai_sink_table: the bracket reader as functions (no behaviour change) Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/bin/wai_sink_table.rs | 43 ++++++++++++++++------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/wai/wai-rs/src/bin/wai_sink_table.rs b/wai/wai-rs/src/bin/wai_sink_table.rs index 924e74fca..b887ba7cc 100644 --- a/wai/wai-rs/src/bin/wai_sink_table.rs +++ b/wai/wai-rs/src/bin/wai_sink_table.rs @@ -410,6 +410,28 @@ fn decodes(cap: &str) -> bool { /// One read of every counter, with the instant of each read. type Reads = Vec<(Option, Instant)>; +fn read_all(counters: &mut [Box]) -> Reads { + counters.iter_mut().map(|c| (c.read_nj(), Instant::now())).collect() +} + +/// `n` intervals of every counter at the sampling interval: `[advance nJ, length µs]` each. +fn bracket(counters: &mut [Box], n: usize) -> Vec> { + let mut last = read_all(counters); + let mut iv: Vec> = counters.iter().map(|_| Vec::new()).collect(); + for _ in 0..n { + std::thread::sleep(SAMPLE); + let now = read_all(counters); + for (k, c) in counters.iter().enumerate() { + if let ((Some(a), ta), (Some(b), tb)) = (last[k], now[k]) { + let t = tb.duration_since(ta).as_micros().max(1) as u64; + iv[k].push((wai::sink_meter::advance(a, b, c.wrap_nj()), t)); + } + } + last = now; + } + iv +} + /// One round of one case: the readings of every counter. fn round(case: &Case, counters: &mut [Box], workers: usize) -> Vec { std::thread::sleep(SETTLE); @@ -429,27 +451,10 @@ fn round(case: &Case, counters: &mut [Box], workers: usize) -> Vec< }) .collect(); let mut out: Vec = counters.iter().map(|_| RoundReadings::default()).collect(); - let mut read_all = |counters: &mut [Box]| -> Reads { counters.iter_mut().map(|c| (c.read_nj(), Instant::now())).collect() }; - let bracket = |counters: &mut [Box], n: usize, read_all: &mut dyn FnMut(&mut [Box]) -> Reads| -> Vec> { - let mut last = read_all(counters); - let mut iv: Vec> = counters.iter().map(|_| Vec::new()).collect(); - for _ in 0..n { - std::thread::sleep(SAMPLE); - let now = read_all(counters); - for (k, c) in counters.iter().enumerate() { - if let ((Some(a), ta), (Some(b), tb)) = (last[k], now[k]) { - let t = tb.duration_since(ta).as_micros().max(1) as u64; - iv[k].push((wai::sink_meter::advance(a, b, c.wrap_nj()), t)); - } - } - last = now; - } - iv - }; - let idle = bracket(counters, IDLE_SAMPLES, &mut read_all); + let idle = bracket(counters, IDLE_SAMPLES); release.wait(); let c0 = done.load(Ordering::SeqCst); - let busy = bracket(counters, BUSY_SAMPLES, &mut read_all); + let busy = bracket(counters, BUSY_SAMPLES); let c1 = done.load(Ordering::SeqCst); stop.store(true, Ordering::Relaxed); for h in handles { -- 2.54.0 (Apple Git-157) From b3f4b48fb7588f660983bde06c89ac1d59552d28 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:33:37 -0400 Subject: [PATCH 069/242] =?UTF-8?q?sink-energy=20=C2=A75:=20a=20segment=20?= =?UTF-8?q?receipt=20is=20the=20sink-decode=20hop's=20figure=20for=20its?= =?UTF-8?q?=20segment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- wai/extensions/sink-energy.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wai/extensions/sink-energy.md b/wai/extensions/sink-energy.md index 06d55b025..c3e2e31f6 100644 --- a/wai/extensions/sink-energy.md +++ b/wai/extensions/sink-energy.md @@ -275,7 +275,11 @@ later revision registers. ## 5. The segment receipt A segment receipt is a sink's statement of what one segment of one stream cost it to -decode. A sink signs one per segment it decoded, in order, each naming the one before it. +decode: the sink-decode hop's figure for that segment (role 3 of §6.2). A sink signs one +per segment it decoded, in order, each naming the one before it. Where the sink also keeps +a stream log, the log's segment record states the same figure and class for role 3 +(stream-termination §4.6), and the receipt here is where its bracket, table row or +estimator inputs are. ### 5.1 The record -- 2.54.0 (Apple Git-157) From 5f7ca8ca8d789759502f2ba0080d0786cc0f49eb Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:36:10 -0400 Subject: [PATCH 070/242] wai-rs: narrow integer kernels on NEON, AVX2 and AVX-512, the same integers The integer decoders' convolutions spent their time in 32x32->64-bit multiply-accumulates, portable Rust on x86_64. A kernel set (Isa) now selects narrow kernels where the processor runs them: 16-bit operands (an input past 2^15 - 1 split into two 15-bit limbs), 32-bit partial sums over blocks of taps sized so no partial sum leaves i32, then 64-bit sums (vpmaddwd / vpdpwssd on x86_64 with two channels per word, smlal on aarch64). The inverse GDN's norms run in 64-bit SIMD lanes on x86_64 (vpmuldq, an exact product, then the floor shift), the NEON ones fuse the shift and the add at gf = 16, and each integer root is seeded from the IEEE-754 square root and corrected in integers. The wide kernels stay, as Isa::Wide, and run wherever a narrow plan does not apply. int_ops lends the interpreter rescale, clamp, relu and leaky on the workers, and requantises in i64 where every value fits (else in the interpreter's i128). max|x| is read in parallel, once per inverse GDN. The equivalence tests run every kernel set the processor runs: random geometry against the exact sums written out, the block edges, the limb edges, the seeded root, the i64 requantisation and the element-wise operators against the interpreter. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/int_audio.rs | 4 +- wai/wai-rs/src/int_kernels.rs | 631 +++++++++++++++++++++++++-- wai/wai-rs/src/int_kernels/narrow.rs | 566 ++++++++++++++++++++++++ wai/wai-rs/src/int_kernels/x86.rs | 396 +++++++++++++++++ wai/wai-rs/src/int_mlicpp.rs | 2 +- wai/wai-rs/src/int_ops.rs | 256 ++++++++++- wai/wai-rs/src/int_transform.rs | 9 +- wai/wai-rs/src/int_transformer.rs | 18 +- wai/wai-rs/src/jpegai_ops.rs | 4 +- 9 files changed, 1818 insertions(+), 68 deletions(-) create mode 100644 wai/wai-rs/src/int_kernels/narrow.rs create mode 100644 wai/wai-rs/src/int_kernels/x86.rs diff --git a/wai/wai-rs/src/int_audio.rs b/wai/wai-rs/src/int_audio.rs index f1d88fea8..e6d987864 100644 --- a/wai/wai-rs/src/int_audio.rs +++ b/wai/wai-rs/src/int_audio.rs @@ -674,7 +674,7 @@ mod geometry { let go = || crate::int_ops::run1(g, Tensor::new(x.len() as u32, 1, 1, x.to_vec()), &[t_out]).map(|t| t.data).map_err(|r| r.code); let want = with_policy(Policy::reference(), go); let mut runs = Vec::new(); - for p in FAST { + for &p in FAST.iter() { let (got, n) = fast_conv_runs(|| with_policy(p, go)); assert_eq!(got, want, "{p:?} differs from the interpreter alone"); runs.push(n); @@ -710,7 +710,7 @@ mod geometry { let g = ops::load(&model).expect("the model lowers and loads"); let go = |p: Policy| fast_conv_runs(|| with_policy(p, || crate::int_ops::run1(&g, Tensor::new(1, 1, 3, vec![1, 2, 3]), &[1]).map_err(|r| r.code))); assert!(matches!(go(Policy::reference()), (Err(Code::TooLarge), 0))); - for p in FAST { + for &p in FAST.iter() { assert!(matches!(go(p), (Err(Code::TooLarge), 0)), "{p:?}"); } } diff --git a/wai/wai-rs/src/int_kernels.rs b/wai/wai-rs/src/int_kernels.rs index 17cbc1938..1caffb0b3 100644 --- a/wai/wai-rs/src/int_kernels.rs +++ b/wai/wai-rs/src/int_kernels.rs @@ -29,11 +29,13 @@ //! take adjacent output elements. No output element's sum is split between threads //! or lanes. //! * **Each output element adds the same terms, computed the same way, in the same -//! order and accumulator type as the reference, apart from terms that are zero.** -//! A kernel may add terms the reference skips because they are zero (a zero input, -//! a zero-padding sample) and may omit terms the reference adds although they are -//! zero (a tap whose weight is zero for every channel of a block). Adding or -//! omitting a zero changes no partial sum, and a zero product cannot overflow. +//! order and accumulator type as the reference, apart from terms that are zero,** +//! in the wide kernels ([`Isa::Wide`]). A kernel may add terms the reference skips +//! because they are zero (a zero input, a zero-padding sample) and may omit terms the +//! reference adds although they are zero (a tap whose weight is zero for every +//! channel of a block). Adding or omitting a zero changes no partial sum, and a zero +//! product cannot overflow. The narrow kernels (below) add the same nonzero terms in +//! another grouping, under bounds that keep every value they form exact. //! * **A narrower type is used only when a range check over the actual operands //! proves it yields the same integer.** A 32×32→64-bit product only when both //! operands have `|v| ≤ i32::MAX`; an `i64` accumulator in place of the reference's @@ -139,6 +141,42 @@ //! (`tests/int_kernels_vectors.rs`). The `wai-conformance` workflow runs all of //! these, in both profiles, on each of its platform legs. //! +//! # The narrow kernels +//! +//! Integer addition is exact wherever no value leaves its type, so an output's sum is +//! the same integer however its terms are grouped, provided no grouping forms a value +//! its type cannot hold. The narrow kernels ([`Isa::Neon`], [`Isa::Avx2`], +//! [`Isa::Avx2Vnni`], [`Isa::Avx512`], [`Isa::Avx512Vnni`]) use that, for convolutions +//! and transposed convolutions whose weights all have `|w| ≤ 2^15 − 1` and inputs `|x| < +//! 2^30`, within the reference's own bound above (`terms · max|w| · max|x| ≤ i64::MAX`): +//! +//! * **16-bit operands.** An input with `|x| ≤ 2^15 − 1` is one 16-bit operand; a wider +//! one is split into `hi = ⌊x / 2^15⌋` and `lo = x − hi·2^15`, each 16-bit, and +//! `Σ w·x = 2^15·Σ w·hi + Σ w·lo` (two passes over the taps). +//! * **32-bit partial sums in blocks.** A 32-bit lane sums one output element's +//! products over at most `kblk` taps, with `kblk · (products per tap) · max|w| · +//! max|operand| ≤ i32::MAX`, so no partial sum of a block can leave `i32`; the block's +//! sum is then sign-extended, scaled by its limb's power of two and added into the +//! element's 64-bit sum, every one of which stays within `Σ_limbs terms · max|w| · +//! max|operand| · 2^shift ≤ i64::MAX` (the plan in `narrow.rs` refuses otherwise, and +//! the wide kernels run). +//! * **What the lanes do.** On x86_64 two input channels share a 32-bit word and +//! `vpmaddwd` / `vpdpwssd` multiply both 16-bit pairs and add them into the lane; on +//! aarch64 `smlal` multiplies a 16-bit input by a 16-bit weight and adds the product +//! into a 32-bit lane. Each wraps on +//! overflow, and none can overflow under the bounds above, so each lane holds the +//! exact sum of its block. Rows not a multiple of the vector width finish in scalar +//! 64-bit arithmetic (on AVX-512, under a lane mask). +//! +//! The narrow kernel sets also run the inverse GDN's norms in SIMD 64-bit lanes on x86_64 +//! (each term `(γ·x²) >> gf` formed exactly from `i32` operands by `vpmuldq`, floored by +//! an arithmetic shift, added in `j` order as the reference adds it), fuse the shift and +//! the addition at `gf = 16` on NEON (`ssra`), and seed each integer square root from the +//! IEEE-754 square root before correcting it in integers ([`isqrt_u64`]), which gives +//! the integer root for any seed. [`Isa::Wide`] runs the kernels as they were before the +//! narrow sets, and every set is held to the reference by the same tests ([`isas`] lists +//! the sets a processor runs; the tests run each). +//! //! # Threads and SIMD //! //! Threads are `std::thread::scope` workers pulling tiles from a shared queue — no @@ -148,9 +186,14 @@ //! process-wide with the `WAI_INT_THREADS` environment variable, capped so each //! worker gets a minimum amount of work). //! -//! On aarch64 the 32×32→64-bit convolution and IGDN kernels use NEON widening +//! The kernel set is [`Policy::isa`]: [`Isa::Auto`] takes the best set the processor +//! runs, or the one the `WAI_INT_ISA` environment variable names where it runs. In the +//! wide set, on aarch64 the 32×32→64-bit convolution and IGDN kernels use NEON widening //! multiply-accumulate (`std::arch`, confined to the `neon` submodule); every other -//! target runs the same kernels as portable scalar Rust, with identical results. +//! target runs the same kernels as portable scalar Rust, with identical results. The +//! narrow sets' x86_64 kernels are in the `x86` submodule, each compiled for its features +//! and selected only where the processor reports them; wasm and other targets have no +//! narrow set and run the wide kernels. // On the JPEG AI exact decode path (jpegai_synthesis and int_transformer run // their loops through these kernels): no platform math-library call (see clippy.toml). @@ -158,6 +201,21 @@ use std::cell::Cell; +#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] +mod narrow; +#[cfg(target_arch = "x86_64")] +mod x86; + +/// No narrow kernels on other targets: the wide kernels run. +#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] +mod narrow { + #[allow(clippy::too_many_arguments)] + #[inline(always)] + pub(super) fn conv(_: &super::ConvGeom, _: &[i64], _: &[i64], _: u64, _: u64, _: usize, _: super::Isa, _: &E) -> Option> { + None + } +} + /// Which implementation the integer kernels run. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum KernelPath { @@ -184,17 +242,135 @@ pub struct Policy { /// used as given (bounded by the number of tiles). Ignored on wasm and for the /// reference path (always one thread). pub threads: usize, + /// The instruction set of [`KernelPath::Fast`] ([`Isa`]). Ignored for the + /// reference path. + pub isa: Isa, } impl Policy { /// The scalar reference kernels, single-threaded. pub const fn reference() -> Self { - Self { path: KernelPath::Reference, threads: 1 } + Self { path: KernelPath::Reference, threads: 1, isa: Isa::Auto } } - /// The fast kernels on `threads` workers (`0` = all available cores). + /// The fast kernels on `threads` workers (`0` = all available cores), on the + /// best instruction set this processor runs ([`Isa::Auto`]). pub const fn fast(threads: usize) -> Self { - Self { path: KernelPath::Fast, threads } + Self { path: KernelPath::Fast, threads, isa: Isa::Auto } } + /// The fast kernels on `threads` workers with the kernel set `isa`. A set this + /// processor does not run is never executed: the wide kernels run in its place + /// ([`Isa::supported`]). + pub const fn fast_isa(threads: usize, isa: Isa) -> Self { + Self { path: KernelPath::Fast, threads, isa } + } +} + +/// Which kernels [`KernelPath::Fast`] runs for a convolution, a transposed convolution +/// and an inverse GDN. Every set gives the same integers (the module docs); they differ +/// in the instructions they use, and so in time and energy. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Isa { + /// The best set this processor runs: the one `WAI_INT_ISA` names (by + /// [`Isa::name`]) where it is set and supported, else the first supported of + /// `avx512-vnni`, `avx512`, `avx2-vnni`, `avx2` and `neon`, else `wide`. + Auto, + /// The wide kernels: 32×32→64-bit products and 64-bit sums (NEON widening + /// multiply-accumulate on aarch64, portable Rust elsewhere). + Wide, + /// The narrow kernels on NEON (aarch64): 16-bit operands, 32-bit partial sums. + Neon, + /// The narrow kernels on AVX2 (x86_64): pairs of 16-bit operands multiplied and + /// added into 32-bit lanes (`vpmaddwd`). + Avx2, + /// [`Isa::Avx2`] with the fused multiply-accumulate of AVX-VNNI (`vpdpwssd`). + Avx2Vnni, + /// The narrow kernels on AVX-512 (F and BW). + Avx512, + /// [`Isa::Avx512`] with the fused multiply-accumulate of AVX512-VNNI. + Avx512Vnni, +} + +impl Isa { + /// Every set but [`Isa::Auto`]. + pub const ALL: [Isa; 6] = [Isa::Wide, Isa::Neon, Isa::Avx2, Isa::Avx2Vnni, Isa::Avx512, Isa::Avx512Vnni]; + + /// The name `WAI_INT_ISA` takes. + pub const fn name(self) -> &'static str { + match self { + Isa::Auto => "auto", + Isa::Wide => "wide", + Isa::Neon => "neon", + Isa::Avx2 => "avx2", + Isa::Avx2Vnni => "avx2-vnni", + Isa::Avx512 => "avx512", + Isa::Avx512Vnni => "avx512-vnni", + } + } + + /// Whether this processor runs the set (probed at run time on x86_64; NEON is part + /// of the aarch64 baseline). [`Isa::Auto`] and [`Isa::Wide`] run everywhere. + pub fn supported(self) -> bool { + match self { + Isa::Auto | Isa::Wide => true, + Isa::Neon => cfg!(target_arch = "aarch64"), + #[cfg(target_arch = "x86_64")] + Isa::Avx2 => std::arch::is_x86_feature_detected!("avx2"), + #[cfg(target_arch = "x86_64")] + Isa::Avx2Vnni => std::arch::is_x86_feature_detected!("avx2") && std::arch::is_x86_feature_detected!("avxvnni"), + #[cfg(target_arch = "x86_64")] + Isa::Avx512 => std::arch::is_x86_feature_detected!("avx512f") && std::arch::is_x86_feature_detected!("avx512bw"), + #[cfg(target_arch = "x86_64")] + Isa::Avx512Vnni => { + std::arch::is_x86_feature_detected!("avx512f") + && std::arch::is_x86_feature_detected!("avx512bw") + && std::arch::is_x86_feature_detected!("avx512vnni") + } + #[cfg(not(target_arch = "x86_64"))] + Isa::Avx2 | Isa::Avx2Vnni | Isa::Avx512 | Isa::Avx512Vnni => false, + } + } + + /// The set a kernel runs for `self`: [`Isa::Auto`] resolved, and a set this + /// processor does not run replaced by [`Isa::Wide`]. Never `Auto`. + pub fn effective(self) -> Isa { + match self { + Isa::Auto => auto_isa(), + s if s.supported() => s, + _ => Isa::Wide, + } + } +} + +/// Every kernel set this processor runs, [`Isa::Wide`] first: the sets an equivalence +/// test holds against the reference. +pub fn isas() -> Vec { + Isa::ALL.into_iter().filter(|s| s.supported()).collect() +} + +/// The policies a decoder-level equivalence test runs, the reference first: the fast +/// kernels serial, on two explicit tilings and on the automatic worker count, all on +/// [`Isa::Auto`]'s set, then each other set this processor runs ([`isas`]) on five +/// workers. +pub fn equivalence_policies() -> Vec { + let auto = Isa::Auto.effective(); + let mut v = vec![Policy::reference(), Policy::fast(1), Policy::fast(2), Policy::fast(5), Policy::fast(0)]; + v.extend(isas().into_iter().filter(|&s| s != auto).map(|s| Policy::fast_isa(5, s))); + v +} + +/// [`Isa::Auto`]'s choice, made once per process. +fn auto_isa() -> Isa { + static A: std::sync::OnceLock = std::sync::OnceLock::new(); + *A.get_or_init(|| { + #[cfg(not(any(target_family = "wasm", kani)))] + if let Ok(v) = std::env::var("WAI_INT_ISA") + && let Some(s) = Isa::ALL.into_iter().find(|s| s.name() == v.trim()) + && s.supported() + { + return s; + } + [Isa::Avx512Vnni, Isa::Avx512, Isa::Avx2Vnni, Isa::Avx2, Isa::Neon].into_iter().find(|s| s.supported()).unwrap_or(Isa::Wide) + }) } impl Default for Policy { @@ -352,6 +528,19 @@ pub(crate) fn max_abs(v: &[i64]) -> u64 { v.iter().fold(0u64, |m, &x| m.max(x.unsigned_abs())) } +/// [`max_abs`], over chunks of `v` on up to `threads` workers: the largest of the chunks' +/// largest, which is the same value. +pub(crate) fn max_abs_par(v: &[i64], threads: usize) -> u64 { + const CHUNK: usize = 1 << 16; + let threads = threads.min(v.len() / CHUNK).max(1); + if threads == 1 { + return max_abs(v); + } + let mut m = vec![0u64; v.len().div_ceil(CHUNK)]; + par_for_each(m.iter_mut().zip(v.chunks(CHUNK)).collect(), threads, |(d, c): (&mut u64, &[i64])| *d = max_abs(c)); + m.into_iter().max().unwrap_or(0) +} + // ───────────────────────────────────────────────────────────────────── // Multiply-accumulate kinds // ───────────────────────────────────────────────────────────────────── @@ -631,6 +820,60 @@ mod neon { } p } + + /// [`igdn_norms4`] at gf = 16: each term's flooring shift and its addition are one + /// `ssra` (signed shift right by 16 and accumulate), which forms the same + /// `acc + (term >> 16)` as `sshl` then `add`. + #[inline(always)] + pub(super) fn igdn_norms4_sra16(x2: &[i32], np: usize, gt: &[i32], beta: [i64; 4], norms: &mut [i64]) -> usize { + let mut p = 0; + while p + 8 <= np { + // SAFETY: every raw access is within a bounds-checked slice of exactly + // the accessed length (`xs`: 8×i32, `gv`: 4×i32, `d`: 8×i64). + unsafe { + let (b0, b1, b2, b3) = (vdupq_n_s64(beta[0]), vdupq_n_s64(beta[1]), vdupq_n_s64(beta[2]), vdupq_n_s64(beta[3])); + let (mut a00, mut a01, mut a02, mut a03) = (b0, b0, b0, b0); + let (mut a10, mut a11, mut a12, mut a13) = (b1, b1, b1, b1); + let (mut a20, mut a21, mut a22, mut a23) = (b2, b2, b2, b2); + let (mut a30, mut a31, mut a32, mut a33) = (b3, b3, b3, b3); + for (j, gv) in gt.as_chunks::<4>().0.iter().enumerate() { + let xs = &x2[j * np + p..j * np + p + 8]; + let x0 = vld1q_s32(xs.as_ptr()); + let x1 = vld1q_s32(xs.as_ptr().add(4)); + let g = vld1q_s32(gv.as_ptr()); + let (x0l, x1l) = (vget_low_s32(x0), vget_low_s32(x1)); + a00 = vsraq_n_s64::<16>(a00, vmull_laneq_s32::<0>(x0l, g)); + a01 = vsraq_n_s64::<16>(a01, vmull_high_laneq_s32::<0>(x0, g)); + a02 = vsraq_n_s64::<16>(a02, vmull_laneq_s32::<0>(x1l, g)); + a03 = vsraq_n_s64::<16>(a03, vmull_high_laneq_s32::<0>(x1, g)); + a10 = vsraq_n_s64::<16>(a10, vmull_laneq_s32::<1>(x0l, g)); + a11 = vsraq_n_s64::<16>(a11, vmull_high_laneq_s32::<1>(x0, g)); + a12 = vsraq_n_s64::<16>(a12, vmull_laneq_s32::<1>(x1l, g)); + a13 = vsraq_n_s64::<16>(a13, vmull_high_laneq_s32::<1>(x1, g)); + a20 = vsraq_n_s64::<16>(a20, vmull_laneq_s32::<2>(x0l, g)); + a21 = vsraq_n_s64::<16>(a21, vmull_high_laneq_s32::<2>(x0, g)); + a22 = vsraq_n_s64::<16>(a22, vmull_laneq_s32::<2>(x1l, g)); + a23 = vsraq_n_s64::<16>(a23, vmull_high_laneq_s32::<2>(x1, g)); + a30 = vsraq_n_s64::<16>(a30, vmull_laneq_s32::<3>(x0l, g)); + a31 = vsraq_n_s64::<16>(a31, vmull_high_laneq_s32::<3>(x0, g)); + a32 = vsraq_n_s64::<16>(a32, vmull_laneq_s32::<3>(x1l, g)); + a33 = vsraq_n_s64::<16>(a33, vmull_high_laneq_s32::<3>(x1, g)); + } + for (k, q) in [[a00, a01, a02, a03], [a10, a11, a12, a13], [a20, a21, a22, a23], [a30, a31, a32, a33]] + .into_iter() + .enumerate() + { + let d = &mut norms[k * np + p..k * np + p + 8]; + vst1q_s64(d.as_mut_ptr(), q[0]); + vst1q_s64(d.as_mut_ptr().add(2), q[1]); + vst1q_s64(d.as_mut_ptr().add(4), q[2]); + vst1q_s64(d.as_mut_ptr().add(6), q[3]); + } + } + p += 8; + } + p + } } // ───────────────────────────────────────────────────────────────────── @@ -842,17 +1085,22 @@ impl Layout { /// The zero-padded (and, for a strided regular conv, column-phase-split) input /// in the kernel's operand type. fn pad(&self, g: &ConvGeom, x: &[i64], threads: usize) -> Vec { + self.pad_with(g, x, threads, K::x) + } + + /// [`Layout::pad`], each input value written as `f` of it. + fn pad_with(&self, g: &ConvGeom, x: &[i64], threads: usize, f: impl Fn(i64) -> X + Sync) -> Vec { let chan = self.hp * self.rw; - let mut out = vec![K::X::default(); g.cin * chan]; + let mut out = vec![X::default(); g.cin * chan]; // A pure copy: spread over workers only when it is large enough to matter. let threads = threads.min((g.cin * chan / (1 << 16)).max(1)); - par_for_each(out.chunks_mut(chan).enumerate().collect(), threads, |(ic, dst): (usize, &mut [K::X])| { + par_for_each(out.chunks_mut(chan).enumerate().collect(), threads, |(ic, dst): (usize, &mut [X])| { for iy in 0..g.h { let src = &x[(ic * g.h + iy) * g.w..(ic * g.h + iy) * g.w + g.w]; let row = &mut dst[(iy + self.pt) * self.rw..(iy + self.pt + 1) * self.rw]; if g.transposed || g.sw == 1 { for (d, &s) in row[self.pl..self.pl + g.w].iter_mut().zip(src) { - *d = K::x(s); + *d = f(s); } } else { // padded column c = ix + pl lands in phase c % sw at c / sw @@ -871,7 +1119,7 @@ impl Layout { let c0 = self.pl + ix0; let sub = &mut row[phase * self.pwl..(phase + 1) * self.pwl]; for (d, &s) in sub[c0 / g.sw..].iter_mut().zip(src[ix0..].iter().step_by(g.sw)) { - *d = K::x(s); + *d = f(s); } } } @@ -1123,6 +1371,16 @@ thread_local! { /// Test builds only: how many inverse GDNs the fast kernel ([`igdn`]) has computed on /// this thread. See [`testkit::fast_igdn_runs`]. static FAST_IGDN_RUNS: Cell = const { Cell::new(0) }; + /// Test builds only: how many convolutions the narrow kernels have computed on this + /// thread. See [`testkit::narrow_runs`]. + static NARROW_RUNS: Cell = const { Cell::new(0) }; +} + +/// Record that the narrow convolution kernels run (test builds only; a no-op otherwise). +#[inline(always)] +fn note_narrow() { + #[cfg(test)] + NARROW_RUNS.with(|c| c.set(c.get() + 1)); } /// Record that the fast convolution kernel runs (test builds only; a no-op otherwise). @@ -1152,12 +1410,15 @@ where { let x = &x[..g.cin * g.h * g.w]; let wt = &wt[..g.cin * (g.cout / g.groups) * g.kh * g.kw]; - let (xm, wm) = (max_abs(x), max_abs(wt)); + let threads = threads_for(g.work()); + let (xm, wm) = (max_abs_par(x, threads), max_abs(wt)); if sum_bound(g, xm, wm) > i64::MAX as u128 { return None; } note_fast_conv(); - let threads = threads_for(g.work()); + if let Some(out) = narrow::conv(g, x, wt, xm, wm, threads, policy().isa.effective(), &epi) { + return Some(out); + } Some(if xm <= i32::MAX as u64 && wm <= i32::MAX as u64 { run::(g, x, wt, threads, true, &epi) } else { @@ -1176,15 +1437,18 @@ where { let x = &x[..g.cin * g.h * g.w]; let wt = &wt[..g.cin * (g.cout / g.groups) * g.kh * g.kw]; - let (xm, wm) = (max_abs(x), max_abs(wt)); + let threads = threads_for(g.work()); + let (xm, wm) = (max_abs_par(x, threads), max_abs(wt)); let bound = sum_bound(g, xm, wm); if bound > i128::MAX as u128 { return None; } note_fast_conv(); - let threads = threads_for(g.work()); Some(if bound <= i64::MAX as u128 { let e64 = |oc: usize, r: i64| epi(oc, r as i128); + if let Some(out) = narrow::conv(g, x, wt, xm, wm, threads, policy().isa.effective(), &e64) { + return Some(out); + } if xm <= i32::MAX as u64 && wm <= i32::MAX as u64 { run::(g, x, wt, threads, true, &e64) } else { @@ -1202,6 +1466,18 @@ where /// Positions per IGDN tile. const IGDN_TILE: usize = 64; +/// The x86 kernels an effective kernel set runs, if any. +#[cfg(target_arch = "x86_64")] +fn x86_kind(isa: Isa) -> Option { + match isa { + Isa::Avx2 => Some(x86::Kind::Avx2), + Isa::Avx2Vnni => Some(x86::Kind::Avx2Vnni), + Isa::Avx512 => Some(x86::Kind::Avx512), + Isa::Avx512Vnni => Some(x86::Kind::Avx512Vnni), + _ => None, + } +} + /// Fast inverse GDN, identical to `int_transform::reference::igdn`: /// `y_i = (x_i · isqrt(max(0, beta_i + Σ_j (gamma_ij · x_j²) >> gf))) >> frac`. /// Returns `None` when the fast path does not apply (short buffers, shifts above 62); @@ -1216,6 +1492,12 @@ const IGDN_TILE: usize = 64; /// term is a 32×32→64-bit product (the SIMD-friendly form). #[allow(clippy::too_many_arguments)] pub(crate) fn igdn(x: &[i64], c: usize, hw: usize, frac: u32, gamma: &[i64], beta: &[i64], gf: u32) -> Option> { + igdn_xm(x, c, hw, frac, gamma, beta, gf, None) +} + +/// [`igdn`], with `max|x|` over the first `c·hw` inputs given where the caller has it. +#[allow(clippy::too_many_arguments)] +pub(crate) fn igdn_xm(x: &[i64], c: usize, hw: usize, frac: u32, gamma: &[i64], beta: &[i64], gf: u32, xm: Option) -> Option> { if c == 0 || hw == 0 || gf > 62 || frac > 62 { return None; } @@ -1224,7 +1506,8 @@ pub(crate) fn igdn(x: &[i64], c: usize, hw: usize, frac: u32, gamma: &[i64], bet return None; } let (x, gamma, beta) = (&x[..n], &gamma[..c * c], &beta[..c]); - let xm = max_abs(x) as u128; + let threads = threads_for((c as u64) * (c as u64) * (hw as u64)); + let xm = xm.unwrap_or_else(|| max_abs_par(x, threads)) as u128; let gm = max_abs(gamma) as u128; let bm = max_abs(beta) as u128; let x2m = xm * xm; @@ -1236,7 +1519,9 @@ pub(crate) fn igdn(x: &[i64], c: usize, hw: usize, frac: u32, gamma: &[i64], bet && bm.saturating_add((c as u128).saturating_mul((prod >> gf) + 1)) <= i64::MAX as u128; let narrow_out = xm <= i32::MAX as u128; let narrow_mul = narrow_sum && gm <= i32::MAX as u128 && x2m <= i32::MAX as u128; - let threads = threads_for((c as u64) * (c as u64) * (hw as u64)); + // Resolved here, on the caller's thread, whose policy it is. + let isa = policy().isa.effective(); + let nonneg = gamma.iter().all(|&g| g >= 0); #[cfg(test)] FAST_IGDN_RUNS.with(|k| k.set(k.get() + 1)); @@ -1246,7 +1531,7 @@ pub(crate) fn igdn(x: &[i64], c: usize, hw: usize, frac: u32, gamma: &[i64], bet par_for_each(tiles, threads, |(p0, mut dst)| { let np = dst[0].len(); if narrow_mul { - igdn_tile_narrow(x, c, hw, p0, np, frac, gamma, beta, gf, narrow_out, &mut dst); + igdn_tile_narrow(x, c, hw, p0, np, frac, gamma, beta, gf, narrow_out, isa, nonneg, &mut dst); } else if narrow_sum { igdn_tile_i64(x, c, hw, p0, np, frac, gamma, beta, gf, narrow_out, &mut dst); } else { @@ -1256,15 +1541,36 @@ pub(crate) fn igdn(x: &[i64], c: usize, hw: usize, frac: u32, gamma: &[i64], bet Some(out) } +/// `⌊√n⌋`, the value of `n.isqrt()`, from a seed: the IEEE-754 square root of `n` +/// rounded to `f64` (an operation every conforming platform rounds correctly), capped at +/// the largest root a `u64` has, then corrected in integer arithmetic until `s² ≤ n < +/// (s + 1)²`. The correction alone decides the result, so it is the integer root +/// whatever the seed; the seed decides only how many steps the correction takes (at most +/// one or two from a correctly rounded root). +#[inline(always)] +pub(crate) fn isqrt_u64(n: u64) -> u64 { + let mut s = ((n as f64).sqrt() as u64).min(u32::MAX as u64); + while s * s > n { + s -= 1; + } + while s < u32::MAX as u64 && (s + 1) * (s + 1) <= n { + s += 1; + } + s +} + #[inline(always)] fn igdn_out(xi: i64, s: i64, frac: u32, narrow_out: bool) -> i64 { if narrow_out { (xi * s) >> frac } else { ((xi as i128 * s as i128) >> frac) as i64 } } +/// The inverse GDN of positions `p0 .. p0 + np` where every `γ` and every `x²` fits +/// `i32` and every norm's partial sums fit `i64`: blocks of channels on the SIMD kernels +/// of `isa` (`nonneg`: every `γ ≥ 0`), the rest in scalar arithmetic. #[allow(clippy::too_many_arguments)] fn igdn_tile_narrow( x: &[i64], c: usize, hw: usize, p0: usize, np: usize, frac: u32, gamma: &[i64], beta: &[i64], gf: u32, - narrow_out: bool, dst: &mut [&mut [i64]], + narrow_out: bool, isa: Isa, nonneg: bool, dst: &mut [&mut [i64]], ) { // x_j² at 2^(2·frac), each < 2^31 here. let mut x2 = vec![0i32; c * np]; @@ -1284,20 +1590,56 @@ fn igdn_tile_narrow( } v }; + // The narrow kernel sets seed each root from the IEEE-754 square root ([`isqrt_u64`]); + // the wide set keeps the integer routine. Both give the integer root. + let seeded = isa != Isa::Wide; let emit = |d: &mut [i64], i: usize, norm: &[i64]| { let xi = &x[i * hw + p0..i * hw + p0 + np]; for ((dv, &nv), &xv) in d.iter_mut().zip(norm).zip(xi) { - let s = (nv.max(0) as u64).isqrt() as i64; + let n = nv.max(0) as u64; + let s = if seeded { isqrt_u64(n) } else { n.isqrt() } as i64; *dv = igdn_out(xv, s, frac, narrow_out); } }; - #[cfg_attr(not(target_arch = "aarch64"), allow(unused_mut))] // advanced only by the NEON block + #[cfg_attr(not(any(target_arch = "aarch64", target_arch = "x86_64")), allow(unused_mut))] // advanced only by a SIMD block let mut i = 0; + #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))] + let _ = (isa, nonneg); + // Blocks of eight (AVX-512) or four (AVX2) output channels on x86_64: the norms of + // the block's channels at 16 or 8 positions held in registers across every j. As on + // NEON below, the bound above proves no partial sum can overflow. + #[cfg(target_arch = "x86_64")] + if let Some(kind) = x86_kind(isa) { + let b = x86::igdn_block(kind); + let mut norms = vec![0i64; b * np]; + let mut gt = vec![0i32; c * b]; + while i + b <= c { + for j in 0..c { + for k in 0..b { + gt[j * b + k] = gamma[(i + k) * c + j] as i32; + } + } + // SAFETY: `kind` is a set the processor runs (`Isa::effective`); x2 holds + // c·np values, gt c·b, beta[i..i + b] b, norms b·np; gf ≤ 62 (checked by + // `igdn`); and the narrow bound keeps every partial sum within i64. + let done = unsafe { x86::igdn_norms(kind, &x2, np, >, &beta[i..i + b], gf, nonneg, &mut norms) }; + for k in 0..b { + let nk = &mut norms[k * np..(k + 1) * np]; + for (p, v) in nk.iter_mut().enumerate().skip(done) { + *v = scalar_norm(i + k, p); + } + emit(&mut *dst[i + k], i + k, nk); + } + i += b; + } + } // Blocks of four output channels on NEON (norms of 4 channels × 8 positions held // in registers across every j); the narrow path has already proved no partial - // sum can overflow, so the wrapping lanes compute the exact integers. + // sum can overflow, so the wrapping lanes compute the exact integers. The narrow + // kernel set fuses each term's shift with its addition (`ssra`) where gf is 16. #[cfg(target_arch = "aarch64")] { + let _ = nonneg; let mut norms = vec![0i64; 4 * np]; let mut gt = vec![0i32; c * 4]; while i + 4 <= c { @@ -1306,7 +1648,12 @@ fn igdn_tile_narrow( gt[j * 4 + k] = gamma[(i + k) * c + j] as i32; } } - let done = neon::igdn_norms4(&x2, np, >, [beta[i], beta[i + 1], beta[i + 2], beta[i + 3]], gf, &mut norms); + let b4 = [beta[i], beta[i + 1], beta[i + 2], beta[i + 3]]; + let done = if isa == Isa::Neon && gf == 16 { + neon::igdn_norms4_sra16(&x2, np, >, b4, &mut norms) + } else { + neon::igdn_norms4(&x2, np, >, b4, gf, &mut norms) + }; for k in 0..4 { let nk = &mut norms[k * np..(k + 1) * np]; for (p, v) in nk.iter_mut().enumerate().skip(done) { @@ -1437,8 +1784,15 @@ pub(crate) mod testkit { use super::Policy; /// The fast policies every equivalence test runs: serial, two explicit - /// multi-threaded tilings, and the automatic count. - pub const FAST: [Policy; 4] = [Policy::fast(1), Policy::fast(2), Policy::fast(3), Policy::fast(0)]; + /// multi-threaded tilings, and the automatic count, on the best kernel set this + /// processor runs ([`super::Isa::Auto`]); then each other set it runs, on three + /// workers. + pub static FAST: std::sync::LazyLock> = std::sync::LazyLock::new(|| { + let auto = super::Isa::Auto.effective(); + let mut v = vec![Policy::fast(1), Policy::fast(2), Policy::fast(3), Policy::fast(0)]; + v.extend(super::isas().into_iter().filter(|&s| s != auto).map(|s| Policy::fast_isa(3, s))); + v + }); /// Whether this build traps on integer overflow. Detected by performing one, /// since `cfg!(debug_assertions)` only usually coincides with `overflow-checks`. @@ -1480,6 +1834,14 @@ pub(crate) mod testkit { (r, super::FAST_CONV_RUNS.with(std::cell::Cell::get) - before) } + /// `f`'s result, and how many convolutions the narrow kernels computed on this + /// thread while `f` ran. + pub fn narrow_runs(f: impl FnOnce() -> R) -> (R, u64) { + let before = super::NARROW_RUNS.with(std::cell::Cell::get); + let r = f(); + (r, super::NARROW_RUNS.with(std::cell::Cell::get) - before) + } + /// `f`'s result, and how many inverse GDNs the fast kernel computed on this thread /// while `f` ran. pub fn fast_igdn_runs(f: impl FnOnce() -> R) -> (R, u64) { @@ -1532,7 +1894,7 @@ mod tests { let h = |u: i64, v: i64| u.wrapping_mul(3) - (v >> 7); let want_map: Vec = a.iter().map(|&v| f(v)).collect(); let want_zip: Vec = a.iter().zip(&b).map(|(&u, &v)| h(u, v)).collect(); - for p in FAST.into_iter().chain([Policy::reference()]) { + for p in FAST.iter().copied().chain([Policy::reference()]) { assert_eq!(with_policy(p, || par_map(&a, f)), want_map, "par_map n={n} {p:?}"); assert_eq!(with_policy(p, || par_zip(&a, &b, h)), want_zip, "par_zip n={n} {p:?}"); } @@ -1658,7 +2020,7 @@ mod tests { // (6, 6) = input (1, 1); the rest is padding. let g = ConvGeom::conv(1, 2, 2, 1, 1, 1, 1, 6, 6, 5, 5, 4, 1).unwrap(); assert_eq!((g.ho, g.wo, measure(&g)), (2, 2, (PAD_FACTOR * 9, 9))); - for p in FAST { + for &p in FAST.iter() { let got = fast_conv_runs(|| with_policy(p, || conv_acc_i64(&g, &[1, 2, 3, 4], &[3], |_, a| a + 7))); assert_eq!(got, (Some(vec![7, 7, 7, 3 * 4 + 7]), 1), "at the factor the fast kernel runs ({p:?})"); } @@ -1677,7 +2039,7 @@ mod tests { // accepted. let g = ConvGeom::transposed(2, 1, 1, 1, 1, 1, 1, 1, 6, 6, 13, 13, 2, 2).unwrap(); assert_eq!((g.ho, g.wo, measure(&g)), (2, 2, (PAD_FACTOR * 8, 8))); - for p in FAST { + for &p in FAST.iter() { let got = fast_conv_runs(|| with_policy(p, || conv_acc_i64(&g, &[5, 6], &[3, 4], |_, a| a + 7))); assert_eq!(got, (Some(vec![7; 4]), 1), "at the factor the fast kernel runs ({p:?})"); } @@ -1750,7 +2112,7 @@ mod tests { let lay = Layout::new(&g).unwrap(); // (1 + 2 + 1)² at stride 1, 2² at stride 2, 1 from stride 3 on assert!(lay.hp * lay.rw <= 16, "stride {s}: {}×{} padded", lay.hp, lay.rw); - for p in testkit::FAST { + for &p in testkit::FAST.iter() { // only the centre tap reaches the one output: 5 · 5 assert_eq!(with_policy(p, || conv_acc_i64(&g, &x, &wt, |_, a| a)), Some(vec![25]), "stride {s} {p:?}"); assert_eq!(with_policy(p, || conv_acc_i128(&g, &x, &wt, |_, a| a as i64)), Some(vec![25]), "stride {s} {p:?}"); @@ -1771,7 +2133,7 @@ mod tests { assert_eq!((g.ho, g.wo), (1, 1), "pad {pad} stride {s}"); let lay = Layout::new(&g).unwrap(); assert!(lay.hp == wp && lay.rw < 2 * wp && (s < wp || lay.rw == wp), "pad {pad} stride {s}: {}×{}", lay.hp, lay.rw); - for p in testkit::FAST { + for &p in testkit::FAST.iter() { let got = with_policy(p, || conv_acc_i64(&g, &x, &wt, |_, a| a)); assert_eq!(got, Some(vec![want]), "pad {pad} stride {s} {p:?}"); } @@ -1779,6 +2141,209 @@ mod tests { } } + /// The exact sums of a (transposed) convolution, written out from its definition in + /// `i128`: the regular form as a gather, the transposed one as a scatter into the + /// uncropped plane, then cropped. Independent of every kernel in this module. + fn exact_sums(g: &ConvGeom, x: &[i64], wt: &[i64]) -> Vec { + let (cin, h, w, cout, kh, kw) = (g.cin, g.h, g.w, g.cout, g.kh, g.kw); + if g.transposed { + let (uh, uw) = (g.ho + 2 * g.ph, g.wo + 2 * g.pw); + let mut plane = vec![0i128; cout * uh * uw]; + for ci in 0..cin { + for u in 0..h { + for v in 0..w { + for o in 0..cout { + for i in 0..kh { + for j in 0..kw { + let (y, xx) = (u * g.sh + i, v * g.sw + j); + if y < uh && xx < uw { + plane[(o * uh + y) * uw + xx] += wt[((ci * cout + o) * kh + i) * kw + j] as i128 * x[(ci * h + u) * w + v] as i128; + } + } + } + } + } + } + } + let mut out = Vec::with_capacity(cout * g.ho * g.wo); + for o in 0..cout { + for y in 0..g.ho { + for xx in 0..g.wo { + out.push(plane[(o * uh + y + g.ph) * uw + xx + g.pw]); + } + } + } + out + } else { + let (cg, og) = (cin / g.groups, cout / g.groups); + let mut out = Vec::with_capacity(cout * g.ho * g.wo); + for o in 0..cout { + for y in 0..g.ho { + for xx in 0..g.wo { + let mut acc = 0i128; + for ci in 0..cg { + for i in 0..kh { + for j in 0..kw { + let (iy, ix) = (y * g.sh + i, xx * g.sw + j); + if iy >= g.ph && iy - g.ph < h && ix >= g.pw && ix - g.pw < w { + let c = (o / og) * cg + ci; + acc += wt[((o * cg + ci) * kh + i) * kw + j] as i128 * x[(c * h + iy - g.ph) * w + ix - g.pw] as i128; + } + } + } + } + out.push(acc); + } + } + } + out + } + } + + /// The kernel sets with narrow kernels this processor runs. + fn narrow_isas() -> Vec { + isas().into_iter().filter(|&s| s != Isa::Wide).collect() + } + + /// Every narrow kernel set against the exact sums written out, on random geometry + /// (regular, grouped and transposed; channel counts that fill a register block, part + /// of one and several; odd input channels, whose last pair is half zero; rows + /// covering every vector width and tail) and operands from a few units to the edges + /// the plan admits: one 16-bit limb at |x| ≤ 2^15 − 1, two from 2^15 to 2^30 − 1, and + /// weights to 2^15 − 1. Each case must take the narrow kernels, and each output must + /// be the exact sum. + #[test] + fn narrow_convolutions_give_the_exact_sums() { + use testkit::{narrow_runs, Rng}; + let isas = narrow_isas(); + if isas.is_empty() { + return; // no narrow kernels on this target: the wide kernels' own tests hold + } + let mut r = Rng(0x4E41_0001); + let (mut cases, mut runs) = (0u64, 0u64); + for case in 0..600 { + let transposed = case % 3 != 0; + let (k, s) = (1 + r.below(5), 1 + r.below(3)); + let cin = 1 + r.below(9); + let cout = 1 + r.below(19); + let (h, w) = (1 + r.below(5), 1 + r.below(45)); + let pad = r.below(k); + let g = if transposed { + let op = r.below(s); + ConvGeom::transposed(cin, h, w, cout, k, k, s, s, pad, pad, op, op, cin * h * w, cin * cout * k * k) + } else { + let groups = [1, 1, 2, 3][r.below(4)]; + let (cin, cout) = (cin * groups, cout.div_ceil(groups) * groups); + ConvGeom::conv(cin, h, w + k, cout, groups, k, k, s, s, pad, pad, cin * h * (w + k), cin / groups * cout * k * k) + }; + let Some(g) = g else { continue }; + let xmax = [127i64, 2047, 32767, 32768, 1 << 20, (1 << 30) - 1][r.below(6)]; + let wmax = [255i64, 2047, 32767][r.below(3)]; + let x = r.vec(g.cin * g.h * g.w, xmax, 15); + let wt = r.vec(g.cin * (g.cout / g.groups) * g.kh * g.kw, wmax, 15); + let want = exact_sums(&g, &x, &wt); + if want.iter().any(|&v| i64::try_from(v).is_err()) { + continue; + } + cases += 1; + for &isa in &isas { + for t in [1usize, 3] { + let (got, n) = narrow_runs(|| with_policy(Policy::fast_isa(t, isa), || conv_acc_i128(&g, &x, &wt, |_, a| a as i64))); + let got = got.expect("in range: the fast kernels run"); + assert!(got.iter().zip(&want).all(|(&a, &b)| a as i128 == b), "{isa:?} t={t} {g:?} xmax {xmax} wmax {wmax}"); + runs += n; + } + } + } + assert!(cases > 300, "only {cases} random geometries"); + assert_eq!(runs, cases * isas.len() as u64 * 2, "every case ran on the narrow kernels"); + } + + /// The block sizes at their edge: every weight 2^15 − 1 and every input 2^15 − 1 (or + /// −(2^15 − 1)) in one sign, so each product is within a few units of 2^30 and the + /// running sum of any block one tap longer than the plan's would leave `i32`; and + /// inputs at ±(2^30 − 1), whose high limb is 2^15 − 1 or −2^15. A block flushed late, a + /// widened sum read from the wrong lane or a limb weighted wrongly changes an output. + #[test] + fn narrow_blocks_flush_before_any_partial_sum_leaves_i32() { + use testkit::narrow_runs; + let isas = narrow_isas(); + if isas.is_empty() { + return; + } + let m = 32767i64; + for (cin, w) in [(9usize, 37usize), (64, 33), (17, 16), (3, 1), (200, 50)] { + let g = ConvGeom::conv(cin, 3, w, 11, 1, 3, 3, 1, 1, 1, 1, cin * 3 * w, 11 * cin * 9).unwrap(); + let t = ConvGeom::transposed(cin, 3, w, 11, 5, 5, 2, 2, 2, 2, 1, 1, cin * 3 * w, cin * 11 * 25).unwrap(); + for (xv, wv) in [(m, m), (-m, m), (m, -m), ((1 << 30) - 1, m), (-(1 << 30) + 1, m), (-(1 << 30) + 1, -m), (1 << 15, 1)] { + for geo in [g, t] { + let x = vec![xv; geo.cin * geo.h * geo.w]; + let wt = vec![wv; geo.cin * (geo.cout / geo.groups) * geo.kh * geo.kw]; + let want = exact_sums(&geo, &x, &wt); + let fits = want.iter().all(|&v| i64::try_from(v).is_ok()) && sum_bound(&geo, xv.unsigned_abs(), wv.unsigned_abs()) <= i64::MAX as u128; + if !fits { + continue; + } + for &isa in &isas { + let (got, n) = narrow_runs(|| with_policy(Policy::fast_isa(2, isa), || conv_acc_i128(&geo, &x, &wt, |_, a| a as i64))); + assert_eq!(n, 1, "{isa:?} {geo:?} x {xv} w {wv}: the narrow kernels run"); + let got = got.unwrap(); + assert!(got.iter().zip(&want).all(|(&a, &b)| a as i128 == b), "{isa:?} {geo:?} x {xv} w {wv}"); + } + } + } + } + } + + /// Where the plan does not admit the operands (an input of 2^30 or more, a weight of + /// 2^15 or more), the narrow kernels decline and the wide kernels give the same sums. + #[test] + fn past_the_narrow_widths_the_wide_kernels_run() { + use testkit::narrow_runs; + let g = ConvGeom::transposed(5, 4, 9, 6, 5, 5, 2, 2, 2, 2, 1, 1, 5 * 4 * 9, 5 * 6 * 25).unwrap(); + let mut r = testkit::Rng(0x4E41_0002); + for (xmax, wmax) in [(1i64 << 30, 2047i64), (2047, 32768), (1 << 33, 1 << 16)] { + let mut x = r.vec(5 * 4 * 9, xmax, 10); + let mut wt = r.vec(5 * 6 * 25, wmax, 10); + x[3] = xmax; + wt[7] = -wmax; + let want = exact_sums(&g, &x, &wt); + for isa in isas() { + let (got, n) = narrow_runs(|| with_policy(Policy::fast_isa(1, isa), || conv_acc_i128(&g, &x, &wt, |_, a| a as i64))); + assert_eq!(n, 0, "{isa:?}: the narrow kernels decline x {xmax} w {wmax}"); + assert!(got.unwrap().iter().zip(&want).all(|(&a, &b)| a as i128 == b), "{isa:?} x {xmax} w {wmax}"); + } + } + } + + /// The seeded integer root is `u64::isqrt` on every value class: small values, every + /// perfect square and its neighbours around the powers of two, the edge of `f64`'s + /// exact integers (2^53) and of `u64`, and random values of every magnitude. + #[test] + fn the_seeded_root_is_the_integer_root() { + let mut r = testkit::Rng(0x5157_0001); + let check = |n: u64| assert_eq!(isqrt_u64(n), n.isqrt(), "{n}"); + for n in 0..100_000u64 { + check(n); + } + for e in 0..32u32 { + for d in [-2i64, -1, 0, 1, 2] { + let s = (1u64 << e).wrapping_add_signed(d); + let q = s.wrapping_mul(s); + for dd in [-1i64, 0, 1] { + check(q.wrapping_add_signed(dd)); + } + } + } + for n in [(1u64 << 53) - 1, 1 << 53, (1 << 53) + 1, u64::MAX, u64::MAX - 1, (u32::MAX as u64) * (u32::MAX as u64), (u32::MAX as u64) * (u32::MAX as u64) - 1] { + check(n); + } + for _ in 0..200_000 { + let sh = r.below(64) as u32; + check(r.next() >> sh); + } + } + #[test] fn an_explicit_worker_count_of_any_size_gives_the_same_integers() { // `Policy::threads` is caller-chosen; the fast path's own arithmetic on it diff --git a/wai/wai-rs/src/int_kernels/narrow.rs b/wai/wai-rs/src/int_kernels/narrow.rs new file mode 100644 index 000000000..c1792ba11 --- /dev/null +++ b/wai/wai-rs/src/int_kernels/narrow.rs @@ -0,0 +1,566 @@ +//! The narrow convolution kernels: 16-bit operands, 32-bit partial sums, 64-bit sums +//! (the parent module's documentation, *The narrow kernels*, states the rule). +//! +//! A convolution of the operator set forms each output's exact sum `Σ w·x`. Where every +//! weight has `|w| ≤ 2^15 − 1` and every input `|x| < 2^30`, this module forms the same +//! sum from 16-bit operands: +//! +//! - an input with `|x| ≤ 2^15 − 1` is one operand (`Limb::Whole`); otherwise each input +//! is split into `hi = ⌊x / 2^15⌋` and `lo = x − hi·2^15 ∈ [0, 2^15)`, both 16-bit, and +//! `Σ w·x = 2^15·Σ w·hi + Σ w·lo` (`Limb::Hi`, `Limb::Lo`); +//! - each output's products are summed in a 32-bit lane over a block of at most +//! `kblk` taps, where `kblk` is chosen from the operands' largest magnitudes so that +//! no partial sum of a block can leave `i32`, and each block's sum is then added, +//! widened and shifted by its limb's `2^15` or `2^0`, into a 64-bit sum; +//! - the plan admits a convolution only where every such 64-bit sum, at every step, +//! stays within `i64` too ([`plan`]). +//! +//! No value at any step leaves the type it is held in, so every step is exact and the +//! result is the exact integer `Σ w·x`: the same integer the interpreter forms in +//! `i128`, in whatever order the terms are added. On x86_64 two input channels share one +//! 32-bit word (`vpmaddwd` and `vpdpwssd` multiply 16-bit pairs and add both products +//! into one 32-bit lane); on aarch64 each channel has its own 16-bit lane (`smlal`). Rows +//! whose length is not a multiple of the vector width finish in scalar 64-bit arithmetic. + +use super::{par_for_each, weight_at, ConvGeom, Isa, Layout, Tile}; + +/// The largest operand magnitude a 16-bit lane takes here. With `|w| ≤ 2^15 − 1`, a sum +/// of two products of 16-bit operands stays below `2^31` (only `(−2^15)·(−2^15)` twice +/// reaches it). +const I16: u64 = i16::MAX as u64; + +/// How an input value becomes a 16-bit operand. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Limb { + /// `x` itself, `|x| ≤ 2^15 − 1`. + Whole, + /// `x mod 2^15`, in `[0, 2^15)`. + Lo, + /// `⌊x / 2^15⌋`, in `[−2^15, 2^15)` for `|x| < 2^30`. + Hi, +} + +impl Limb { + #[inline(always)] + fn of(self, v: i64) -> i16 { + match self { + Limb::Whole => v as i16, + Limb::Lo => (v & 0x7fff) as i16, + Limb::Hi => (v >> 15) as i16, + } + } + /// The power of two its sum is scaled by. + fn shift(self) -> u32 { + if self == Limb::Hi { 15 } else { 0 } + } +} + +/// The kernel family an [`Isa`] runs here, or none. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Kernel { + #[cfg(target_arch = "x86_64")] + X86(super::x86::Kind), + #[cfg(target_arch = "aarch64")] + Neon, +} + +impl Kernel { + /// `isa` must already be [`Isa::effective`], so a set named here is one the processor runs. + fn of(isa: Isa) -> Option { + match isa { + #[cfg(target_arch = "x86_64")] + Isa::Avx2 => Some(Kernel::X86(super::x86::Kind::Avx2)), + #[cfg(target_arch = "x86_64")] + Isa::Avx2Vnni => Some(Kernel::X86(super::x86::Kind::Avx2Vnni)), + #[cfg(target_arch = "x86_64")] + Isa::Avx512 => Some(Kernel::X86(super::x86::Kind::Avx512)), + #[cfg(target_arch = "x86_64")] + Isa::Avx512Vnni => Some(Kernel::X86(super::x86::Kind::Avx512Vnni)), + #[cfg(target_arch = "aarch64")] + Isa::Neon => Some(Kernel::Neon), + _ => None, + } + } + /// Whether two channels share a 32-bit word. + fn pairs(self) -> bool { + match self { + #[cfg(target_arch = "x86_64")] + Kernel::X86(_) => true, + #[cfg(target_arch = "aarch64")] + Kernel::Neon => false, + } + } + /// Output channels one tile holds in registers. + fn block(self) -> usize { + match self { + #[cfg(target_arch = "x86_64")] + Kernel::X86(k) => k.block(), + #[cfg(target_arch = "aarch64")] + Kernel::Neon => 8, + } + } +} + +/// The limbs a convolution runs, each with the taps one 32-bit partial sum may take. +#[derive(Clone, Debug, PartialEq, Eq)] +struct Plan { + limbs: Vec<(Limb, usize)>, +} + +/// The plan for `g` with inputs `|x| ≤ xm` and weights `|w| ≤ wm`, or `None` where the +/// narrow kernels do not apply. `pairs`: a tap adds two products (two channels). +/// +/// For each limb with operand bound `l`, one product is at most `p = wm·l` in magnitude, +/// so a block of `k` taps (`k` or `2k` products) has every partial sum within +/// `k·p` (`2k·p`), which `kblk` keeps within `i32::MAX`. Every 64-bit sum is a sum of +/// some of an output's at most `terms` products of each limb, scaled by its limb's power +/// of two, so it stays within `Σ_limbs terms·p·2^shift`, which must be within `i64::MAX`. +fn plan(g: &ConvGeom, xm: u64, wm: u64, pairs: bool) -> Option { + if wm > I16 || xm >= 1 << 30 { + return None; + } + let terms = g.terms_per_output() as u128; + let per_tap: u128 = if pairs { 2 } else { 1 }; + let limbs: &[(Limb, u64)] = if xm <= I16 { &[(Limb::Whole, xm)] } else { &[(Limb::Hi, (xm >> 15) + 1), (Limb::Lo, I16)] }; + let mut total: u128 = 0; + let mut out = Vec::with_capacity(limbs.len()); + for &(l, lmax) in limbs { + let p = wm as u128 * lmax as u128; + let kblk = if p == 0 { usize::MAX } else { usize::try_from(i32::MAX as u128 / (per_tap * p)).unwrap_or(usize::MAX) }; + if kblk == 0 { + return None; + } + total = total.checked_add(terms.checked_mul(p)? << l.shift())?; + out.push((l, kblk)); + } + (total <= i64::MAX as u128).then_some(Plan { limbs: out }) +} + +/// One limb's padded input in the kernel's form. +enum Planes { + /// Two channels per 32-bit word: the first in the low half, both as 16-bit two's + /// complement. Plane `grp·pg + q` holds channels `grp·cin_g + 2q` and `+ 1` (zero + /// where the group has no such channel). + #[cfg(target_arch = "x86_64")] + Pairs(Vec), + /// One channel per 16-bit lane, plane `ic` for channel `ic`. + #[cfg(target_arch = "aarch64")] + Single(Vec), +} + +/// The pair planes of [`Planes::Pairs`] from the channel planes `xp` (`cin` planes of +/// `chan` values each). +#[cfg(target_arch = "x86_64")] +fn interleave(g: &ConvGeom, chan: usize, xp: &[i16], threads: usize) -> Vec { + let cin_g = g.cin / g.groups; + let pg = cin_g.div_ceil(2); + let mut out = vec![0u32; g.groups * pg * chan]; + let threads = threads.min((out.len() / (1 << 16)).max(1)); + par_for_each(out.chunks_mut(chan).enumerate().collect(), threads, |(pl, dst): (usize, &mut [u32])| { + let (grp, q) = (pl / pg, pl % pg); + let a = grp * cin_g + 2 * q; + let lo = &xp[a * chan..(a + 1) * chan]; + if 2 * q + 1 < cin_g { + let hi = &xp[(a + 1) * chan..(a + 2) * chan]; + for ((d, &l), &h) in dst.iter_mut().zip(lo).zip(hi) { + *d = (l as u16 as u32) | ((h as u16 as u32) << 16); + } + } else { + for (d, &l) in dst.iter_mut().zip(lo) { + *d = l as u16 as u32; + } + } + }); + out +} + +/// One tap table: each tap's weights for the tile's channels (`STRIDE` lanes per tap, +/// [`TapW`]), and its input offset. +struct Taps { + w: Vec, + offs: Vec, + /// The largest offset (0 for an empty table). + max_off: usize, +} + +impl Taps { + fn new() -> Self { + Taps { w: Vec::new(), offs: Vec::new(), max_off: 0 } + } + fn push(&mut self, w: &[TapW], off: usize) { + self.w.extend_from_slice(w); + self.offs.push(off); + self.max_off = self.max_off.max(off); + } + fn is_empty(&self) -> bool { + self.offs.is_empty() + } +} + +/// The narrow convolution of `g`, or `None` where it does not apply (an [`Isa`] with no +/// narrow kernels, or operands past [`plan`]'s bounds); the caller then runs the wide +/// kernels. `epi(oc, sum)` finishes each output from its exact sum. +#[allow(clippy::too_many_arguments)] +pub(super) fn conv(g: &ConvGeom, x: &[i64], wt: &[i64], xm: u64, wm: u64, threads: usize, isa: Isa, epi: &E) -> Option> +where + E: Fn(usize, i64) -> i64 + Sync, +{ + let k = Kernel::of(isa)?; + let plan = plan(g, xm, wm, k.pairs())?; + super::note_narrow(); + let lay = Layout::new(g).expect("ConvGeom::sized checked the layout"); + let chan = lay.hp * lay.rw; + let planes: Vec<(Planes, usize, u32)> = plan + .limbs + .iter() + .map(|&(l, kblk)| { + let xp = lay.pad_with(g, x, threads, |v| l.of(v)); + #[cfg(target_arch = "x86_64")] + let p = Planes::Pairs(interleave(g, chan, &xp, threads)); + #[cfg(target_arch = "aarch64")] + let p = { + let _ = chan; + Planes::Single(xp) + }; + (p, kblk, l.shift()) + }) + .collect(); + + let plane = g.ho * g.wo; + let mut out = vec![0i64; g.cout * plane]; + let block = k.block(); + let cout_g = g.cout / g.groups; + let mut blocks = Vec::new(); + for grp in 0..g.groups { + let mut oc = grp * cout_g; + while oc < (grp + 1) * cout_g { + let nb = block.min((grp + 1) * cout_g - oc); + blocks.push((oc, nb)); + oc += nb; + } + } + let nbands = if threads > 1 { threads.saturating_mul(4).div_ceil(blocks.len()).clamp(1, g.ho) } else { 1 }; + let rows = g.ho.div_ceil(nbands); + let mut bands: Vec> = out.chunks_mut(plane).map(|p| p.chunks_mut(rows * g.wo)).collect(); + let mut tiles = Vec::new(); + for &(oc0, nb) in &blocks { + for b in 0..g.ho.div_ceil(rows) { + let slices: Vec<&mut [i64]> = (oc0..oc0 + nb).map(|oc| bands[oc].next().unwrap()).collect(); + tiles.push(Tile { oc0, y0: b * rows, slices }); + } + } + let ctx = Ctx { k, g, lay: &lay, planes: &planes, wt }; + par_for_each(tiles, threads, |t| match t.slices.len() { + 1 => ctx.tile::<1, E>(epi, t), + 2 => ctx.tile::<2, E>(epi, t), + 3 => ctx.tile::<3, E>(epi, t), + 4 => ctx.tile::<4, E>(epi, t), + 5 => ctx.tile::<5, E>(epi, t), + 6 => ctx.tile::<6, E>(epi, t), + 7 => ctx.tile::<7, E>(epi, t), + _ => ctx.tile::<8, E>(epi, t), + }); + Some(out) +} + +/// What every tile of one convolution reads. +struct Ctx<'a> { + k: Kernel, + g: &'a ConvGeom, + lay: &'a Layout, + planes: &'a [(Planes, usize, u32)], + wt: &'a [i64], +} + +/// One weight lane of a tap: a pair of 16-bit weights (x86_64), or one (aarch64). +#[cfg(target_arch = "x86_64")] +type TapW = i32; +#[cfg(target_arch = "aarch64")] +type TapW = i16; + +impl Ctx<'_> { + /// The weights of channels `oc0 .. oc0 + NB` for input channel `ic` (or, with pairs, + /// channels `ic` and `ic + 1` of the group, `ic + 1` only where `second`) at kernel + /// tap `(ky, kx)`; `None` where every one is zero, a tap the sums may omit. + /// + /// x86_64 lays a tap out as `NB` words, each a pair of 16-bit weights (channel `ic` + /// in the low half); aarch64 as 8 16-bit weights, the lanes past `NB` zero, so that + /// one vector load reads them. + #[inline(always)] + fn push_tap(&self, taps: &mut Taps, off: usize, oc0: usize, icl: usize, second: bool, ky: usize, kx: usize) { + let (g, wt) = (self.g, self.wt); + #[cfg(target_arch = "x86_64")] + let w: [i32; NB] = std::array::from_fn(|j| { + let a = weight_at(g, wt, oc0 + j, icl, ky, kx) as i16 as u16 as u32; + let b = if second { weight_at(g, wt, oc0 + j, icl + 1, ky, kx) as i16 as u16 as u32 } else { 0 }; + (a | (b << 16)) as i32 + }); + #[cfg(target_arch = "aarch64")] + let w: [i16; 8] = { + let _ = second; + std::array::from_fn(|j| if j < NB { weight_at(g, wt, oc0 + j, icl, ky, kx) as i16 } else { 0 }) + }; + if w.iter().any(|&v| v != 0) { + taps.push(&w, off); + } + } + + /// Input channels per kernel lane step: two with pairs, else one. + fn step(&self) -> usize { + if self.k.pairs() { 2 } else { 1 } + } + + fn tile i64 + Sync>(&self, epi: &E, mut t: Tile<'_>) { + let (g, lay) = (self.g, self.lay); + let rows = t.slices[0].len() / g.wo; + let chan = lay.hp * lay.rw; + let step = self.step(); + + if !g.transposed { + let cin_g = g.cin / g.groups; + let grp = t.oc0 / (g.cout / g.groups); + // the group's first plane: pair planes with pairs, channel planes without + let p0 = if step == 2 { grp * cin_g.div_ceil(2) } else { grp * cin_g }; + let mut taps = Taps::new(); + for (q, icl) in (0..cin_g).step_by(step).enumerate() { + for ky in 0..g.kh { + for kx in 0..g.kw { + let off = (p0 + q) * chan + ky * lay.rw + (kx % g.sw) * lay.pwl + kx / g.sw; + self.push_tap::(&mut taps, off, t.oc0, icl, icl + 1 < cin_g, ky, kx); + } + } + } + let mut acc = vec![0i64; NB * g.wo]; + for r in 0..rows { + let oy = t.y0 + r; + self.sums::(&taps, oy * g.sh * lay.rw, g.wo, &mut acc); + for (j, s) in t.slices.iter_mut().enumerate() { + let oc = t.oc0 + j; + for (d, &a) in s[r * g.wo..(r + 1) * g.wo].iter_mut().zip(&acc[j * g.wo..(j + 1) * g.wo]) { + *d = epi(oc, a); + } + } + } + return; + } + + // Transposed: the phase tables of the wide kernels' `tile`, over pairs of input + // channels where the kernel takes pairs. + let (sh, sw) = (g.sh, g.sw); + let (nsig, nrho) = (rows.min(sh), g.wo.min(sw)); + let mut tables: Vec<(Taps, usize)> = Vec::with_capacity(nsig * nrho); + for i in 0..nsig { + let sigma = (t.y0 + i + g.ph) % sh; + let nqy = g.kh.saturating_sub(sigma).div_ceil(sh); + for b in 0..nrho { + let rho = (b + g.pw) % sw; + let nqx = g.kw.saturating_sub(rho).div_ceil(sw); + let n = (g.wo - b).div_ceil(sw); + let u0 = (b + g.pw) / sw; + let mut taps = Taps::new(); + if nqy > 0 && nqx > 0 { + for (q, ic) in (0..g.cin).step_by(step).enumerate() { + for qy in (0..nqy).rev() { + let ky = sigma + qy * sh; + for qx in (0..nqx).rev() { + let kx = rho + qx * sw; + let off = q * chan + (lay.pt - qy) * lay.rw + (lay.pl - qx) + u0; + self.push_tap::(&mut taps, off, t.oc0, ic, ic + 1 < g.cin, ky, kx); + } + } + } + } + tables.push((taps, n)); + } + } + let mut acc = vec![0i64; NB * g.wo.div_ceil(sw)]; + for r in 0..rows { + let v = (t.y0 + r + g.ph) / sh; + let i = r % nsig; + for (b, (taps, n)) in tables[i * nrho..(i + 1) * nrho].iter().enumerate() { + let n = *n; + self.sums::(taps, v * lay.rw, n, &mut acc[..NB * n]); + for (j, s) in t.slices.iter_mut().enumerate() { + let oc = t.oc0 + j; + let row = &mut s[r * g.wo..(r + 1) * g.wo]; + for (e, &a) in acc[j * n..(j + 1) * n].iter().enumerate() { + row[b + e * sw] = epi(oc, a); + } + } + } + } + } + + /// `acc[j·n + e] = Σ_t w_t[j] · x[offs_t + base + e]` over every limb, for the `n` + /// elements of one row and the tile's `NB` channels. + fn sums(&self, taps: &Taps, base: usize, n: usize, acc: &mut [i64]) { + let acc = &mut acc[..NB * n]; + acc.fill(0); + if n == 0 || taps.is_empty() { + return; + } + for (p, kblk, sh) in self.planes { + match (self.k, p) { + #[cfg(target_arch = "x86_64")] + (Kernel::X86(kind), Planes::Pairs(x)) => { + // Every element a kernel reads, `x[offs_t + base + e]` for `e < n`, lies + // within `x`: the raw loads below rely on this check. + assert!(taps.max_off + base + n <= x.len(), "a narrow row reads within its padded input"); + // SAFETY: the processor runs `kind` (`Kernel::of` takes only an + // `Isa::effective` set), and the assertion bounds every read. + unsafe { super::x86::sums::(kind, &taps.w, &taps.offs, x, base, n, *kblk, *sh, acc) } + } + #[cfg(target_arch = "aarch64")] + (Kernel::Neon, Planes::Single(x)) => { + assert!(taps.max_off + base + n <= x.len(), "a narrow row reads within its padded input"); + // SAFETY: NEON is part of the aarch64 baseline, and the assertion bounds + // every read. + unsafe { neon::sums::(&taps.w, &taps.offs, x, base, n, *kblk, *sh, acc) } + } + } + } + } +} + +/// Elements `e0 .. n` of [`Ctx::sums`] for one limb in scalar 64-bit arithmetic, with +/// pairs: `acc[j·n + e] += (Σ_t lo(w)·lo(x) + hi(w)·hi(x)) · 2^sh`. +#[cfg(target_arch = "x86_64")] +#[allow(clippy::too_many_arguments)] +pub(super) fn tail_pairs(ws: &[i32], offs: &[usize], x: &[u32], base: usize, e0: usize, n: usize, sh: u32, acc: &mut [i64]) { + for e in e0..n { + let mut s = [0i64; NB]; + for (w, &o) in ws.chunks_exact(NB).zip(offs) { + let xv = x[o + base + e]; + let (xl, xh) = (xv as u16 as i16 as i64, (xv >> 16) as u16 as i16 as i64); + for j in 0..NB { + let wv = w[j] as u32; + s[j] += (wv as u16 as i16 as i64) * xl + ((wv >> 16) as u16 as i16 as i64) * xh; + } + } + for j in 0..NB { + acc[j * n + e] += s[j] << sh; + } + } +} + +/// [`tail_pairs`] with one channel per lane. +#[cfg(target_arch = "aarch64")] +#[allow(clippy::too_many_arguments)] +fn tail_single(ws: &[i16], offs: &[usize], x: &[i16], base: usize, e0: usize, n: usize, sh: u32, acc: &mut [i64]) { + for e in e0..n { + let mut s = [0i64; NB]; + for (w, &o) in ws.chunks_exact(8).zip(offs) { + let xv = x[o + base + e] as i64; + for j in 0..NB { + s[j] += w[j] as i64 * xv; + } + } + for j in 0..NB { + acc[j * n + e] += s[j] << sh; + } + } +} + +/// The NEON narrow kernel: `NB ≤ 8` channels × 8 adjacent elements in 32-bit lanes +/// (`smlal` / `smlal2` by weight lane), each block of at most `kblk` taps summed from +/// zero and then widened, shifted and added into the 64-bit sums. +#[cfg(target_arch = "aarch64")] +mod neon { + use std::arch::aarch64::*; + + macro_rules! lanes { + ($nb:expr, $a:ident, $xl:ident, $xv:ident, $wv:ident; $($j:literal)*) => { + $( + if $nb > $j { + $a[$j][0] = vmlal_laneq_s16::<$j>($a[$j][0], $xl, $wv); + $a[$j][1] = vmlal_high_laneq_s16::<$j>($a[$j][1], $xv, $wv); + } + )* + }; + } + + /// See [`super::Ctx::sums`]. `kblk` taps' products fit a 32-bit lane ([`super::plan`]). + /// + /// # Safety + /// `offs_t + base + n ≤ x.len()` for every tap `t`, and `acc.len() ≥ NB·n`. + #[allow(clippy::too_many_arguments)] + pub(super) unsafe fn sums(ws: &[i16], offs: &[usize], x: &[i16], base: usize, n: usize, kblk: usize, sh: u32, acc: &mut [i64]) { + let taps = offs.len(); + let xp = x.as_ptr(); + let ap = acc.as_mut_ptr(); + let shv = vdupq_n_s64(sh as i64); + let mut e = 0; + while e + 8 <= n { + let mut t0 = 0; + while t0 < taps { + let t1 = t0 + kblk.min(taps - t0); + // SAFETY (all raw accesses below): each load reads `x[offs_t + base + e .. + // + 8]` with `e + 8 ≤ n`, in bounds by the caller's contract; each weight + // load reads `ws[8t .. 8t + 8]`; each store writes `acc[j·n + e .. + 8]`. + unsafe { + let z = vdupq_n_s32(0); + let mut a = [[z; 2]; NB]; + for t in t0..t1 { + let xv = vld1q_s16(xp.add(*offs.get_unchecked(t) + base + e)); + let xl = vget_low_s16(xv); + let wv = vld1q_s16(ws.as_ptr().add(8 * t)); + lanes!(NB, a, xl, xv, wv; 0 1 2 3 4 5 6 7); + } + for (j, aj) in a.iter().enumerate() { + let d = ap.add(j * n + e); + for (h, &v) in aj.iter().enumerate() { + let lo = vshlq_s64(vmovl_s32(vget_low_s32(v)), shv); + let hi = vshlq_s64(vmovl_high_s32(v), shv); + let p = d.add(4 * h); + vst1q_s64(p, vaddq_s64(vld1q_s64(p), lo)); + vst1q_s64(p.add(2), vaddq_s64(vld1q_s64(p.add(2)), hi)); + } + } + } + t0 = t1; + } + e += 8; + } + super::tail_single::(ws, offs, x, base, e, n, sh, acc); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_limb_split_rebuilds_every_input() { + for v in [-(1i64 << 30) + 1, -(1 << 15) - 1, -(1 << 15), -32767, -1, 0, 1, 32767, 32768, (1 << 30) - 1, 316_606, -316_606] { + let (hi, lo) = (Limb::Hi.of(v) as i64, Limb::Lo.of(v) as i64); + assert!((0..1 << 15).contains(&lo), "{v}"); + assert_eq!(hi * (1 << 15) + lo, v, "{v}"); + } + for v in [-32767i64, -1, 0, 1, 32767] { + assert_eq!(Limb::Whole.of(v) as i64, v); + } + } + + #[test] + fn the_plan_keeps_every_partial_sum_in_its_type() { + let g = ConvGeom::transposed(192, 128, 192, 192, 5, 5, 2, 2, 2, 2, 1, 1, 192 * 128 * 192, 192 * 192 * 25).unwrap(); + for pairs in [false, true] { + let per = if pairs { 2u128 } else { 1 }; + for (xm, wm) in [(7622u64, 2047u64), (32767, 32767), (32768, 2047), (316_606, 2047), ((1 << 30) - 1, 32767), (0, 2047), (5, 0)] { + let p = plan(&g, xm, wm, pairs).unwrap_or_else(|| panic!("{xm} {wm}")); + let mut total = 0u128; + for &(l, k) in &p.limbs { + let lmax = match l { Limb::Whole => xm, Limb::Hi => (xm >> 15) + 1, Limb::Lo => 32767 } as u128; + let prod = wm as u128 * lmax; + assert!(k >= 1 && (prod == 0 || per * k as u128 * prod <= i32::MAX as u128), "{xm} {wm} {l:?} {k}"); + total += g.terms_per_output() as u128 * prod << l.shift(); + } + assert!(total <= i64::MAX as u128); + assert_eq!(p.limbs.len(), if xm <= 32767 { 1 } else { 2 }); + } + // past either width, the wide kernels run + assert_eq!(plan(&g, 1 << 30, 1, pairs), None); + assert_eq!(plan(&g, 1, 32768, pairs), None); + } + } +} diff --git a/wai/wai-rs/src/int_kernels/x86.rs b/wai/wai-rs/src/int_kernels/x86.rs new file mode 100644 index 000000000..53dae9c5c --- /dev/null +++ b/wai/wai-rs/src/int_kernels/x86.rs @@ -0,0 +1,396 @@ +//! The x86_64 microkernels of the narrow convolutions and the inverse GDN, on AVX2 and +//! AVX-512 (`std::arch`), each selected only where the processor reports its features +//! ([`super::Isa::supported`]). +//! +//! Convolution: two input channels share a 32-bit word, and a tap's weights for one +//! output channel are the matching pair. `vpmaddwd` (AVX2, AVX-512BW) multiplies the +//! two 16-bit pairs of each 32-bit lane and adds both products into it; `vpdpwssd` +//! (AVX-VNNI, AVX512-VNNI) does the same and adds the lane's accumulator, wrapping. A +//! lane accumulates one output element's products over a block of at most `kblk` taps, +//! which [`super::narrow`] chose so that no partial sum leaves `i32`; so neither +//! instruction wraps, and each lane holds the exact sum of its block's products. Each +//! block's sums are then sign-extended to 64 bits, shifted by the limb's power of two +//! and added into the output's 64-bit sum. +//! +//! Inverse GDN: `vpmuldq` multiplies the low signed 32 bits of each 64-bit lane (`γ` and +//! `x²`, each within `i32` on this path) into an exact 64-bit product; it is then +//! shifted right arithmetically by `gf` (`vpsraq` on AVX-512; on AVX2, which has no +//! 64-bit arithmetic shift, a logical shift where every product is non-negative, else +//! the logical shift with the sign bits put back) and added into the norm, which the +//! caller has proved cannot leave `i64`. + +use std::arch::x86_64::*; + +/// Which x86 kernels run. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(super) enum Kind { + Avx2, + Avx2Vnni, + Avx512, + Avx512Vnni, +} + +impl Kind { + /// Output channels a convolution tile holds in registers. + pub(super) fn block(self) -> usize { + match self { + Kind::Avx2 | Kind::Avx2Vnni => 4, + Kind::Avx512 | Kind::Avx512Vnni => 8, + } + } +} + +/// `acc[j·n + e] += (Σ_t pairdot(ws[t·NB + j], x[offs_t + base + e])) · 2^sh` for +/// `e < n`, `j < NB`, in blocks of at most `kblk` taps per 32-bit partial sum. +/// +/// # Safety +/// The processor runs `kind`; `offs_t + base + n ≤ x.len()` for every tap `t`; +/// `ws.len() = NB · offs.len()`; `acc.len() ≥ NB · n`; and no block's partial sum of +/// any element leaves `i32` (`kblk` from [`super::narrow`]'s plan). +#[allow(clippy::too_many_arguments)] +pub(super) unsafe fn sums(kind: Kind, ws: &[i32], offs: &[usize], x: &[u32], base: usize, n: usize, kblk: usize, sh: u32, acc: &mut [i64]) { + debug_assert_eq!(ws.len(), NB * offs.len()); + debug_assert!(acc.len() >= NB * n); + // SAFETY: forwarded from the caller. + unsafe { + match kind { + Kind::Avx2 => avx2_madd::(ws, offs, x, base, n, kblk, sh, acc), + Kind::Avx2Vnni => avx2_vnni::(ws, offs, x, base, n, kblk, sh, acc), + Kind::Avx512 => avx512_madd::(ws, offs, x, base, n, kblk, sh, acc), + Kind::Avx512Vnni => avx512_vnni::(ws, offs, x, base, n, kblk, sh, acc), + } + } +} + +/// The multiply-add of one tap into a 32-bit accumulator: `acc + lo(x)·lo(w) + hi(x)·hi(w)`. +trait Dot256 { + unsafe fn dot(acc: __m256i, x: __m256i, w: __m256i) -> __m256i; +} +struct Madd256; +impl Dot256 for Madd256 { + #[inline(always)] + unsafe fn dot(acc: __m256i, x: __m256i, w: __m256i) -> __m256i { + // SAFETY: called only inside functions compiled with AVX2. + unsafe { _mm256_add_epi32(acc, _mm256_madd_epi16(x, w)) } + } +} +struct Vnni256; +impl Dot256 for Vnni256 { + #[inline(always)] + unsafe fn dot(acc: __m256i, x: __m256i, w: __m256i) -> __m256i { + // SAFETY: called only inside functions compiled with AVX-VNNI. + unsafe { _mm256_dpwssd_avx_epi32(acc, x, w) } + } +} + +trait Dot512 { + unsafe fn dot(acc: __m512i, x: __m512i, w: __m512i) -> __m512i; +} +struct Madd512; +impl Dot512 for Madd512 { + #[inline(always)] + unsafe fn dot(acc: __m512i, x: __m512i, w: __m512i) -> __m512i { + // SAFETY: called only inside functions compiled with AVX-512BW. + unsafe { _mm512_add_epi32(acc, _mm512_madd_epi16(x, w)) } + } +} +struct Vnni512; +impl Dot512 for Vnni512 { + #[inline(always)] + unsafe fn dot(acc: __m512i, x: __m512i, w: __m512i) -> __m512i { + // SAFETY: called only inside functions compiled with AVX512-VNNI. + unsafe { _mm512_dpwssd_epi32(acc, x, w) } + } +} + +#[target_feature(enable = "avx2")] +#[allow(clippy::too_many_arguments)] +unsafe fn avx2_madd(ws: &[i32], offs: &[usize], x: &[u32], base: usize, n: usize, kblk: usize, sh: u32, acc: &mut [i64]) { + // SAFETY: forwarded. + unsafe { body256::(ws, offs, x, base, n, kblk, sh, acc) } +} + +#[target_feature(enable = "avx2,avxvnni")] +#[allow(clippy::too_many_arguments)] +unsafe fn avx2_vnni(ws: &[i32], offs: &[usize], x: &[u32], base: usize, n: usize, kblk: usize, sh: u32, acc: &mut [i64]) { + // SAFETY: forwarded. + unsafe { body256::(ws, offs, x, base, n, kblk, sh, acc) } +} + +#[target_feature(enable = "avx512f,avx512bw")] +#[allow(clippy::too_many_arguments)] +unsafe fn avx512_madd(ws: &[i32], offs: &[usize], x: &[u32], base: usize, n: usize, kblk: usize, sh: u32, acc: &mut [i64]) { + // SAFETY: forwarded. + unsafe { body512::(ws, offs, x, base, n, kblk, sh, acc) } +} + +#[target_feature(enable = "avx512f,avx512bw,avx512vnni")] +#[allow(clippy::too_many_arguments)] +unsafe fn avx512_vnni(ws: &[i32], offs: &[usize], x: &[u32], base: usize, n: usize, kblk: usize, sh: u32, acc: &mut [i64]) { + // SAFETY: forwarded. + unsafe { body512::(ws, offs, x, base, n, kblk, sh, acc) } +} + +/// Add the eight 32-bit sums of `a`, sign-extended and shifted left by `cnt`, into +/// `d[0..8]`. +#[inline(always)] +unsafe fn flush256(a: __m256i, cnt: __m128i, d: *mut i64) { + // SAFETY: `d[0..8]` is the caller's; AVX2 is enabled in every caller. + unsafe { + let lo = _mm256_sll_epi64(_mm256_cvtepi32_epi64(_mm256_castsi256_si128(a)), cnt); + let hi = _mm256_sll_epi64(_mm256_cvtepi32_epi64(_mm256_extracti128_si256::<1>(a)), cnt); + _mm256_storeu_si256(d as *mut __m256i, _mm256_add_epi64(_mm256_loadu_si256(d as *const __m256i), lo)); + _mm256_storeu_si256(d.add(4) as *mut __m256i, _mm256_add_epi64(_mm256_loadu_si256(d.add(4) as *const __m256i), hi)); + } +} + +/// AVX2: `NB ≤ 4` channels × 16 elements (two vectors of eight 32-bit lanes), then 8, +/// then the scalar tail. +#[inline(always)] +#[allow(clippy::too_many_arguments)] +unsafe fn body256(ws: &[i32], offs: &[usize], x: &[u32], base: usize, n: usize, kblk: usize, sh: u32, acc: &mut [i64]) { + let taps = offs.len(); + let xp = x.as_ptr(); + let ap = acc.as_mut_ptr(); + // SAFETY (every raw access): reads `x[offs_t + base + e .. + 8·V]` with + // `e + 8·V ≤ n`, in bounds by the contract of [`sums`]; reads `ws[t·NB + j]`, and + // writes `acc[j·n + e .. + 8·V]`, both within their slices. + unsafe { + let cnt = _mm_cvtsi32_si128(sh as i32); + let mut e = 0; + macro_rules! step { + ($v:literal) => { + while e + 8 * $v <= n { + let mut t0 = 0; + while t0 < taps { + let t1 = t0 + kblk.min(taps - t0); + let mut a = [[_mm256_setzero_si256(); $v]; NB]; + for t in t0..t1 { + let p = xp.add(*offs.get_unchecked(t) + base + e); + let mut xv = [_mm256_setzero_si256(); $v]; + for (k, v) in xv.iter_mut().enumerate() { + *v = _mm256_loadu_si256(p.add(8 * k) as *const __m256i); + } + let w = ws.as_ptr().add(t * NB); + for (j, aj) in a.iter_mut().enumerate() { + let wj = _mm256_set1_epi32(*w.add(j)); + for (av, &xk) in aj.iter_mut().zip(&xv) { + *av = D::dot(*av, xk, wj); + } + } + } + for (j, aj) in a.iter().enumerate() { + for (k, &av) in aj.iter().enumerate() { + flush256(av, cnt, ap.add(j * n + e + 8 * k)); + } + } + t0 = t1; + } + e += 8 * $v; + } + }; + } + step!(2); + step!(1); + super::narrow::tail_pairs::(ws, offs, x, base, e, n, sh, acc); + } +} + +/// Add the sixteen 32-bit sums of `a` selected by `m` (bit `i` for element `i`), +/// sign-extended and shifted left by `cnt`, into `d[0..16]`. +#[inline(always)] +unsafe fn flush512(a: __m512i, cnt: __m128i, m: u16, d: *mut i64) { + // SAFETY: `d[i]` for every selected `i` is the caller's; masked loads and stores + // touch no other element. AVX-512F is enabled in every caller. + unsafe { + let lo = _mm512_sll_epi64(_mm512_cvtepi32_epi64(_mm512_castsi512_si256(a)), cnt); + let hi = _mm512_sll_epi64(_mm512_cvtepi32_epi64(_mm512_extracti64x4_epi64::<1>(a)), cnt); + let (ml, mh) = (m as u8, (m >> 8) as u8); + _mm512_mask_storeu_epi64(d, ml, _mm512_add_epi64(_mm512_maskz_loadu_epi64(ml, d), lo)); + _mm512_mask_storeu_epi64(d.add(8), mh, _mm512_add_epi64(_mm512_maskz_loadu_epi64(mh, d.add(8)), hi)); + } +} + +/// AVX-512: `NB ≤ 8` channels × 32 elements (two vectors of sixteen 32-bit lanes), then +/// 16, then the last `n mod 16` under a lane mask. +#[inline(always)] +#[allow(clippy::too_many_arguments)] +unsafe fn body512(ws: &[i32], offs: &[usize], x: &[u32], base: usize, n: usize, kblk: usize, sh: u32, acc: &mut [i64]) { + let taps = offs.len(); + let xp = x.as_ptr(); + let ap = acc.as_mut_ptr(); + // SAFETY (every raw access): reads `x[offs_t + base + e + i]` only for `e + i < n` + // (whole vectors where `e + 16·V ≤ n`, masked lanes after), in bounds by the contract + // of [`sums`]; reads `ws[t·NB + j]`; writes `acc[j·n + e + i]` only for `e + i < n`. + unsafe { + let cnt = _mm_cvtsi32_si128(sh as i32); + let mut e = 0; + macro_rules! step { + ($v:literal, $mask:expr) => {{ + let m: u16 = $mask; + let mut t0 = 0; + while t0 < taps { + let t1 = t0 + kblk.min(taps - t0); + let mut a = [[_mm512_setzero_si512(); $v]; NB]; + for t in t0..t1 { + let p = xp.add(*offs.get_unchecked(t) + base + e); + let mut xv = [_mm512_setzero_si512(); $v]; + for (k, v) in xv.iter_mut().enumerate() { + *v = if m == u16::MAX { _mm512_loadu_si512(p.add(16 * k) as *const __m512i) } else { _mm512_maskz_loadu_epi32(m, p.add(16 * k) as *const i32) }; + } + let w = ws.as_ptr().add(t * NB); + for (j, aj) in a.iter_mut().enumerate() { + let wj = _mm512_set1_epi32(*w.add(j)); + for (av, &xk) in aj.iter_mut().zip(&xv) { + *av = D::dot(*av, xk, wj); + } + } + } + for (j, aj) in a.iter().enumerate() { + for (k, &av) in aj.iter().enumerate() { + flush512(av, cnt, m, ap.add(j * n + e + 16 * k)); + } + } + t0 = t1; + } + }}; + } + while e + 32 <= n { + step!(2, u16::MAX); + e += 32; + } + if e + 16 <= n { + step!(1, u16::MAX); + e += 16; + } + if e < n { + step!(1, ((1u32 << (n - e)) - 1) as u16); + } + } +} + +// ───────────────────────────────────────────────────────────────────── +// Inverse GDN norms +// ───────────────────────────────────────────────────────────────────── + +/// Channels per block of [`igdn_norms`] for `kind`. +pub(super) fn igdn_block(kind: Kind) -> usize { + match kind { + Kind::Avx2 | Kind::Avx2Vnni => 4, + Kind::Avx512 | Kind::Avx512Vnni => 8, + } +} + +/// IGDN norms of `B = igdn_block(kind)` output channels: `norms[k·np + p] = beta[k] + +/// Σ_j (gt[j·B + k] · x2[j·np + p]) >> gf` for positions `p` in a leading run (whose +/// length is returned; the caller computes the rest), terms in `j` order from `beta`. +/// `nonneg`: every `gt` is `≥ 0`, so every product is (each `x2 ≥ 0`). +/// +/// # Safety +/// The processor runs `kind`; `x2.len() ≥ c·np`, `gt.len() = c·B`, `beta.len() = B`, +/// `norms.len() ≥ B·np`, `gf ≤ 63`; and no partial sum leaves `i64` (the caller's bound). +#[allow(clippy::too_many_arguments)] +pub(super) unsafe fn igdn_norms(kind: Kind, x2: &[i32], np: usize, gt: &[i32], beta: &[i64], gf: u32, nonneg: bool, norms: &mut [i64]) -> usize { + // SAFETY: forwarded from the caller. + unsafe { + match (kind, nonneg) { + (Kind::Avx512 | Kind::Avx512Vnni, _) => igdn_avx512(x2, np, gt, beta, gf, norms), + (Kind::Avx2 | Kind::Avx2Vnni, true) => igdn_avx2::(x2, np, gt, beta, gf, norms), + (Kind::Avx2 | Kind::Avx2Vnni, false) => igdn_avx2::(x2, np, gt, beta, gf, norms), + } + } +} + +/// AVX-512: 8 channels × 16 positions (two vectors of eight 64-bit lanes per channel). +#[target_feature(enable = "avx512f")] +unsafe fn igdn_avx512(x2: &[i32], np: usize, gt: &[i32], beta: &[i64], gf: u32, norms: &mut [i64]) -> usize { + const B: usize = 8; + let c = gt.len() / B; + let cnt = _mm_cvtsi32_si128(gf as i32); + let mut p = 0; + // SAFETY (every raw access): reads `x2[j·np + p .. + 16]` with `p + 16 ≤ np`, + // `gt[j·B + k]` and `beta[k]`, and writes `norms[k·np + p .. + 16]`, all within the + // slices by the contract of [`igdn_norms`]. + unsafe { + while p + 16 <= np { + let mut a = [[_mm512_setzero_si512(); 2]; B]; + for (k, ak) in a.iter_mut().enumerate() { + let b = _mm512_set1_epi64(beta[k]); + *ak = [b, b]; + } + for j in 0..c { + let xs = _mm512_loadu_si512(x2.as_ptr().add(j * np + p) as *const __m512i); + let x0 = _mm512_cvtepi32_epi64(_mm512_castsi512_si256(xs)); + let x1 = _mm512_cvtepi32_epi64(_mm512_extracti64x4_epi64::<1>(xs)); + for (k, ak) in a.iter_mut().enumerate() { + let g = _mm512_set1_epi64(*gt.get_unchecked(j * B + k) as i64); + ak[0] = _mm512_add_epi64(ak[0], _mm512_sra_epi64(_mm512_mul_epi32(x0, g), cnt)); + ak[1] = _mm512_add_epi64(ak[1], _mm512_sra_epi64(_mm512_mul_epi32(x1, g), cnt)); + } + } + for (k, ak) in a.iter().enumerate() { + let d = norms.as_mut_ptr().add(k * np + p); + _mm512_storeu_si512(d as *mut __m512i, ak[0]); + _mm512_storeu_si512(d.add(8) as *mut __m512i, ak[1]); + } + p += 16; + } + } + p +} + +/// `⌊v / 2^gf⌋` in each 64-bit lane (`cnt` = gf, `back` = 64 − gf): the logical shift, +/// with the gf top bits set again in a negative lane (a shift left by 64 of all ones +/// gives 0, so gf = 0 is `v` itself). With `NONNEG` every lane is non-negative and the +/// logical shift is the floor. +#[inline(always)] +unsafe fn sra256(v: __m256i, cnt: __m128i, back: __m128i) -> __m256i { + // SAFETY: called only inside functions compiled with AVX2. + unsafe { + if NONNEG { + _mm256_srl_epi64(v, cnt) + } else { + let neg = _mm256_cmpgt_epi64(_mm256_setzero_si256(), v); + _mm256_or_si256(_mm256_srl_epi64(v, cnt), _mm256_sll_epi64(neg, back)) + } + } +} + +/// AVX2: 4 channels × 8 positions (two vectors of four 64-bit lanes per channel). +#[target_feature(enable = "avx2")] +unsafe fn igdn_avx2(x2: &[i32], np: usize, gt: &[i32], beta: &[i64], gf: u32, norms: &mut [i64]) -> usize { + const B: usize = 4; + let c = gt.len() / B; + let cnt = _mm_cvtsi32_si128(gf as i32); + // the sign bits a logical shift by gf clears: shifted in from the left by 64 − gf + let back = _mm_cvtsi32_si128(64 - gf as i32); + let zero = _mm256_setzero_si256(); + let mut p = 0; + // SAFETY (every raw access): as in `igdn_avx512`, with 8 positions per step. + unsafe { + while p + 8 <= np { + let mut a = [[zero; 2]; B]; + for (k, ak) in a.iter_mut().enumerate() { + let b = _mm256_set1_epi64x(beta[k]); + *ak = [b, b]; + } + for j in 0..c { + let xs = _mm256_loadu_si256(x2.as_ptr().add(j * np + p) as *const __m256i); + let x0 = _mm256_cvtepi32_epi64(_mm256_castsi256_si128(xs)); + let x1 = _mm256_cvtepi32_epi64(_mm256_extracti128_si256::<1>(xs)); + for (k, ak) in a.iter_mut().enumerate() { + let g = _mm256_set1_epi64x(*gt.get_unchecked(j * B + k) as i64); + ak[0] = _mm256_add_epi64(ak[0], sra256::(_mm256_mul_epi32(x0, g), cnt, back)); + ak[1] = _mm256_add_epi64(ak[1], sra256::(_mm256_mul_epi32(x1, g), cnt, back)); + } + } + for (k, ak) in a.iter().enumerate() { + let d = norms.as_mut_ptr().add(k * np + p); + _mm256_storeu_si256(d as *mut __m256i, ak[0]); + _mm256_storeu_si256(d.add(4) as *mut __m256i, ak[1]); + } + p += 8; + } + } + p +} diff --git a/wai/wai-rs/src/int_mlicpp.rs b/wai/wai-rs/src/int_mlicpp.rs index 32c180aa5..a65d23a9f 100644 --- a/wai/wai-rs/src/int_mlicpp.rs +++ b/wai/wai-rs/src/int_mlicpp.rs @@ -848,7 +848,7 @@ mod tests { let (pl, _) = encode(&m, &mut g); let want = with_policy(Policy::reference(), || decode_traced(&m, &pl)); assert!(want.1.is_ok()); - for p in FAST { + for &p in FAST.iter() { assert_eq!(with_policy(p, || decode_traced(&m, &pl)), want, "{p:?}"); } } diff --git a/wai/wai-rs/src/int_ops.rs b/wai/wai-rs/src/int_ops.rs index 957b28173..e5908e099 100644 --- a/wai/wai-rs/src/int_ops.rs +++ b/wai/wai-rs/src/int_ops.rs @@ -16,7 +16,10 @@ //! fast kernels, which form the same exact sums (`int_kernels`' equivalence rule) and //! finish each output with the operator set's own requantisation //! ([`wai_ops::interp::rq`]), and an inverse GDN runs on the fast IGDN kernel, whose -//! narrowed paths compute the same integers. Where a fast kernel declines (operands past +//! narrowed paths compute the same integers; and `rescale`, `clamp`, `relu` and `leaky` +//! run element by element on the fast kernels' workers ([`elementwise`]), each element +//! the same function of its input as the interpreter's, and the requantisation in `i64` +//! wherever its values fit ([`rq_ch`]). Where a fast kernel declines (operands past //! its narrowed types, geometry it does not cover, a value that would leave `i64`), the //! interpreter computes the operator, and refuses it where it refuses. //! @@ -240,6 +243,8 @@ impl Kernels for Host { let u = |v: &u32| *v as usize; let g = match &op.p { Params::Igdn { gf, frac, gamma, beta, .. } => return Some(vec![igdn(x, *gf, *frac, gamma, beta, (oc, oh, ow))?]), + Params::Rescale(_) | Params::Clamp { .. } | Params::Leaky { .. } => return Some(vec![elementwise(op, x, (oc, oh, ow))?]), + Params::None if op.code == wai_ops::graph::opcode::RELU => return Some(vec![elementwise(op, x, (oc, oh, ow))?]), Params::Conv { cout, kh, kw, sh, sw, ph, pw, groups, w: wt, .. } => { ConvGeom::conv(cin, h, w, u(cout), u(groups), u(kh), u(kw), u(sh), u(sw), u(ph), u(pw), x.data.len(), wt.len())? } @@ -256,6 +261,73 @@ impl Kernels for Host { } } +/// `rescale`, `clamp`, `relu` and `leaky` (§5.4, §5.5) element by element on the +/// fast kernels' workers: each output element the same function of its own input as the +/// interpreter's, in `i64` where that is exact (`clamp`, `relu`; `rescale` by +/// [`rq_ch`]) and in the interpreter's `i128` otherwise (`leaky`'s product). `None`, for +/// the interpreter to compute the operator (and refuse it), where a result would leave +/// `i64` or a parameter is one no loaded graph holds. +fn elementwise(op: &Op, x: &Tensor, shape: (u32, u32, u32)) -> Option { + use crate::int_kernels::{par_for_each, threads_for}; + use std::sync::atomic::{AtomicBool, Ordering}; + const CHUNK: usize = 1 << 14; + let plane = x.h as usize * x.w as usize; + let n = x.data.len(); + if shape != (x.c, x.h, x.w) || n == 0 || n != x.c as usize * plane { + return None; + } + let threads = threads_for((n as u64).saturating_mul(16)); + let mut out = vec![0i64; n]; + let bad = AtomicBool::new(false); + let fail = || { + bad.store(true, Ordering::Relaxed); + 0 + }; + match (&op.p, op.code) { + (Params::Rescale(rq), _) => { + let chans: Vec = (0..x.c as usize).map(|o| RqCh::of(rq, o)).collect(); + par_for_each(out.chunks_mut(plane).enumerate().collect(), threads, |(o, d): (usize, &mut [i64])| { + for (y, &v) in d.iter_mut().zip(&x.data[o * plane..(o + 1) * plane]) { + *y = match rq_ch(rq, &chans[o], o, v as i128).map(i64::try_from) { + Some(Ok(r)) => r, + _ => fail(), + }; + } + }); + } + (Params::Clamp { lo, hi }, _) if lo <= hi => { + par_for_each(out.chunks_mut(CHUNK).zip(x.data.chunks(CHUNK)).collect(), threads, |(d, s): (&mut [i64], &[i64])| { + for (y, &v) in d.iter_mut().zip(s) { + *y = v.clamp(*lo, *hi); + } + }); + } + (Params::None, wai_ops::graph::opcode::RELU) => { + par_for_each(out.chunks_mut(CHUNK).zip(x.data.chunks(CHUNK)).collect(), threads, |(d, s): (&mut [i64], &[i64])| { + for (y, &v) in d.iter_mut().zip(s) { + *y = v.max(0); + } + }); + } + (Params::Leaky { a, f }, _) if *f < 128 => { + par_for_each(out.chunks_mut(CHUNK).zip(x.data.chunks(CHUNK)).collect(), threads, |(d, s): (&mut [i64], &[i64])| { + for (y, &v) in d.iter_mut().zip(s) { + *y = if v >= 0 { + v + } else { + match i64::try_from((v as i128 * *a as i128) >> *f) { + Ok(r) => r, + Err(_) => fail(), + } + }; + } + }); + } + _ => return None, + } + (!bad.load(Ordering::Relaxed)).then(|| Tensor::new(x.c, x.h, x.w, out)) +} + /// An inverse GDN on the fast kernel ([`crate::int_kernels::igdn`]), whose integers are the /// interpreter's (§5.7: each term `γ·x²` floored by `2^gf` before it is summed, the /// norm's integer square root, then `x·r` floored by `2^frac`) wherever no value leaves the @@ -272,7 +344,8 @@ fn igdn(x: &Tensor, gf: u32, frac: u32, gamma: &[i64], beta: &[i64], shape: (u32 if shape != (x.c, x.h, x.w) || c.checked_mul(c) != Some(gamma.len()) || beta.len() != c || gf > 127 || frac > 127 { return None; } - let xm = max_abs(&x.data) as u128; + let xm = crate::int_kernels::max_abs_par(&x.data, crate::int_kernels::threads_for((c as u64).saturating_mul(c as u64).saturating_mul(hw as u64))); + let (xm64, xm) = (xm, xm as u128); // x² ≤ 2^126 and x·root ≤ 2^126 fit u128. let term = (max_abs(gamma) as u128).checked_mul(xm * xm).filter(|&t| t <= I128)?; let norm = (max_abs(beta) as u128).checked_add((c as u128).checked_mul((term >> gf) + 1)?).filter(|&n| n <= I128)?; @@ -280,7 +353,7 @@ fn igdn(x: &Tensor, gf: u32, frac: u32, gamma: &[i64], beta: &[i64], shape: (u32 if root > I64 || ((xm * root) >> frac) + 1 > I64 { return None; } - let d = crate::int_kernels::igdn(&x.data, c, hw, frac, gamma, beta, gf)?; + let d = crate::int_kernels::igdn_xm(&x.data, c, hw, frac, gamma, beta, gf, Some(xm64))?; Some(Tensor::new(x.c, x.h, x.w, d)) } @@ -291,7 +364,8 @@ fn igdn(x: &Tensor, gf: u32, frac: u32, gamma: &[i64], beta: &[i64], shape: (u32 fn conv(g: &ConvGeom, x: &[i64], wt: &[i64], rq: &wai_ops::graph::Rq) -> Option> { use std::sync::atomic::{AtomicBool, Ordering}; let wide = AtomicBool::new(false); - let out = crate::int_kernels::conv_acc_i128(g, x, wt, |oc, acc| match wai_ops::interp::rq(rq, oc, acc).map(i64::try_from) { + let chans: Vec = (0..g.cout).map(|o| RqCh::of(rq, o)).collect(); + let out = crate::int_kernels::conv_acc_i128(g, x, wt, |oc, acc| match rq_ch(rq, &chans[oc], oc, acc).map(i64::try_from) { Some(Ok(v)) => v, _ => { wide.store(true, Ordering::Relaxed); @@ -301,6 +375,68 @@ fn conv(g: &ConvGeom, x: &[i64], wt: &[i64], rq: &wai_ops::graph::Rq) -> Option< (!wide.load(Ordering::Relaxed)).then_some(out) } +/// [`rq_ch`], reading channel `o`'s parameters itself. +#[cfg(test)] +fn rq_fast(rq: &wai_ops::graph::Rq, o: usize, v: i128) -> Option { + rq_ch(rq, &RqCh::of(rq, o), o, v) +} + +/// Channel `o`'s requantisation parameters, read once per operator. +struct RqCh { + pre: i64, + mult: Option, + shift: u32, + post: i64, +} + +impl RqCh { + fn of(rq: &wai_ops::graph::Rq, o: usize) -> RqCh { + RqCh { pre: rq.pre(o), mult: rq.mult(o), shift: rq.shift(o), post: rq.post(o) } + } +} + +/// The operator set's requantisation `rq_o(v)` ([`wai_ops::interp::rq`], §5.1), with +/// channel `o`'s parameters `ch` read already: in `i64` where `v` and every value it forms +/// fit `i64`, else in `i128` by the interpreter's own function. Each step is the same +/// operation on the same integers in either width (checked, so a value past `i64` takes +/// the `i128` path instead of wrapping), so the result is the interpreter's. +#[inline(always)] +fn rq_ch(rq: &wai_ops::graph::Rq, ch: &RqCh, o: usize, v: i128) -> Option { + if let Ok(v) = i64::try_from(v) + && let Some(y) = rq_i64(rq.round, ch, v) + { + return Some(y as i128); + } + wai_ops::interp::rq(rq, o, v) +} + +/// [`rq_ch`]'s `i64` form: `None` where a value would leave `i64` or the shift is past +/// 62. +#[inline(always)] +fn rq_i64(round: wai_ops::graph::Round, ch: &RqCh, v: i64) -> Option { + use wai_ops::graph::Round; + let t = v.checked_add(ch.pre)?; + let p = match ch.mult { + Some(m) => t.checked_mul(m)?, + None => t, + }; + let s = ch.shift; + let q = match (s, round) { + (0, _) => p, + (63.., _) => return None, + (_, Round::Floor) => p >> s, + (_, Round::HalfUp) => p.checked_add(1 << (s - 1))? >> s, + (_, Round::HalfEven) => { + // the reference's: the low s bits of p's two's complement, against 2^(s−1) + let q = p >> s; + let rem = (p as u64) & ((1u64 << s) - 1); + let h = 1u64 << (s - 1); + if rem > h || (rem == h && q & 1 == 1) { q + 1 } else { q } + } + }; + q.checked_add(ch.post) +} + /// [`whm1_net`] at the edges of the widths `WHM1` gives a layer's weights (`i16`) and an /// inverse GDN's `γ` (`i32`): a model at either end converts, and its lowered graph, as a /// sink loads it from its bytes, holds the model's value; one past either end, which only @@ -381,7 +517,7 @@ mod equivalence { let Ok(l) = load(g) else { return (false, 0) }; let want = with_policy(Policy::reference(), || run1(&l, x.clone(), &[])); let mut fast = 0; - for p in FAST { + for &p in FAST.iter() { let (got, runs) = fast_conv_runs(|| with_policy(p, || run1(&l, x.clone(), &[]))); assert_eq!(got, want, "{p:?} on operator {}", g.ops[0].code); fast += runs; @@ -421,6 +557,86 @@ mod equivalence { assert!(fast > 600, "the fast kernel ran only {fast} times"); } + /// The requantisation in `i64` ([`rq_ch`]) is the interpreter's (`wai_ops::interp::rq`) + /// on every rounding, bias kind, multiplier kind and shift up to 70, for values across + /// `i64` and past it, the edges among them: equal where both return, and `None` exactly + /// where the interpreter's is. + #[test] + fn the_i64_requantisation_is_the_interpreters() { + let mut r = Rng(0x0D2C_00A1); + for case in 0..4000 { + let n = 1 + r.below(4); + let round = [Round::Floor, Round::HalfUp, Round::HalfEven][case % 3]; + let bias_kind = [BiasKind::None, BiasKind::Pre, BiasKind::Post][(case / 3) % 3]; + let m_kind = [0u8, 1, 2][(case / 9) % 3]; + let mag = [1i64 << 10, 1 << 31, i64::MAX >> 1, i64::MAX][r.below(4)]; + let q = Rq { + round, + bias_kind, + m_kind, + s_kind: 2, + m_bytes: 8, + b_bytes: 8, + mult: (0..if m_kind == 2 { n } else { 1 }).map(|_| r.signed(mag >> 8)).collect(), + shift: (0..n).map(|_| [0u8, 1, 15, 16, 31, 62, 63, 64, 70][r.below(9)]).collect(), + bias: (0..n).map(|_| r.signed(mag >> 1)).collect(), + }; + for _ in 0..8 { + let o = r.below(n); + let v: i128 = match r.below(5) { + 0 => r.signed(1 << 20) as i128, + 1 => r.signed(mag) as i128, + 2 => [i64::MIN as i128, i64::MAX as i128, 0, -1, 1][r.below(5)], + 3 => (r.signed(i64::MAX) as i128) << 20, + _ => -((1i128 << r.below(62)) as i128), + }; + assert_eq!(rq_fast(&q, o, v), wai_ops::interp::rq(&q, o, v), "{q:?} o {o} v {v}"); + } + } + } + + /// `rescale`, `clamp`, `relu` and `leaky` on the fast workers give the interpreter's + /// integers: random channel counts and planes (single elements to several chunks), + /// every rounding and bias form of `rescale`, inputs to `i64::MAX`, and `leaky` slopes + /// up to 2^40. (A value past `i64`, which load step 8 leaves no loaded graph to form, + /// would decline to the interpreter.) + #[test] + fn elementwise_operators_equal_the_interpreter() { + let mut r = Rng(0x0D2C_00A2); + let mut loaded = 0; + for case in 0..240 { + let c = 1 + r.below(5) as u32; + let (h, w) = (1 + r.below(40) as u32, 1 + r.below(300) as u32); + let xmax = [1i64 << 12, 1 << 40, i64::MAX][case % 3]; + let (code, p) = match case % 4 { + 0 => { + let round = [Round::Floor, Round::HalfUp, Round::HalfEven][r.below(3)]; + let bias_kind = [BiasKind::None, BiasKind::Pre, BiasKind::Post][r.below(3)]; + let with_mult = r.below(2) == 1; + (opcode::RESCALE, Params::Rescale(rq(&mut r, c, round, bias_kind, with_mult))) + } + 1 => { + let lo = r.signed(1 << 20); + (opcode::CLAMP, Params::Clamp { lo, hi: lo + r.signed(1 << 20).abs() }) + } + 2 => (opcode::RELU, Params::None), + _ => { + let mag = [1i64 << 10, 1 << 40][r.below(2)]; + (opcode::LEAKY, Params::Leaky { a: r.signed(mag), f: r.below(64) as u32 }) + } + }; + let g = graph(TensorRec::check(c, -xmax, xmax), code, c, p); + let Ok(l) = load(&g) else { continue }; + loaded += 1; + let x = Tensor::new(c, h, w, r.vec((c * h * w) as usize, xmax, 20)); + let want = with_policy(Policy::reference(), || run1(&l, x.clone(), &[])); + for &pol in FAST.iter() { + assert_eq!(with_policy(pol, || run1(&l, x.clone(), &[])), want, "{pol:?} operator {code}"); + } + } + assert!(loaded > 120, "only {loaded} graphs loaded"); + } + /// The one-dimensional transposed convolutions of `wam1/1` (§13.3: `kh = 1`, `sh = 1`, /// `ph = 0`, `oph = 0` over `[cin, 1, T]`), with `WAM1`'s requantisation (a multiplier /// per channel, one shift, a `post` bias, `floor`): random kernels up to 9, strides up @@ -478,7 +694,7 @@ mod equivalence { }; let ((want, none), no_igdn) = fast_igdn_runs(|| fast_conv_runs(|| with_policy(Policy::reference(), || ops::decode_typed(&gs, &st).unwrap().attr))); assert_eq!((none, no_igdn), (0, 0), "the reference policy runs no fast kernel"); - for p in FAST { + for &p in FAST.iter() { let ((got, runs), igdns) = fast_igdn_runs(|| fast_conv_runs(|| with_policy(p, || ops::decode_typed(&gs, &st).unwrap().attr))); assert!(got == want, "{p:?}: the attribute bytes differ from the interpreter alone"); assert_eq!((runs, igdns), (2, 1), "{p:?}: both transposed convolutions and the inverse GDN run on the fast kernels"); @@ -488,7 +704,8 @@ mod equivalence { /// The committed `wai.audio.int_codec` synthesis through its lowering `wam1/1`: under /// every fast policy each of its four one-dimensional transposed convolutions and each of /// its inverse GDNs run on the fast kernels, and every sample is the interpreter's. Its - /// PCM step runs on the interpreter under every policy. + /// PCM step's crop runs on the interpreter under every policy, its rescale and clamp on + /// the fast workers ([`elementwise`]). #[cfg(feature = "audio_int")] #[test] fn the_audio_synthesis_runs_its_convolutions_on_the_fast_kernel() { @@ -506,7 +723,7 @@ mod equivalence { assert!(igdns > 0, "the committed audio synthesis has an inverse GDN"); let ((want, none), no_igdn) = fast_igdn_runs(|| fast_conv_runs(|| with_policy(Policy::reference(), || ops::decode_typed(&m, &s).unwrap()))); assert_eq!((none, no_igdn), (0, 0), "the reference policy runs no fast kernel"); - for p in FAST { + for &p in FAST.iter() { let ((got, runs), ig) = fast_igdn_runs(|| fast_conv_runs(|| with_policy(p, || ops::decode_typed(&m, &s).unwrap()))); assert!(got == want, "{p:?}: the samples differ from the interpreter alone"); assert_eq!((runs, ig), (4, igdns), "{p:?}: every transposed convolution and inverse GDN runs on the fast kernels"); @@ -517,8 +734,8 @@ mod equivalence { /// `wai.video.int_hyper`'s vector) through its lowering `whm1/1`: under every fast policy /// its four convolutions (`h_s`'s transposed convolution and convolution, `g_s`'s two /// transposed convolutions) and `g_s`'s inverse GDN run on the fast kernels, and every - /// byte is the interpreter's. `h_s`'s leaky ReLU and the RGB8 step run on the - /// interpreter under every policy. + /// byte is the interpreter's. `h_s`'s leaky ReLU and the RGB8 step (a clamp and a + /// rescale) run element by element on the fast workers ([`elementwise`]). #[test] fn the_conditional_codec_runs_its_networks_on_the_fast_kernels() { use crate::int_hyper_synth::{ops, CondEntropy, CondStream}; @@ -536,7 +753,7 @@ mod equivalence { let decode = |pol: Policy| fast_igdn_runs(|| fast_conv_runs(|| with_policy(pol, || ops::decode(&g, &h, &ent, &st).unwrap()))); let ((want, none), no_igdn) = decode(Policy::reference()); assert_eq!((none, no_igdn), (0, 0), "the reference policy runs no fast kernel"); - for pol in FAST { + for &pol in FAST.iter() { let ((got, convs), igdns) = decode(pol); assert!(got == want, "{pol:?}: the image differs from the interpreter alone"); assert_eq!((convs, igdns), (4, 1), "{pol:?}: every convolution and the inverse GDN run on the fast kernels"); @@ -547,8 +764,8 @@ mod equivalence { /// fast policy every convolution and transposed convolution of its three graphs, each /// time it runs (`h_s` once, the entropy network before each of the `2·K` passes, `g_s` /// once), and `g_s`'s inverse GDNs run on the fast kernels, and every bucket, symbol and - /// byte is the interpreter's. The leaky ReLU, the ReLU and the RGB8 step run on the - /// interpreter under every policy. + /// byte is the interpreter's. The leaky ReLU, the ReLU and the RGB8 step run element + /// by element on the fast workers ([`elementwise`]). #[test] fn the_mlicpp_networks_run_on_the_fast_kernels() { use crate::int_kernels::testkit::fast_igdn_runs; @@ -572,7 +789,7 @@ mod equivalence { let ((want, none), no_igdn) = decode(Policy::reference()); assert!(want.1.is_ok(), "the committed vector decodes"); assert_eq!((none, no_igdn), (0, 0), "the reference policy runs no fast kernel"); - for pol in FAST { + for &pol in FAST.iter() { let ((got, convs), igdns) = decode(pol); assert!(got == want, "{pol:?}: the buckets, symbols or image differ from the interpreter alone"); assert_eq!((convs, igdns), (want_convs, want_igdns), "{pol:?}: every convolution and inverse GDN runs on the fast kernels"); @@ -603,7 +820,7 @@ mod equivalence { let Ok(l) = load(&g) else { continue }; loaded += 1; let want = with_policy(Policy::reference(), || run1(&l, x.clone(), &[])); - for pol in FAST { + for &pol in FAST.iter() { let (got, runs) = fast_igdn_runs(|| with_policy(pol, || run1(&l, x.clone(), &[]))); assert_eq!(got, want, "{pol:?}: c={c} gf={gf} frac={frac} |x|<={xmax}"); fast += runs; @@ -657,8 +874,9 @@ mod equivalence { /// lowering `world/1`: under every fast policy each convolution of `T`, every time it /// runs (once per row), and each transposed convolution and inverse GDN of `g_s`, every /// time it runs (once per frame), run on the fast kernels, and every frame's bytes are - /// the interpreter's. `T`'s planes, its leaky ReLU and clamp, and the RGB8 step run on - /// the interpreter under every policy. + /// the interpreter's. `T`'s planes run on the interpreter under every policy; its leaky + /// ReLU and clamp, and the RGB8 step, element by element on the fast workers + /// ([`elementwise`]). #[cfg(feature = "world_int")] #[test] fn the_world_renderer_runs_its_networks_on_the_fast_kernels() { @@ -691,7 +909,7 @@ mod equivalence { let render = |pol: Policy| fast_igdn_runs(|| fast_conv_runs(|| with_policy(pol, || w.decode_planes(&z0, &rows, lat).unwrap().frames))); let ((want, none), no_igdn) = render(Policy::reference()); assert_eq!((none, no_igdn), (0, 0), "{d}: the reference policy runs no fast kernel"); - for pol in FAST { + for &pol in FAST.iter() { let ((got, runs), igdns) = render(pol); assert!(got == want, "{d} {pol:?}: the frames differ from the interpreter alone"); assert_eq!((runs, igdns), (want_convs, want_igdns), "{d} {pol:?}: every convolution and inverse GDN runs on the fast kernels"); @@ -712,7 +930,7 @@ mod equivalence { let l = load(&g).expect("the graph loads"); let want = with_policy(Policy::reference(), || run1(&l, x.clone(), &[])).unwrap(); let mut runs = Vec::new(); - for p in FAST { + for &p in FAST.iter() { let (got, n) = fast_conv_runs(|| with_policy(p, || run1(&l, x.clone(), &[]))); assert_eq!(got.as_ref(), Ok(&want), "{p:?}"); runs.push(n); diff --git a/wai/wai-rs/src/int_transform.rs b/wai/wai-rs/src/int_transform.rs index 9e31949f2..1ef8c0bb1 100644 --- a/wai/wai-rs/src/int_transform.rs +++ b/wai/wai-rs/src/int_transform.rs @@ -1112,7 +1112,7 @@ mod igdn_equivalence { g.vec(c * c, gr, 20).into_iter().map(|v| if signed_gamma { v } else { v.abs() }).collect(); let beta: Vec = g.vec(c, br, 0).into_iter().map(i64::abs).collect(); let want = reference::igdn(&x, c, hw, frac, &gamma, &beta, gf); - for p in FAST { + for &p in FAST.iter() { let got = with_policy(p, || igdn(&x, c, hw, frac, &gamma, &beta, gf)); assert_eq!(got, want, "igdn[{label}] {p:?} c={c} hw={hw} frac={frac} gf={gf}"); } @@ -1124,6 +1124,11 @@ mod igdn_equivalence { let mut g = Rng(0x16D_0001); // Q12 activations, the committed models' magnitudes (32×32→64 terms) check(&mut g, 120, 1 << 15, 1 << 18, 1 << 26, 16, false, "narrow"); + // the same with signed gamma: the SIMD floor shifts of negative terms (on AVX2, + // which has no 64-bit arithmetic shift, a logical shift with the sign put back) + check(&mut g, 120, 1 << 15, 1 << 18, 1 << 26, 16, true, "narrow-signed"); + // shifts from 0 up, signed: a shift by 0 and the sign bits shifted back in by 64 − gf + check(&mut g, 120, 1 << 15, 1 << 12, 1 << 26, 0, true, "narrow-low-gf"); // x² beyond i32, terms still in i64; signed gamma (floor shifts of negatives) check(&mut g, 120, 1 << 20, 1 << 16, 1 << 30, 16, true, "i64"); // gamma·x² beyond i64: the i128 path @@ -1146,7 +1151,7 @@ mod igdn_equivalence { let gamma = vec![1i64; c * c]; let beta = vec![-(1i64 << 40), 5, -3]; let want = reference::igdn(&x, c, hw, 12, &gamma, &beta, 16); - for p in FAST { + for &p in FAST.iter() { assert_eq!(with_policy(p, || igdn(&x, c, hw, 12, &gamma, &beta, 16)), want, "{p:?}"); } } diff --git a/wai/wai-rs/src/int_transformer.rs b/wai/wai-rs/src/int_transformer.rs index 432216c9b..eb860e2ef 100644 --- a/wai/wai-rs/src/int_transformer.rs +++ b/wai/wai-rs/src/int_transformer.rs @@ -1076,7 +1076,7 @@ mod kernel_equivalence { let bias = g.vec(cout, 1 << 16, 0); let shift = 16 + g.below(16) as u32; let want = reference::conv_grouped(&x, cin, h, w, &wt, cout, k, stride, pad, groups, &mult, &bias, shift); - for p in FAST { + for &p in FAST.iter() { let got = with_policy(p, || conv_grouped(&x, cin, h, w, &wt, cout, k, stride, pad, groups, &mult, &bias, shift)); assert_eq!(got, want, "conv_grouped {p:?} k={k} s={stride} p={pad} g={groups} {cin}x{h}x{w}->{cout}"); } @@ -1107,7 +1107,7 @@ mod kernel_equivalence { let mult: Vec = (0..cout).map(|_| 1 + g.signed(1 << 16).abs()).collect(); let bias = g.vec(cout, 1 << 16, 0); let want = reference::conv_transpose(&x, cin, h, w, &wt, cout, k, stride, pad, opad, &mult, &bias, shift); - for p in FAST { + for &p in FAST.iter() { let got = with_policy(p, || conv_transpose(&x, cin, h, w, &wt, cout, k, stride, pad, opad, &mult, &bias, shift)); assert_eq!(got, want, "conv_transpose {p:?} k={k} s={stride} p={pad} op={opad} {cin}x{h}x{w}->{cout}"); } @@ -1131,7 +1131,7 @@ mod kernel_equivalence { let eps = [0u128, 42_949][case % 2]; let want = with_policy(Policy::reference(), || mdta(&q, &k, &v, &temp, eps)); assert_eq!(want, reference::mdta(&q, &k, &v, &temp, eps)); - for p in FAST { + for &p in FAST.iter() { assert_eq!(with_policy(p, || mdta(&q, &k, &v, &temp, eps)), want, "mdta {p:?} case {case}"); } } @@ -1147,7 +1147,7 @@ mod kernel_equivalence { let beta = g.vec(c, 1 << 15, 0); let eps = g.below(1 << 20) as u128; let want = reference::layernorm_chw(&x, c, hw, &gamma, &beta, eps); - for p in FAST { + for &p in FAST.iter() { assert_eq!(with_policy(p, || layernorm_chw(&x, c, hw, &gamma, &beta, eps)), want, "{p:?} c={c} hw={hw}"); } } @@ -1185,7 +1185,7 @@ mod kernel_equivalence { let x = g.vec(c * h * w, 3 << 16, 0); let eps = 42_949u128; let want = with_policy(Policy::reference(), || tab(&x, &attn, &ffn, eps)); - for p in FAST { + for &p in FAST.iter() { assert_eq!(with_policy(p, || tab(&x, &attn, &ffn, eps)), want, "tab {p:?} c={c} heads={heads} {h}x{w}"); } } @@ -1204,7 +1204,7 @@ mod kernel_equivalence { let x = vec![xv; cin * h * w]; let wt = vec![wv; cin * cout * k * k]; let want = reference::conv_transpose(&x, cin, h, w, &wt, cout, k, 1, 0, 0, &[1], &[0], 8); - for p in FAST { + for &p in FAST.iter() { let got = with_policy(p, || conv_transpose(&x, cin, h, w, &wt, cout, k, 1, 0, 0, &[1], &[0], 8)); assert_eq!(got, want, "bound edge x={xv} {p:?}"); } @@ -1239,7 +1239,7 @@ mod kernel_equivalence { } else { assert_eq!(want, Some((vec![1i64 << 61], 1, 1))); } - for p in FAST { + for &p in FAST.iter() { assert_eq!(run(p), want, "fast must match the reference, panic included ({p:?})"); } } @@ -1264,7 +1264,7 @@ mod kernel_equivalence { let wt: Vec = (1..=cin as i64).map(|c| 10 * c).collect(); let want = reference::conv_grouped(&x, cin, h, h, &wt, 1, 1, stride, 5, 1, &mult, &bias, shift); assert_eq!(want, (out.to_vec(), 2, 2), "reference, {cin}×{h}² stride {stride}"); - for p in FAST { + for &p in FAST.iter() { let got = fast_conv_runs(|| with_policy(p, || conv_grouped(&x, cin, h, h, &wt, 1, 1, stride, 5, 1, &mult, &bias, shift))); assert_eq!(got, (want.clone(), runs), "{cin}×{h}² stride {stride} {p:?}"); @@ -1277,7 +1277,7 @@ mod kernel_equivalence { let (x, wt) = (vec![5i64; cin], vec![3i64; cin]); let want = reference::conv_transpose(&x, cin, 1, 1, &wt, 1, 1, 1, pad, opad, &mult, &bias, shift); assert_eq!(want, (vec![7; side * side], side, side), "reference, pad {pad} opad {opad}"); - for p in FAST { + for &p in FAST.iter() { let got = fast_conv_runs(|| with_policy(p, || conv_transpose(&x, cin, 1, 1, &wt, 1, 1, 1, pad, opad, &mult, &bias, shift))); assert_eq!(got, (want.clone(), runs), "pad {pad} opad {opad} {p:?}"); diff --git a/wai/wai-rs/src/jpegai_ops.rs b/wai/wai-rs/src/jpegai_ops.rs index 957a350f8..3123a8543 100644 --- a/wai/wai-rs/src/jpegai_ops.rs +++ b/wai/wai-rs/src/jpegai_ops.rs @@ -508,7 +508,7 @@ mod fast_kernels { let x = || Tensor::new(c as u32, 4, 4, z.clone()); let (want, none) = fast_conv_runs(|| with_policy(Policy::reference(), || crate::int_ops::run1(sp.graph(plane), x(), &[]).unwrap())); assert_eq!(none, 0, "the reference policy runs no fast kernel"); - for p in FAST { + for &p in FAST.iter() { let (got, runs) = fast_conv_runs(|| with_policy(p, || crate::int_ops::run1(sp.graph(plane), x(), &[]).unwrap())); assert!(got == want, "{plane:?} {p:?}: the scale differs from the interpreter alone"); assert_eq!(runs, 3, "{plane:?} {p:?}: the three convolutions run on the fast kernel"); @@ -544,7 +544,7 @@ mod fast_kernels { let run = || wai_ops::run_with(g, ins.clone(), &[], &mut wai_ops::Sequence::new(), &crate::int_ops::Host); let (want, none) = fast_conv_runs(|| with_policy(Policy::reference(), run)); assert_eq!(none, 0, "{role}: the reference policy runs no fast kernel"); - for p in FAST { + for &p in FAST.iter() { let (got, runs) = fast_conv_runs(|| with_policy(p, run)); assert!(got == want, "{role} {p:?}: the outputs differ from the interpreter alone"); if want.is_ok() { -- 2.54.0 (Apple Git-157) From 67ac8c544be859bc22a58895c11f47a6c78117e1 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:36:10 -0400 Subject: [PATCH 071/242] wai-rs: the decoder tests run every kernel set, and the ops corpus runs through the fast kernels Each decoder-level equivalence test now runs int_kernels::equivalence_policies: the reference, the fast kernels on every tiling, and each kernel set the processor runs. tests/int_ops_conformance.rs decodes every graph vector of ops-conformance/ (vectors/ and graphs/) through the interpreter with the fast kernels behind it, under each of those policies, and holds every decode to the integers or refusal the vector states. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/Cargo.toml | 5 ++ wai/wai-rs/tests/int_audio_ops.rs | 2 +- wai/wai-rs/tests/int_hyper_ops.rs | 2 +- wai/wai-rs/tests/int_kernels_geometry.rs | 2 +- wai/wai-rs/tests/int_kernels_vectors.rs | 5 +- wai/wai-rs/tests/int_mlicpp_ops.rs | 2 +- wai/wai-rs/tests/int_ops_conformance.rs | 84 ++++++++++++++++++++++++ wai/wai-rs/tests/int_splat_ops.rs | 2 +- wai/wai-rs/tests/int_synth_ops.rs | 2 +- wai/wai-rs/tests/int_world_ops.rs | 2 +- wai/wai-rs/tests/jpegai_nets_ops.rs | 2 +- wai/wai-rs/tests/jpegai_ops.rs | 2 +- 12 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 wai/wai-rs/tests/int_ops_conformance.rs diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index 60b936089..265ea239d 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -1182,6 +1182,11 @@ name = "int_hyper_ops" path = "tests/int_hyper_ops.rs" required-features = ["video_int", "legacy_int"] +[[test]] +name = "int_ops_conformance" +path = "tests/int_ops_conformance.rs" +required-features = ["neural_int"] + # wai.world.model's integer renderer through its lowering world/1 (integer-operators §13.5). [[test]] name = "int_world_ops" diff --git a/wai/wai-rs/tests/int_audio_ops.rs b/wai/wai-rs/tests/int_audio_ops.rs index 257af6dc8..fb41f9b65 100644 --- a/wai/wai-rs/tests/int_audio_ops.rs +++ b/wai/wai-rs/tests/int_audio_ops.rs @@ -397,7 +397,7 @@ fn every_check_range_refuses_exactly_what_the_direct_decode_refuses() { #[test] fn every_kernel_policy_decodes_through_the_lowering_alike() { use wai::int_kernels::{with_policy, Policy}; - const POLICIES: [Policy; 5] = [Policy::reference(), Policy::fast(1), Policy::fast(2), Policy::fast(5), Policy::fast(0)]; + static POLICIES: std::sync::LazyLock> = std::sync::LazyLock::new(wai::int_kernels::equivalence_policies); let m = model(|_| {}); let x0 = wai_ops::lower::wam1(&read(SET)).unwrap().tensors[0].hi; let at = |t: i64| clip(move |o| o.median_q[1] = t - ((o.offset as i64) << o.afrac)); diff --git a/wai/wai-rs/tests/int_hyper_ops.rs b/wai/wai-rs/tests/int_hyper_ops.rs index 36706d6ed..9ec31cd0c 100644 --- a/wai/wai-rs/tests/int_hyper_ops.rs +++ b/wai/wai-rs/tests/int_hyper_ops.rs @@ -504,7 +504,7 @@ fn every_check_range_refuses_exactly_what_the_direct_decode_refuses() { #[test] fn every_kernel_policy_decodes_through_the_lowering_alike() { use wai::int_kernels::{with_policy, Policy}; - const POLICIES: [Policy; 5] = [Policy::reference(), Policy::fast(1), Policy::fast(2), Policy::fast(5), Policy::fast(0)]; + static POLICIES: std::sync::LazyLock> = std::sync::LazyLock::new(wai::int_kernels::equivalence_policies); let x0 = wai_ops::lower::whm1(&read(&format!("{DIR}/tiny_h_s.bin")), false).unwrap().tensors[0].hi; let base = Owned::committed(); let bs = hyper::CondBitstream::try_from_value(&json(&format!("{DIR}/tiny_bitstream.json"))).unwrap(); diff --git a/wai/wai-rs/tests/int_kernels_geometry.rs b/wai/wai-rs/tests/int_kernels_geometry.rs index f859d9fa1..f715e60aa 100644 --- a/wai/wai-rs/tests/int_kernels_geometry.rs +++ b/wai/wai-rs/tests/int_kernels_geometry.rs @@ -29,7 +29,7 @@ use serde_json::json; use wai::int_kernels::{with_policy, Policy}; -const POLICIES: [Policy; 5] = [Policy::reference(), Policy::fast(1), Policy::fast(2), Policy::fast(5), Policy::fast(0)]; +static POLICIES: std::sync::LazyLock> = std::sync::LazyLock::new(wai::int_kernels::equivalence_policies); /// A 3×3 kernel `[[1, 2, 3], [4, 5, 6], [7, 8, 9]]`. const KERNEL: [i64; 9] = [1, 2, 3, 4, 5, 6, 7, 8, 9]; diff --git a/wai/wai-rs/tests/int_kernels_vectors.rs b/wai/wai-rs/tests/int_kernels_vectors.rs index b302a4006..e3050ad0a 100644 --- a/wai/wai-rs/tests/int_kernels_vectors.rs +++ b/wai/wai-rs/tests/int_kernels_vectors.rs @@ -2,7 +2,8 @@ //! (int_hyper, int_mlicpp, int_audio, int_splat, int_world one and two agents, the //! neural-keyframe video, the int_synth corpus), plus the real-footage video, //! decoded under the scalar reference kernels AND under each fast-kernel policy -//! (serial, explicit multi-threaded tilings, automatic): the outputs must be +//! (serial, explicit multi-threaded tilings, automatic, and each kernel set the +//! processor runs, `int_kernels::equivalence_policies`): the outputs must be //! byte-identical to each other and must reproduce the vector's frozen hash. The //! plain `int_video` vector (block motion compensation + rANS, no learned layer) //! never reaches the kernels and is left to `tests/int_video.rs` and the @@ -24,7 +25,7 @@ mod sha256_sidecar; const RGB8: &str = "RGB8: rows top to bottom, pixels left to right, R, G, B bytes"; -const POLICIES: [Policy; 5] = [Policy::reference(), Policy::fast(1), Policy::fast(2), Policy::fast(5), Policy::fast(0)]; +static POLICIES: std::sync::LazyLock> = std::sync::LazyLock::new(wai::int_kernels::equivalence_policies); fn vroot() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/vectors") diff --git a/wai/wai-rs/tests/int_mlicpp_ops.rs b/wai/wai-rs/tests/int_mlicpp_ops.rs index 98c49630b..dd843ec51 100644 --- a/wai/wai-rs/tests/int_mlicpp_ops.rs +++ b/wai/wai-rs/tests/int_mlicpp_ops.rs @@ -476,7 +476,7 @@ fn every_check_range_refuses_exactly_what_the_direct_decode_refuses() { #[test] fn every_kernel_policy_decodes_through_the_lowering_alike() { use wai::int_kernels::{with_policy, Policy}; - const POLICIES: [Policy; 5] = [Policy::reference(), Policy::fast(1), Policy::fast(2), Policy::fast(5), Policy::fast(0)]; + static POLICIES: std::sync::LazyLock> = std::sync::LazyLock::new(wai::int_kernels::equivalence_policies); let x0 = wai_ops::lower::whm1(§ions(&read(&format!("{DIR}/model.wmb")))[0], false).unwrap().tensors[0].hi; let base = Wim1::committed(); let z0 = base.z.clone(); diff --git a/wai/wai-rs/tests/int_ops_conformance.rs b/wai/wai-rs/tests/int_ops_conformance.rs new file mode 100644 index 000000000..f8a1d2ff4 --- /dev/null +++ b/wai/wai-rs/tests/int_ops_conformance.rs @@ -0,0 +1,84 @@ +//! Every graph vector of `ops-conformance/` (`vectors/` and `graphs/`: each operator on +//! its own, and the multi-operator graphs, the lowerings and the state vectors among them) +//! decoded through the integer operator set's interpreter with this crate's fast kernels +//! behind it (`int_ops::Host`), under every fast policy and every kernel set this +//! processor runs (`int_kernels::equivalence_policies`): each decode must give the output +//! integers the vector states, or its refusal code. The vectors state the reference +//! interpreter's results (`wai-ops`' `vectors verify` recomputes them), so this holds +//! each fast kernel, operator by operator, to the reference on the whole corpus. +#![cfg(feature = "neural_int")] + +use std::path::PathBuf; + +use serde_json::Value; +use wai::int_kernels::{with_policy, KernelPath}; +use wai_ops::{Profile, Sequence, Tensor}; + +fn corpus() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../ops-conformance") +} + +fn int(v: &Value) -> i64 { + v.as_str().and_then(|s| s.parse().ok()).unwrap_or_else(|| panic!("not a decimal integer: {v}")) +} + +fn tensor(v: &Value) -> Tensor { + let s: Vec = v["shape"].as_array().unwrap().iter().map(int).collect(); + let data = v["data"].as_array().unwrap().iter().map(int).collect(); + Tensor::new(s[0] as u32, s[1] as u32, s[2] as u32, data) +} + +fn unhex(s: &str) -> Vec { + (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect() +} + +/// What a decode gave: its outputs, or its refusal's code. +type Got = Result, String>; + +fn stated(d: &Value) -> Got { + let e = &d["expect"]; + match e.get("refuse") { + Some(c) => Err(c.as_str().unwrap().to_string()), + None => Ok(e["outputs"].as_array().unwrap().iter().map(tensor).collect()), + } +} + +#[test] +fn every_graph_vector_decodes_as_stated_under_every_fast_kernel_set() { + let policies: Vec<_> = wai::int_kernels::equivalence_policies().into_iter().filter(|p| p.path == KernelPath::Fast).collect(); + let (mut files, mut lines, mut decodes) = (0, 0, 0); + for dir in ["vectors", "graphs"] { + let mut names: Vec<_> = std::fs::read_dir(corpus().join(dir)).unwrap().map(|e| e.unwrap().path()).collect(); + names.sort(); + for path in names.into_iter().filter(|p| p.extension().is_some_and(|e| e == "jsonl")) { + files += 1; + for line in std::fs::read_to_string(&path).unwrap().lines().filter(|l| !l.trim().is_empty()) { + let v: Value = serde_json::from_str(line).unwrap(); + let id = v["id"].as_str().unwrap().to_string(); + let loaded = wai_ops::graph::load(&unhex(v["graph"].as_str().unwrap()), Profile::Acc64); + let load = match &loaded { + Ok(_) => "ok".to_string(), + Err(r) => r.code.as_str().to_string(), + }; + assert_eq!(load, v["load"].as_str().unwrap(), "{}: {id}: load", path.display()); + lines += 1; + let Ok(g) = loaded else { continue }; + let ds = v["decodes"].as_array().unwrap(); + for &pol in &policies { + // a sequence carries state across the line's decodes (§10.3) + let mut seq = Sequence::new(); + for (k, d) in ds.iter().enumerate() { + let inputs: Vec = d["inputs"].as_array().unwrap().iter().map(tensor).collect(); + let scalars: Vec = d["scalars"].as_array().unwrap().iter().map(int).collect(); + let got: Got = with_policy(pol, || wai_ops::run_with(&g, inputs, &scalars, &mut seq, &wai::int_ops::Host)) + .map_err(|r| r.code.as_str().to_string()); + assert!(got == stated(d), "{}: {id}: decode {k} under {pol:?}", path.display()); + decodes += 1; + } + } + } + } + } + assert!(files >= 30 && lines >= 1000, "the corpus is all there: {files} files, {lines} vectors"); + assert!(decodes > 0); +} diff --git a/wai/wai-rs/tests/int_splat_ops.rs b/wai/wai-rs/tests/int_splat_ops.rs index cec7a1654..d2b9fa76f 100644 --- a/wai/wai-rs/tests/int_splat_ops.rs +++ b/wai/wai-rs/tests/int_splat_ops.rs @@ -343,7 +343,7 @@ fn every_check_range_refuses_exactly_what_the_direct_decode_refuses() { #[test] fn every_kernel_policy_decodes_through_the_lowering_alike() { use wai::int_kernels::{with_policy, Policy}; - const POLICIES: [Policy; 5] = [Policy::reference(), Policy::fast(1), Policy::fast(2), Policy::fast(5), Policy::fast(0)]; + static POLICIES: std::sync::LazyLock> = std::sync::LazyLock::new(wai::int_kernels::equivalence_policies); let gs = model(|_| {}); let x0 = wai_ops::lower::whm1(&read(SET), true).unwrap().tensors[0].hi; let at = |t: i64| cloud(move |o| o.median_q[1] = t - ((o.offsets[1] as i64) << o.afrac)); diff --git a/wai/wai-rs/tests/int_synth_ops.rs b/wai/wai-rs/tests/int_synth_ops.rs index 5c091b6d0..d95569675 100644 --- a/wai/wai-rs/tests/int_synth_ops.rs +++ b/wai/wai-rs/tests/int_synth_ops.rs @@ -294,7 +294,7 @@ fn the_latent_range_refuses_one_past_each_end() { #[test] fn every_kernel_policy_decodes_through_the_lowering_alike() { use wai::int_kernels::{with_policy, Policy}; - const POLICIES: [Policy; 5] = [Policy::reference(), Policy::fast(1), Policy::fast(2), Policy::fast(5), Policy::fast(0)]; + static POLICIES: std::sync::LazyLock> = std::sync::LazyLock::new(wai::int_kernels::equivalence_policies); let mut cases: Vec<(String, Vec, Vec)> = json("neural-conformance/expected_hashes.json") .as_object() .unwrap() diff --git a/wai/wai-rs/tests/int_world_ops.rs b/wai/wai-rs/tests/int_world_ops.rs index 3665bf659..9d36982c6 100644 --- a/wai/wai-rs/tests/int_world_ops.rs +++ b/wai/wai-rs/tests/int_world_ops.rs @@ -559,7 +559,7 @@ fn the_new_rules_affect_no_committed_set() { #[test] fn every_kernel_policy_renders_through_the_lowering_alike() { use wai::int_kernels::{with_policy, Policy}; - const POLICIES: [Policy; 5] = [Policy::reference(), Policy::fast(1), Policy::fast(2), Policy::fast(5), Policy::fast(0)]; + static POLICIES: std::sync::LazyLock> = std::sync::LazyLock::new(wai::int_kernels::equivalence_policies); for v in vectors() { let world = int_world::ops::load(&v.t, &v.g_s, v.c).unwrap(); let (tg, gg) = int_world::ops::lower(&v.t, &v.g_s, v.c).unwrap(); diff --git a/wai/wai-rs/tests/jpegai_nets_ops.rs b/wai/wai-rs/tests/jpegai_nets_ops.rs index cb6031cd2..e6078f3d8 100644 --- a/wai/wai-rs/tests/jpegai_nets_ops.rs +++ b/wai/wai-rs/tests/jpegai_nets_ops.rs @@ -411,7 +411,7 @@ fn the_networks_keep_to_their_certificate_on_the_test_codestream() { #[test] fn every_kernel_policy_runs_the_networks_alike() { use wai::int_kernels::{with_policy, Policy}; - const POLICIES: [Policy; 5] = [Policy::reference(), Policy::fast(1), Policy::fast(2), Policy::fast(5), Policy::fast(0)]; + static POLICIES: std::sync::LazyLock> = std::sync::LazyLock::new(wai::int_kernels::equivalence_policies); let m = Members::synthetic(); let syn = jpegai_ops::load_networks_bytes(m.nets()).unwrap(); let mut s: u64 = 0x0D2C_007B; diff --git a/wai/wai-rs/tests/jpegai_ops.rs b/wai/wai-rs/tests/jpegai_ops.rs index a14f7b4b0..0e97e1147 100644 --- a/wai/wai-rs/tests/jpegai_ops.rs +++ b/wai/wai-rs/tests/jpegai_ops.rs @@ -439,7 +439,7 @@ fn a_codestream_out_of_scope_is_refused_with_its_reason() { #[test] fn every_kernel_policy_decodes_the_planes_alike() { use wai::int_kernels::{with_policy, Policy}; - const POLICIES: [Policy; 5] = [Policy::reference(), Policy::fast(1), Policy::fast(2), Policy::fast(5), Policy::fast(0)]; + static POLICIES: std::sync::LazyLock> = std::sync::LazyLock::new(wai::int_kernels::equivalence_policies); let mut sets: Vec<(&str, jpegai_ops::ScalePath, Vec)> = vec![]; let b = Bundle::synthetic(); let (n, y, uv) = b.parsed(); -- 2.54.0 (Apple Git-157) From 97b725d02e454db4e908c7c1d975002cda927853 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 20:57:41 -0400 Subject: [PATCH 072/242] wai-rs: the NEON narrow kernel's one safe-looking intrinsic call is in an unsafe block Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/int_kernels/narrow.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wai/wai-rs/src/int_kernels/narrow.rs b/wai/wai-rs/src/int_kernels/narrow.rs index c1792ba11..b7e5b25df 100644 --- a/wai/wai-rs/src/int_kernels/narrow.rs +++ b/wai/wai-rs/src/int_kernels/narrow.rs @@ -488,7 +488,8 @@ mod neon { let taps = offs.len(); let xp = x.as_ptr(); let ap = acc.as_mut_ptr(); - let shv = vdupq_n_s64(sh as i64); + // SAFETY: NEON is part of the aarch64 baseline. + let shv = unsafe { vdupq_n_s64(sh as i64) }; let mut e = 0; while e + 8 <= n { let mut t0 = 0; -- 2.54.0 (Apple Git-157) From d74fcc65596e8c03f89d76bcd7a64714f97fa042 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sat, 24 Oct 2026 21:16:25 -0400 Subject: [PATCH 073/242] wai-rs: narrow passes keep their taps cache-resident, and AVX-512 forms the inverse GDN's outputs in lanes The narrow kernels now take a block of at most 128 taps outermost and sweep the whole row under it, so one block's weights and the input lines its taps read stay in the first-level cache. On AVX-512 (now F, BW and DQ) the inverse GDN's roots are seeded from the IEEE-754 square root, corrected and held to s^2 <= n < (s+1)^2 in lanes, and (x * s) >> frac is vpmullq, exact for |x| < 2^31; a step that fails the check is left to the scalar form. The weights' largest magnitude is read in parallel too. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/int_kernels.rs | 61 ++++++++-- wai/wai-rs/src/int_kernels/narrow.rs | 25 ++-- wai/wai-rs/src/int_kernels/x86.rs | 167 ++++++++++++++++++--------- 3 files changed, 184 insertions(+), 69 deletions(-) diff --git a/wai/wai-rs/src/int_kernels.rs b/wai/wai-rs/src/int_kernels.rs index 1caffb0b3..af749b272 100644 --- a/wai/wai-rs/src/int_kernels.rs +++ b/wai/wai-rs/src/int_kernels.rs @@ -284,7 +284,7 @@ pub enum Isa { Avx2, /// [`Isa::Avx2`] with the fused multiply-accumulate of AVX-VNNI (`vpdpwssd`). Avx2Vnni, - /// The narrow kernels on AVX-512 (F and BW). + /// The narrow kernels on AVX-512 (F, BW and DQ). Avx512, /// [`Isa::Avx512`] with the fused multiply-accumulate of AVX512-VNNI. Avx512Vnni, @@ -318,13 +318,13 @@ impl Isa { #[cfg(target_arch = "x86_64")] Isa::Avx2Vnni => std::arch::is_x86_feature_detected!("avx2") && std::arch::is_x86_feature_detected!("avxvnni"), #[cfg(target_arch = "x86_64")] - Isa::Avx512 => std::arch::is_x86_feature_detected!("avx512f") && std::arch::is_x86_feature_detected!("avx512bw"), - #[cfg(target_arch = "x86_64")] - Isa::Avx512Vnni => { + Isa::Avx512 => { std::arch::is_x86_feature_detected!("avx512f") && std::arch::is_x86_feature_detected!("avx512bw") - && std::arch::is_x86_feature_detected!("avx512vnni") + && std::arch::is_x86_feature_detected!("avx512dq") } + #[cfg(target_arch = "x86_64")] + Isa::Avx512Vnni => Isa::Avx512.supported() && std::arch::is_x86_feature_detected!("avx512vnni"), #[cfg(not(target_arch = "x86_64"))] Isa::Avx2 | Isa::Avx2Vnni | Isa::Avx512 | Isa::Avx512Vnni => false, } @@ -1411,7 +1411,7 @@ where let x = &x[..g.cin * g.h * g.w]; let wt = &wt[..g.cin * (g.cout / g.groups) * g.kh * g.kw]; let threads = threads_for(g.work()); - let (xm, wm) = (max_abs_par(x, threads), max_abs(wt)); + let (xm, wm) = (max_abs_par(x, threads), max_abs_par(wt, threads)); if sum_bound(g, xm, wm) > i64::MAX as u128 { return None; } @@ -1438,7 +1438,7 @@ where let x = &x[..g.cin * g.h * g.w]; let wt = &wt[..g.cin * (g.cout / g.groups) * g.kh * g.kw]; let threads = threads_for(g.work()); - let (xm, wm) = (max_abs_par(x, threads), max_abs(wt)); + let (xm, wm) = (max_abs_par(x, threads), max_abs_par(wt, threads)); let bound = sum_bound(g, xm, wm); if bound > i128::MAX as u128 { return None; @@ -1593,9 +1593,24 @@ fn igdn_tile_narrow( // The narrow kernel sets seed each root from the IEEE-754 square root ([`isqrt_u64`]); // the wide set keeps the integer routine. Both give the integer root. let seeded = isa != Isa::Wide; + // On AVX-512 the roots and the products run in vector lanes ([`x86::igdn_emit_avx512`]) + // where every |x| < 2^31; the rest of each channel's positions here. + #[cfg(target_arch = "x86_64")] + let lanes = narrow_out && matches!(isa, Isa::Avx512 | Isa::Avx512Vnni); let emit = |d: &mut [i64], i: usize, norm: &[i64]| { let xi = &x[i * hw + p0..i * hw + p0 + np]; - for ((dv, &nv), &xv) in d.iter_mut().zip(norm).zip(xi) { + #[cfg(target_arch = "x86_64")] + let done = if lanes { + // SAFETY: the set is one the processor runs (`Isa::effective`, which requires + // F, BW and DQ for it); norm, xi and d hold np values; narrow_out is + // |x| ≤ i32::MAX; frac ≤ 62 (checked by `igdn`). + unsafe { x86::igdn_emit_avx512(norm, xi, frac, np, d) } + } else { + 0 + }; + #[cfg(not(target_arch = "x86_64"))] + let done = 0; + for ((dv, &nv), &xv) in d.iter_mut().zip(norm).zip(xi).skip(done) { let n = nv.max(0) as u64; let s = if seeded { isqrt_u64(n) } else { n.isqrt() } as i64; *dv = igdn_out(xv, s, frac, narrow_out); @@ -2344,6 +2359,36 @@ mod tests { } } + /// The AVX-512 roots and products of the inverse GDN against the scalar form, on norms + /// of every magnitude to `i64::MAX` (negative ones clamp to 0), every perfect square + /// and its neighbours near the powers of two, and inputs to ±(2^31 − 1). + #[cfg(target_arch = "x86_64")] + #[test] + fn the_avx512_roots_and_products_are_the_scalar_ones() { + if !Isa::Avx512.supported() { + return; + } + let mut r = testkit::Rng(0x5157_0002); + let mut norm: Vec = (0..20_000).map(|_| (r.next() >> r.below(64)) as i64).collect(); + for e in 0..32u32 { + for d in [-1i64, 0, 1] { + let s = (1i64 << e) + d; + norm.extend([s * s - 1, s * s, s * s + 1]); + } + } + norm.extend([i64::MAX, i64::MAX - 1, -1, i64::MIN, 0, 1 << 53, (1 << 53) + 1, (1 << 53) - 1]); + let n = norm.len(); + let x: Vec = (0..n).map(|i| if i % 7 == 0 { [i32::MAX as i64, -(i32::MAX as i64), 0][i % 3] } else { r.signed(i32::MAX as i64) }).collect(); + for frac in [0u32, 12, 31, 62] { + let want: Vec = norm.iter().zip(&x).map(|(&nv, &xv)| (xv * (nv.max(0) as u64).isqrt() as i64) >> frac).collect(); + let mut got = vec![0i64; n]; + // SAFETY: AVX-512 F, BW and DQ are present (checked above); the slices hold n. + let done = unsafe { x86::igdn_emit_avx512(&norm, &x, frac, n, &mut got) }; + assert_eq!(done, n - n % 8, "every whole step of 8 is the vector form's"); + assert_eq!(&got[..done], &want[..done], "frac {frac}"); + } + } + #[test] fn an_explicit_worker_count_of_any_size_gives_the_same_integers() { // `Policy::threads` is caller-chosen; the fast path's own arithmetic on it diff --git a/wai/wai-rs/src/int_kernels/narrow.rs b/wai/wai-rs/src/int_kernels/narrow.rs index b7e5b25df..fc5d7a57f 100644 --- a/wai/wai-rs/src/int_kernels/narrow.rs +++ b/wai/wai-rs/src/int_kernels/narrow.rs @@ -24,6 +24,11 @@ use super::{par_for_each, weight_at, ConvGeom, Isa, Layout, Tile}; +/// Taps one pass over a row takes at most (fewer where the plan's block is shorter): the +/// kernels sweep the row's elements once per pass, so its weights and the input lines its +/// taps read stay in the first-level cache across the sweep. +pub(super) const TAPS_PER_PASS: usize = 128; + /// The largest operand magnitude a 16-bit lane takes here. With `|w| ≤ 2^15 − 1`, a sum /// of two products of 16-bit operands stays below `2^31` (only `(−2^15)·(−2^15)` twice /// reaches it). @@ -490,11 +495,16 @@ mod neon { let ap = acc.as_mut_ptr(); // SAFETY: NEON is part of the aarch64 baseline. let shv = unsafe { vdupq_n_s64(sh as i64) }; - let mut e = 0; - while e + 8 <= n { - let mut t0 = 0; - while t0 < taps { - let t1 = t0 + kblk.min(taps - t0); + // Tap blocks outermost, so that one block's weights and the input lines its taps + // read stay in the first-level cache while every 8-element step of the row + // passes over them. + let blk = kblk.min(super::TAPS_PER_PASS); + let n8 = n - n % 8; + let mut t0 = 0; + while t0 < taps { + let t1 = t0 + blk.min(taps - t0); + let mut e = 0; + while e < n8 { // SAFETY (all raw accesses below): each load reads `x[offs_t + base + e .. // + 8]` with `e + 8 ≤ n`, in bounds by the caller's contract; each weight // load reads `ws[8t .. 8t + 8]`; each store writes `acc[j·n + e .. + 8]`. @@ -518,10 +528,11 @@ mod neon { } } } - t0 = t1; + e += 8; } - e += 8; + t0 = t1; } + let e = n8; super::tail_single::(ws, offs, x, base, e, n, sh, acc); } } diff --git a/wai/wai-rs/src/int_kernels/x86.rs b/wai/wai-rs/src/int_kernels/x86.rs index 53dae9c5c..fb68f103c 100644 --- a/wai/wai-rs/src/int_kernels/x86.rs +++ b/wai/wai-rs/src/int_kernels/x86.rs @@ -17,7 +17,9 @@ //! shifted right arithmetically by `gf` (`vpsraq` on AVX-512; on AVX2, which has no //! 64-bit arithmetic shift, a logical shift where every product is non-negative, else //! the logical shift with the sign bits put back) and added into the norm, which the -//! caller has proved cannot leave `i64`. +//! caller has proved cannot leave `i64`. On AVX-512 (with DQ) the outputs are formed in +//! lanes too: each root seeded from the IEEE-754 square root, corrected and verified in +//! integers, then `(x · root) >> frac` by `vpmullq`, exact for `|x| < 2^31`. use std::arch::x86_64::*; @@ -157,42 +159,49 @@ unsafe fn body256(ws: &[i32], offs: &[usize], x: &[u // writes `acc[j·n + e .. + 8·V]`, both within their slices. unsafe { let cnt = _mm_cvtsi32_si128(sh as i32); - let mut e = 0; - macro_rules! step { - ($v:literal) => { - while e + 8 * $v <= n { - let mut t0 = 0; - while t0 < taps { - let t1 = t0 + kblk.min(taps - t0); - let mut a = [[_mm256_setzero_si256(); $v]; NB]; - for t in t0..t1 { - let p = xp.add(*offs.get_unchecked(t) + base + e); - let mut xv = [_mm256_setzero_si256(); $v]; - for (k, v) in xv.iter_mut().enumerate() { - *v = _mm256_loadu_si256(p.add(8 * k) as *const __m256i); - } - let w = ws.as_ptr().add(t * NB); - for (j, aj) in a.iter_mut().enumerate() { - let wj = _mm256_set1_epi32(*w.add(j)); - for (av, &xk) in aj.iter_mut().zip(&xv) { - *av = D::dot(*av, xk, wj); - } + // Tap blocks outermost (see `narrow::TAPS_PER_PASS`); in each, the row in steps of + // 16 elements, then one of 8; the last `n mod 8` in scalar arithmetic after. + let blk = kblk.min(super::narrow::TAPS_PER_PASS); + let (n16, n8) = (n - n % 16, n - n % 8); + let mut t0 = 0; + while t0 < taps { + let t1 = t0 + blk.min(taps - t0); + macro_rules! step { + ($v:literal, $e:expr) => {{ + let e = $e; + let mut a = [[_mm256_setzero_si256(); $v]; NB]; + for t in t0..t1 { + let p = xp.add(*offs.get_unchecked(t) + base + e); + let mut xv = [_mm256_setzero_si256(); $v]; + for (k, v) in xv.iter_mut().enumerate() { + *v = _mm256_loadu_si256(p.add(8 * k) as *const __m256i); + } + let w = ws.as_ptr().add(t * NB); + for (j, aj) in a.iter_mut().enumerate() { + let wj = _mm256_set1_epi32(*w.add(j)); + for (av, &xk) in aj.iter_mut().zip(&xv) { + *av = D::dot(*av, xk, wj); } } - for (j, aj) in a.iter().enumerate() { - for (k, &av) in aj.iter().enumerate() { - flush256(av, cnt, ap.add(j * n + e + 8 * k)); - } - } - t0 = t1; } - e += 8 * $v; - } - }; + for (j, aj) in a.iter().enumerate() { + for (k, &av) in aj.iter().enumerate() { + flush256(av, cnt, ap.add(j * n + e + 8 * k)); + } + } + }}; + } + let mut e = 0; + while e < n16 { + step!(2, e); + e += 16; + } + if n8 > n16 { + step!(1, n16); + } + t0 = t1; } - step!(2); - step!(1); - super::narrow::tail_pairs::(ws, offs, x, base, e, n, sh, acc); + super::narrow::tail_pairs::(ws, offs, x, base, n8, n, sh, acc); } } @@ -224,13 +233,16 @@ unsafe fn body512(ws: &[i32], offs: &[usize], x: &[u // of [`sums`]; reads `ws[t·NB + j]`; writes `acc[j·n + e + i]` only for `e + i < n`. unsafe { let cnt = _mm_cvtsi32_si128(sh as i32); - let mut e = 0; - macro_rules! step { - ($v:literal, $mask:expr) => {{ - let m: u16 = $mask; - let mut t0 = 0; - while t0 < taps { - let t1 = t0 + kblk.min(taps - t0); + // Tap blocks outermost (see `narrow::TAPS_PER_PASS`); in each, the row in steps of + // 32 elements, then one of 16, then the last `n mod 16` under a lane mask. + let blk = kblk.min(super::narrow::TAPS_PER_PASS); + let (n32, n16) = (n - n % 32, n - n % 16); + let mut t0 = 0; + while t0 < taps { + let t1 = t0 + blk.min(taps - t0); + macro_rules! step { + ($v:literal, $e:expr, $mask:expr) => {{ + let (e, m): (usize, u16) = ($e, $mask); let mut a = [[_mm512_setzero_si512(); $v]; NB]; for t in t0..t1 { let p = xp.add(*offs.get_unchecked(t) + base + e); @@ -251,20 +263,20 @@ unsafe fn body512(ws: &[i32], offs: &[usize], x: &[u flush512(av, cnt, m, ap.add(j * n + e + 16 * k)); } } - t0 = t1; - } - }}; - } - while e + 32 <= n { - step!(2, u16::MAX); - e += 32; - } - if e + 16 <= n { - step!(1, u16::MAX); - e += 16; - } - if e < n { - step!(1, ((1u32 << (n - e)) - 1) as u16); + }}; + } + let mut e = 0; + while e < n32 { + step!(2, e, u16::MAX); + e += 32; + } + if n16 > n32 { + step!(1, n32, u16::MAX); + } + if n > n16 { + step!(1, n16, ((1u32 << (n - n16)) - 1) as u16); + } + t0 = t1; } } } @@ -394,3 +406,50 @@ unsafe fn igdn_avx2(x2: &[i32], np: usize, gt: &[i32], beta: } p } + +/// The inverse GDN's outputs from its norms on AVX-512 (F and DQ): `dst[p] = (x[p] · +/// ⌊√max(0, norm[p])⌋) >> frac` for `p` in a leading multiple of 8 (returned). Each root is +/// seeded from the IEEE-754 square root and corrected, then held to `s² ≤ n < (s + 1)²` in +/// integers; a step whose roots do not all meet it is left to the caller (none is +/// expected). The product is `vpmullq`, exact because every `|x| < 2^31` and `s < 2^32`. +/// +/// # Safety +/// The processor runs AVX-512 F and DQ; `norm`, `x` and `dst` hold at least `n` values; +/// every `|x| ≤ i32::MAX`; `frac ≤ 63`. +#[target_feature(enable = "avx512f,avx512dq")] +pub(super) unsafe fn igdn_emit_avx512(norm: &[i64], x: &[i64], frac: u32, n: usize, dst: &mut [i64]) -> usize { + let cnt = _mm_cvtsi32_si128(frac as i32); + let one = _mm512_set1_epi64(1); + let cap = _mm512_set1_epi64(u32::MAX as i64); + let zero = _mm512_setzero_si512(); + let mut p = 0; + // SAFETY (every raw access): reads `norm[p .. p + 8]` and `x[p .. p + 8]` and writes + // `dst[p .. p + 8]` with `p + 8 ≤ n`, within the slices by the contract. + unsafe { + while p + 8 <= n { + let nv = _mm512_max_epi64(_mm512_loadu_si512(norm.as_ptr().add(p) as *const __m512i), zero); + let mut s = _mm512_min_epu64(_mm512_cvttpd_epu64(_mm512_sqrt_pd(_mm512_cvtepi64_pd(nv))), cap); + for _ in 0..2 { + let over = _mm512_cmpgt_epu64_mask(_mm512_mullo_epi64(s, s), nv); + s = _mm512_mask_sub_epi64(s, over, s, one); + } + for _ in 0..2 { + let s1 = _mm512_add_epi64(s, one); + let room = _mm512_cmplt_epu64_mask(s, cap); + let under = _mm512_mask_cmple_epu64_mask(room, _mm512_mullo_epi64(s1, s1), nv); + s = _mm512_mask_add_epi64(s, under, s, one); + } + // s² ≤ n, and (s + 1)² > n wherever s + 1 has a square in u64 + let s1 = _mm512_add_epi64(s, one); + let low = _mm512_cmple_epu64_mask(_mm512_mullo_epi64(s, s), nv); + let high = _mm512_cmpgt_epu64_mask(_mm512_mullo_epi64(s1, s1), nv) | !_mm512_cmplt_epu64_mask(s, cap); + if low & high != 0xff { + break; + } + let xv = _mm512_loadu_si512(x.as_ptr().add(p) as *const __m512i); + _mm512_storeu_si512(dst.as_mut_ptr().add(p) as *mut __m512i, _mm512_sra_epi64(_mm512_mullo_epi64(xv, s), cnt)); + p += 8; + } + } + p +} -- 2.54.0 (Apple Git-157) From bdf83bf5a281b4ca0b9e8880428388d2028b3678 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 25 Oct 2026 03:56:40 -0400 Subject: [PATCH 074/242] sink-energy: the measured decode table of the 20-core Linux reference sink Taken by wai_sink_table on the host once it had been idle for a minute: the package counter live, every row a figure. Co-Authored-By: Claude Opus 5.5 --- .../tables/linux-x86_64-20-core/procedure.txt | 18 + .../tables/linux-x86_64-20-core/readings.json | 1 + .../tables/linux-x86_64-20-core/table.json | 2529 +++++++++++++++++ 3 files changed, 2548 insertions(+) create mode 100644 wai/live-conformance/sink-energy/tables/linux-x86_64-20-core/procedure.txt create mode 100644 wai/live-conformance/sink-energy/tables/linux-x86_64-20-core/readings.json create mode 100644 wai/live-conformance/sink-energy/tables/linux-x86_64-20-core/table.json diff --git a/wai/live-conformance/sink-energy/tables/linux-x86_64-20-core/procedure.txt b/wai/live-conformance/sink-energy/tables/linux-x86_64-20-core/procedure.txt new file mode 100644 index 000000000..428d37eee --- /dev/null +++ b/wai/live-conformance/sink-energy/tables/linux-x86_64-20-core/procedure.txt @@ -0,0 +1,18 @@ +wai-sink-table/1 (extensions/sink-energy.md §4.4), as this run carried it out. + +command: /home/dcharlot/ena-target/release/wai_sink_table run --out /home/dcharlot/ena-out --rounds 7 +sampling interval: 200000 us; idle bracket 10 intervals; busy bracket 14 intervals; rounds 7; workers 8, released together from a start barrier +order: every case once per round, the order rotated by one each round; 2 s of settling before each idle bracket +build: release rustc 1.98.0 (88d9e12ae 2026-08-18) x86_64-unknown-linux-gnu +machine: linux 7.0.0-38-generic, x86_64 processor, 20 logical cores; release build; load averages at the end Some(("4.57", "3.80", "6.70")) +survey: + linux-nvml not-implemented: nvmlDeviceGetTotalEnergyConsumption, device 0; counter rate and power reading per 1 s interval (mW): 23326/10379 13408/10409 15528/10410 17504/10408 19593/10397 + linux-rapl live: powercap energy_uj: /sys/class/powercap/intel-rapl:0 +work items (a completion is one decode of the whole item; its output was checked once before the rounds): + wai.video.int_motion software [builtin:wai@0.3.0] 8 frame per completion at 96x96: tests/vectors/int_video/video_real_bitstream.json sha256 8e05b13cf7a3f4910f41feb8f12efc8bb32c44a80dbfdaa2b312c850db042b75 (8 frames 96x96); decodes + wai.neural.int_hyper software [builtin:wai@0.3.0] 1 image per completion at 16x16: tests/vectors/int_hyper tiny conditional codec, seeded random weights (16x16), bitstream sha256 11c4637ce1038584a47d7bbc2d0515d48518292189e42070c6ad029b0947efbb; decodes + wai.neural.int_synth software [builtin:wai@0.3.0] 1 image per completion at 128x128: neural-conformance/vectors/photo latent.bin sha256 dd430da21fb066174f02dde9fd45902f9388327fbfccc489efa530ac0f65a5e0 (128x128); decodes + wai.audio.int_codec software [builtin:wai@0.3.0] 512 content-ms per completion: tests/vectors/int_audio (8192 samples at 16000 Hz), bitstream sha256 4cba3bb4edf1d08a5cb529187dec566d440ed120c2efe1369dcbf4712e2a1840; output hash e5ff66ad1b86117c as committed + wai.world.model software [builtin:wai@0.3.0] 9 frame per completion at 64x64: tests/vectors/int_world (9 frames 64x64); clip hash 2072048504126404994 +note: platform media tool: none on this sink; no hardware decode path is reachable from the procedure +rows absent for no_decoder: the registered payload capabilities this build (neural_int, video_int, audio_int, world_int) does not decode; no_work_item: those it decodes and this procedure has no item for diff --git a/wai/live-conformance/sink-energy/tables/linux-x86_64-20-core/readings.json b/wai/live-conformance/sink-energy/tables/linux-x86_64-20-core/readings.json new file mode 100644 index 000000000..0c5f9cf98 --- /dev/null +++ b/wai/live-conformance/sink-energy/tables/linux-x86_64-20-core/readings.json @@ -0,0 +1 @@ +{"rows":[{"capability":"wai.audio.aac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.ac4","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.avs3","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.flac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.iamf","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.int_codec","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[20474863000,200112],[18228530000,200124],[15958028000,200126],[15702413000,200118],[18438796000,200120],[14658531000,200065],[14728783000,200125],[14502649000,200120],[15241660000,200087],[15034324000,200126],[14943871000,200116],[15325766000,200134],[15102623000,200128],[14476220000,200126]],"completions":4341,"idle":[[686278000,200258],[565672000,200185],[555113000,200235],[574095000,200223],[535155000,200243],[556762000,200223],[566221000,200215],[531920000,200212],[550536000,200237],[614439000,200234]]},{"busy":[[20393441000,200160],[16985735000,200131],[15793356000,200123],[15192283000,200115],[14224024000,200128],[14571679000,200131],[14253076000,200129],[14458642000,200124],[13933375000,200113],[14107813000,200119],[14682763000,200118],[14514062000,200079],[15007347000,200122],[13823695000,200135]],"completions":4276,"idle":[[1131833000,200375],[1155637000,200380],[1069944000,200413],[1484493000,200417],[1135617000,200375],[979917000,200416],[1004636000,200379],[996763000,200394],[986020000,200421],[1256711000,200384]]},{"busy":[[20387582000,200110],[17225420000,200122],[15950521000,200157],[14370202000,200125],[14607507000,200174],[13529689000,200109],[14096400000,200104],[14366601000,200128],[14251917000,200123],[14251916000,200122],[13861293000,200092],[14244593000,200117],[13936304000,200113],[13886500000,200118]],"completions":4296,"idle":[[582152000,200181],[573363000,200182],[555907000,200178],[703062000,200198],[572142000,200175],[560790000,200173],[656553000,200168],[540038000,200178],[566221000,200174],[536986000,200179]]},{"busy":[[19491467000,200105],[17162859000,200186],[14926963000,200126],[14397485000,200126],[14549096000,200122],[13862148000,200159],[14574669000,200080],[13830714000,200125],[14319727000,200135],[13929713000,200120],[13979883000,200205],[14586145000,200126],[13351650000,200137],[13216763000,200125]],"completions":4161,"idle":[[695677000,200384],[581725000,200407],[591368000,200386],[521361000,200390],[533141000,200379],[547606000,200396],[534911000,200398],[588438000,200341],[559813000,200394],[558714000,200339]]},{"busy":[[20245859000,200149],[17688309000,200127],[15210167000,200121],[14232019000,200129],[14153467000,200084],[14717492000,200119],[14247095000,200124],[14222375000,200123],[13714443000,200163],[14107569000,200120],[14175500000,200114],[13307950000,200131],[13799159000,200122],[13885584000,200131]],"completions":4279,"idle":[[644651000,200317],[547850000,200276],[544250000,200318],[692381000,200306],[545408000,200316],[547912000,200311],[542662000,200200],[562559000,200315],[577025000,200326],[561949000,200334]]},{"busy":[[18466627000,200123],[16185078000,200121],[15895407000,200120],[14584984000,200113],[14447656000,200118],[13813563000,200117],[14809472000,200123],[13404812000,200124],[14228662000,200116],[12908109000,200123],[13772243000,200122],[14109460000,200121],[12981229000,200148],[13754421000,200112]],"completions":4184,"idle":[[1015805000,200225],[1017271000,200226],[1060117000,200222],[1026364000,200241],[1132199000,200134],[1010984000,200216],[1124204000,200229],[1234921000,200229],[1040097000,200097],[1128782000,200222]]},{"busy":[[19743724000,200120],[16916949000,200119],[15182761000,200111],[14605981000,200124],[15646688000,200123],[14692101000,200155],[14376367000,200117],[13983546000,200122],[13964259000,200117],[13975062000,200121],[13684901000,200134],[13200162000,200124],[13585841000,200118],[14219385000,200131]],"completions":4277,"idle":[[676634000,200237],[593748000,200228],[623839000,200236],[554625000,200192],[568236000,200231],[585447000,200224],[546569000,200230],[558226000,200196],[552489000,200218],[563719000,200216]]}]},{"capability":"wai.audio.ivas","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.mpegh","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.opus","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.opus.packet","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.scene","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.audio.score","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.avatar.pose","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.feed.state","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.film.linear","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.film.reel","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.generate.spectral","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.haptic.mihs","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.haptic.signal","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.avif","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jp2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jpeg","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jpegai","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.jxl","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.image.png","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.mesh.draco","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.mesh.glb","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.bmshj2018","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.cmic","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dcvc_fm","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.dcvc_rt","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.elic","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.encodec32","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.glc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.int_hyper","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[19944407000,200081],[19005810000,200073],[17514726000,200070],[16409748000,200070],[16124226000,200071],[15898458000,200070],[15783834000,200116],[15834066000,200133],[15441794000,200113],[15360862000,200130],[15333701000,200118],[15252524000,200120],[15076683000,200121],[15039268000,200082]],"completions":252270,"idle":[[1147947000,200303],[1179501000,200341],[1152280000,200323],[1131161000,200319],[1131223000,200320],[1135861000,200314],[1126768000,200307],[1249691000,200313],[1150144000,200317],[1119443000,200319]]},{"busy":[[19904918000,200110],[17502946000,200076],[15619528000,200071],[14729027000,200071],[14605858000,200070],[14294031000,200074],[14275110000,200128],[14206202000,200129],[13996851000,200116],[14029749000,200080],[13895228000,200078],[13880092000,200119],[13964319000,200114],[13851405000,200118]],"completions":245626,"idle":[[609251000,200324],[713804000,200318],[550841000,200316],[563048000,200312],[553343000,200316],[536925000,200315],[559508000,200316],[528868000,200305],[556823000,200306],[562620000,200309]]},{"busy":[[17271318000,200124],[16990069000,200117],[15344626000,200125],[14678491000,200116],[14338952000,200118],[14145289000,200121],[13957728000,200222],[14006067000,200135],[13698452000,200123],[13721156000,200117],[13579372000,200115],[13618617000,200127],[13481655000,200122],[13540615000,200120]],"completions":241918,"idle":[[978330000,200401],[1037595000,200382],[946226000,200391],[1144711000,200426],[1167234000,200455],[983274000,200406],[984189000,200415],[992673000,200411],[990293000,200409],[994382000,200317]]},{"busy":[[19903697000,200105],[16661701000,200122],[14959434000,200119],[14277368000,200072],[14073634000,200121],[13995874000,200116],[13964747000,200115],[13916102000,200124],[13893764000,200088],[13737635000,200122],[13735805000,200081],[13627162000,200114],[13659023000,200121],[13630336000,200121]],"completions":243771,"idle":[[1076902000,200388],[1019895000,200400],[978513000,200413],[1068967000,200399],[1032651000,200396],[976865000,200399],[1048276000,200400],[985044000,200245],[1297116000,200370],[1007077000,200380]]},{"busy":[[17228350000,200180],[16917010000,200136],[15001304000,200248],[14286340000,200133],[13911403000,200083],[13463466000,200114],[13828944000,200174],[13646450000,200115],[12985684000,200124],[12869840000,200090],[12894865000,200115],[13756495000,200084],[13495327000,200123],[13187711000,200121]],"completions":234093,"idle":[[556090000,200370],[651548000,200374],[553405000,200377],[616575000,200368],[546081000,200371],[553709000,200384],[550047000,200379],[547973000,200365],[555540000,200369],[537230000,200375]]},{"busy":[[18238662000,200146],[15604513000,200080],[14561364000,200108],[13384487000,200167],[13103849000,200120],[13147854000,200135],[13140713000,200081],[13101102000,200118],[13022977000,200119],[12989469000,200120],[13045804000,200120],[12933316000,200081],[12960050000,200113],[12902616000,200078]],"completions":238752,"idle":[[1217953000,200395],[1181882000,200393],[1178708000,200389],[1162778000,200389],[1194882000,200413],[1270016000,200309],[1286374000,200387],[3943166000,200106],[882139000,200244],[665831000,200418]]},{"busy":[[18953687000,200133],[16013509000,200115],[14485314000,200115],[13919581000,200118],[13652248000,200117],[13513943000,200125],[13513515000,200123],[13427395000,200120],[13371425000,200134],[13364224000,200115],[13347622000,200125],[13355068000,200118],[13318143000,200116],[13247891000,200120]],"completions":241401,"idle":[[1160947000,200377],[1055600000,200392],[1045774000,200387],[1062375000,200334],[1042722000,200354],[1049680000,200388],[1043576000,200392],[995908000,200397],[1190183000,200227],[1284237000,200402]]}]},{"capability":"wai.neural.int_mlicpp","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[]},{"capability":"wai.neural.int_synth","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[20187265000,200672],[18990064000,200116],[17274248000,200136],[17173051000,200556],[16491718000,200256],[16250813000,200139],[16470722000,200126],[15787924000,200126],[16030110000,200189],[15826131000,200130],[15300376000,200129],[15718771000,200115],[15451316000,200155],[16117390000,200120]],"completions":501,"idle":[[643370000,200255],[540587000,200387],[557677000,200387],[653013000,200381],[555663000,200394],[535582000,200383],[569823000,200388],[549803000,200381],[562254000,200396],[578429000,200399]]},{"busy":[[20615547000,200149],[18171829000,200301],[16947894000,200122],[16421039000,200122],[15960531000,200107],[14937766000,200144],[15172447000,200103],[15349448000,200149],[15404318000,200119],[14698388000,200118],[15119468000,200669],[14803673000,200113],[14692590000,200982],[15546408000,200116]],"completions":498,"idle":[[557799000,200345],[540892000,200365],[554076000,200358],[592894000,200376],[614501000,200243],[554319000,200375],[559874000,200336],[593200000,200241],[553526000,200293],[555663000,200376]]},{"busy":[[20621834000,200095],[17404253000,200625],[16183125000,200133],[15940083000,200100],[14963219000,200408],[15303855000,200142],[14364953000,200126],[14961571000,200135],[14701073000,200182],[14532495000,200138],[14352441000,200647],[14824913000,200139],[14497461000,200137],[14559044000,200395]],"completions":488,"idle":[[649108000,200200],[559202000,200232],[636107000,200157],[550536000,200227],[540953000,200236],[610655000,200224],[546447000,200228],[539793000,200232],[537902000,200235],[567381000,200241]]},{"busy":[[20050669000,200285],[16797504000,200166],[15283041000,200214],[15560935000,200154],[15613180000,200139],[14915611000,200131],[14289820000,200190],[15248740000,200298],[14259729000,200141],[14282861000,200155],[14417749000,200173],[14749291000,200146],[14451928000,200130],[13972132000,200756]],"completions":482,"idle":[[590209000,200375],[589354000,200315],[549498000,200366],[553038000,200293],[704832000,200394],[547911000,200377],[573485000,200370],[579589000,200369],[565794000,200231],[609129000,200368]]},{"busy":[[20638863000,200724],[16435200000,200115],[15944722000,200118],[14513818000,200147],[15114708000,200151],[15101707000,200151],[14605919000,200177],[14861901000,200262],[14573937000,200171],[14422265000,200144],[13962123000,200156],[14447534000,200111],[13691371000,200180],[14320581000,200114]],"completions":488,"idle":[[545958000,200232],[552855000,200231],[559141000,200189],[556273000,200194],[583861000,200194],[544371000,200226],[572570000,200233],[766965000,200187],[573058000,200207],[558409000,200231]]},{"busy":[[20318002000,200119],[17660966000,201149],[15967183000,200141],[14746422000,200503],[14340845000,200619],[14507959000,200366],[14608055000,200117],[14947899000,201089],[14317590000,200124],[14276575000,200141],[14335108000,200362],[15509176000,200192],[14610681000,200113],[13829981000,200451]],"completions":482,"idle":[[589659000,200376],[681334000,200364],[562865000,200386],[679746000,200376],[558104000,200380],[553527000,200269],[578306000,200364],[553100000,200314],[609068000,200384],[604124000,200407]]},{"busy":[[20672188000,200118],[16261128000,200131],[15490195000,200987],[14575219000,200170],[14537011000,200146],[14401635000,200145],[13811916000,200114],[14519127000,200116],[13806728000,200360],[14610802000,200121],[14023707000,200113],[13898524000,200138],[13825953000,200112],[14183619000,200176]],"completions":483,"idle":[[1262997000,200421],[1310910000,200403],[1306332000,200422],[1237545000,200403],[1444638000,200314],[1228878000,200408],[1195981000,200409],[1200192000,200415],[1169004000,200403],[1194516000,200408]]}]},{"capability":"wai.neural.mbt2018_mean","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mimi","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mlicpp","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.mlicv2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.snac","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.video_bmshj2018","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.neural.wavtokenizer","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.pc.gpcc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.pc.vpcc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.prior.nnc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.prior.set","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.quantum.circuit","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.quantum.circuit2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.semantic.clip","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.semantic.perco2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.4d","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.int_codec","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[]},{"capability":"wai.splat.sog","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.splat.spz","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.tensor.nnc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.ternary.tt","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.text.xz","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.text.zstd","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.av1","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.av1.lossless","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.av2","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.avc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.avs3","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.evc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.evc.baseline","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.hevc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.int_hyper","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[]},{"capability":"wai.video.int_motion","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[19560375000,200094],[19039502000,200083],[19016186000,200071],[17490190000,200079],[16924395000,200071],[16553730000,200080],[16390766000,200071],[16256123000,200069],[15858663000,200070],[15903218000,200069],[15774984000,200068],[16426655000,200067],[16188252000,200069],[15691549000,200070]],"completions":8390,"idle":[[984128000,200185],[1057920000,200171],[967160000,200179],[2019160000,200153],[922666000,200172],[920835000,200177],[1067869000,200177],[987912000,200182],[1348141000,200120],[1701229000,200150]]},{"busy":[[15616903000,200245],[14433923000,200154],[15916646000,200150],[15108787000,200115],[14581262000,200092],[13883936000,200090],[13590847000,200100],[13556850000,200121],[13491665000,200125],[13448268000,200079],[13349026000,200121],[13233060000,200120],[13297085000,200120],[13314724000,200118]],"completions":7807,"idle":[[970030000,200398],[1086850000,200386],[993405000,200353],[977110000,200396],[1111813000,200233],[1050535000,200255],[980161000,200232],[1023435000,200301],[949948000,200386],[964720000,200390]]},{"busy":[[18763684000,200171],[16469258000,200131],[15661947000,200125],[14871239000,200118],[14542687000,200094],[14383630000,200042],[14316553000,200100],[12776456000,200118],[13446133000,200085],[13418423000,200121],[13156582000,200119],[13336087000,200121],[13177334000,200117],[13181851000,200119]],"completions":7913,"idle":[[690977000,200069],[546386000,200069],[572447000,200109],[589293000,200184],[593382000,200177],[1927241000,200172],[714354000,200156],[595701000,200068],[727599000,200069],[549315000,200070]]},{"busy":[[19153150000,200111],[15557516000,200113],[14501794000,200117],[14706871000,200091],[14022548000,200125],[12965970000,200093],[13859217000,200159],[13693569000,200140],[12772306000,200117],[12857084000,200116],[12993619000,200134],[14016443000,200129],[13312710000,200092],[12648711000,200118]],"completions":7874,"idle":[[562803000,200222],[576598000,200219],[997617000,200232],[552184000,200229],[655699000,200224],[552061000,200224],[673094000,200228],[546874000,200217],[552061000,200231],[556334000,200214]]},{"busy":[[18853834000,200131],[17259355000,200082],[15771566000,200117],[15094261000,200132],[14288660000,200120],[14691124000,200120],[14471338000,200116],[14391076000,200123],[14258875000,200114],[14259851000,200085],[14170008000,200120],[14169458000,200083],[14040369000,200116],[14105616000,200115]],"completions":8068,"idle":[[563658000,200238],[564513000,200175],[604246000,200205],[573119000,200240],[573424000,200230],[782652000,200148],[621763000,200232],[564879000,200232],[551940000,200246],[545469000,200241]]},{"busy":[[18041580000,200094],[15664205000,200094],[14238794000,200114],[13583523000,200117],[13430202000,200118],[13383816000,200037],[13150662000,200131],[13137845000,200081],[12262908000,200128],[12585905000,200126],[12655974000,200076],[13402492000,200145],[13030240000,200114],[12928373000,200121]],"completions":7840,"idle":[[1225766000,200393],[1225705000,200359],[1204098000,200403],[1172544000,200400],[1173581000,200388],[1203549000,200397],[1199399000,200413],[1187985000,200408],[1183652000,200369],[1264950000,200392]]},{"busy":[[19044263000,200117],[15469809000,200100],[15288047000,200123],[14482751000,200083],[14447534000,200121],[13830042000,200118],[13880153000,200220],[13798548000,200230],[13758937000,200122],[12839506000,200096],[12520659000,200125],[12518523000,200121],[12623258000,200217],[13749111000,200084]],"completions":7889,"idle":[[574889000,200310],[554320000,200315],[612425000,200303],[558226000,200311],[605711000,200305],[1195920000,200189],[1092893000,200184],[1111691000,200177],[1009824000,200178],[1035459000,200181]]}]},{"capability":"wai.video.j2k","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.vp8","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.vp9","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.video.vvc","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.live","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.model","decoder":"builtin:wai@0.3.0","path":"software","profile":"linux-rapl","rounds":[{"busy":[[20328867000,200197],[17560196000,200122],[16054708000,200119],[15333334000,200107],[15615378000,200220],[15520895000,200113],[15795187000,200103],[14537072000,200113],[14619531000,200125],[14186365000,200120],[15093894000,200115],[14427392000,200107],[14104884000,200122],[14328149000,200135]],"completions":893,"idle":[[554381000,200390],[555113000,200369],[554076000,200389],[534483000,200384],[530272000,200388],[659789000,200392],[528441000,200380],[522459000,200399],[519286000,200391],[544310000,200415]]},{"busy":[[19573681000,200122],[18365859000,200121],[14981407000,200117],[14530907000,200174],[14503320000,200054],[13168057000,200114],[15447348000,200100],[15609396000,200111],[15206504000,200103],[12784147000,200128],[13695278000,200109],[13679774000,200105],[13495083000,200113],[15131492000,200115]],"completions":877,"idle":[[1078733000,200170],[1003049000,200175],[1065427000,200184],[1334835000,200198],[1177854000,200173],[1166806000,200172],[1103757000,200170],[1019101000,200182],[1147397000,200180],[1083737000,200176]]},{"busy":[[20350961000,200110],[17537736000,200132],[16182088000,200113],[15775777000,200102],[14818810000,200099],[15052330000,200106],[15629964000,200092],[13308194000,200124],[15899007000,200096],[15071006000,200211],[14502161000,200093],[13653102000,200135],[12493254000,200097],[13047696000,200087]],"completions":876,"idle":[[552550000,200068],[557188000,200176],[637084000,200180],[544920000,200169],[544799000,200179],[550047000,200086],[676939000,200177],[581542000,200175],[581969000,200067],[608274000,200111]]},{"busy":[[19695629000,200115],[17850235000,200114],[15141074000,200117],[15892965000,200118],[13652736000,200085],[13149502000,200099],[14871056000,200127],[13776820000,200143],[13207730000,200108],[13467983000,200114],[13792384000,200102],[13738124000,200123],[14180140000,200105],[13746730000,200110]],"completions":872,"idle":[[547911000,200378],[564146000,200384],[565734000,200252],[654295000,200374],[565001000,200371],[556639000,200396],[692076000,200317],[562804000,200387],[568541000,200369],[564696000,200383]]},{"busy":[[20536141000,200119],[17321733000,200109],[14995628000,200109],[14624841000,200110],[14060084000,200116],[14763512000,200118],[14829551000,200104],[14951378000,200133],[15362388000,200110],[14967735000,200104],[14571984000,200112],[14223963000,200130],[13494045000,200109],[12896695000,200162]],"completions":881,"idle":[[1257016000,200180],[1266965000,200172],[1219723000,200200],[1201840000,200170],[1228818000,200181],[1264767000,200172],[1272885000,200173],[1193479000,200178],[1199337000,200168],[1305478000,200173]]},{"busy":[[19786326000,200126],[16581928000,200122],[15265220000,200114],[14098719000,200115],[14185937000,200101],[14204371000,200125],[13428188000,200122],[14511804000,200160],[16111653000,200117],[13776271000,200106],[13487697000,200119],[14091212000,200197],[13587428000,200115],[13561916000,200119]],"completions":874,"idle":[[655028000,200233],[552855000,200245],[595641000,200220],[716917000,200242],[560545000,200240],[568663000,200246],[553954000,200250],[545897000,200233],[558714000,200233],[562865000,200232]]},{"busy":[[18676283000,200160],[15629843000,200117],[15094016000,200115],[13548061000,200117],[14640099000,200105],[14836388000,200208],[13206265000,200117],[13159329000,200103],[14289881000,200134],[13982020000,200168],[12702726000,200157],[14288049000,200116],[13433865000,200116],[12427458000,200108]],"completions":867,"idle":[[561217000,200387],[619993000,200379],[622069000,200458],[587035000,200305],[550963000,200311],[555418000,200304],[641844000,200330],[575194000,200298],[617857000,200319],[574706000,200310]]}]},{"capability":"wai.world.replay","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]},{"capability":"wai.world.scene","decoder":"builtin:wai@0.3.0","path":"software","profile":null,"rounds":[]}],"wai_sink_readings":1} \ No newline at end of file diff --git a/wai/live-conformance/sink-energy/tables/linux-x86_64-20-core/table.json b/wai/live-conformance/sink-energy/tables/linux-x86_64-20-core/table.json new file mode 100644 index 000000000..290036dcf --- /dev/null +++ b/wai/live-conformance/sink-energy/tables/linux-x86_64-20-core/table.json @@ -0,0 +1,2529 @@ +{ + "wai_sink_table": 1, + "signer": "5f8823017ac5f259575e1c43369a132cd44159e64c3a6dcdda69bb6b3b782c96", + "procedure": "wai-sink-table/1", + "procedure_sha256": "794e1039a82d21ef1014cdcf0013661cb1e118ed15b12f710a96b47e10a41980", + "readings_sha256": "5f3eec0c38aeec9bc7dd4541847eeb1bc29b0831b6103e6cd83d2b98541896fd", + "machine": { + "os": "linux 7.0.0-38-generic", + "arch": "x86_64", + "processor": "x86_64 processor, 20 logical cores", + "cores": 20, + "memory_gib": 15 + }, + "build": { + "profile": "release", + "toolchain": "rustc 1.98.0 (88d9e12ae 2026-08-18)", + "target": "x86_64-unknown-linux-gnu" + }, + "measured": "2026-10-25", + "rounds": 7, + "sample_us": 200000, + "counters": [ + { + "profile": "linux-nvml", + "status": "not-implemented", + "detail": "nvmlDeviceGetTotalEnergyConsumption, device 0; counter rate and power reading per 1 s interval (mW): 23326/10379 13408/10409 15528/10410 17504/10408 19593/10397" + }, + { + "profile": "linux-rapl", + "status": "live", + "detail": "powercap energy_uj: /sys/class/powercap/intel-rapl:0" + } + ], + "rows": [ + { + "capability": "wai.audio.aac", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.ac4", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.avs3", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.flac", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.iamf", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.int_codec", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "content-ms", + "units": 512, + "width": null, + "height": null, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2864842, + "sd_uw": 228432, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 79531247, + "sd_uw": 9065647, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801627, + "completions": 4341 + }, + { + "idle": { + "n": 10, + "mean_uw": 5589735, + "sd_uw": 783275, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 75646261, + "sd_uw": 8618909, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801727, + "completions": 4276 + }, + { + "idle": { + "n": 10, + "mean_uw": 2920994, + "sd_uw": 265732, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 74585164, + "sd_uw": 9202809, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801714, + "completions": 4296 + }, + { + "idle": { + "n": 10, + "mean_uw": 2850943, + "sd_uw": 248998, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 73586212, + "sd_uw": 8309184, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801877, + "completions": 4161 + }, + { + "idle": { + "n": 10, + "mean_uw": 2878968, + "sd_uw": 253731, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 74134336, + "sd_uw": 9360947, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801757, + "completions": 4279 + }, + { + "idle": { + "n": 10, + "mean_uw": 5389871, + "sd_uw": 367508, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 72585147, + "sd_uw": 7391446, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801701, + "completions": 4184 + }, + { + "idle": { + "n": 10, + "mean_uw": 2908547, + "sd_uw": 202525, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 74160413, + "sd_uw": 8504489, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801736, + "completions": 4277 + } + ], + "status": "figure", + "reason": null, + "median": 6, + "range": [ + 44953, + 49434 + ], + "joules_micro": 46631, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 31918, + "window_us": 2801736 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.ivas", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.mpegh", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.opus", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.opus.packet", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.scene", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.audio.score", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.avatar.pose", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.feed.state", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.film.linear", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.film.reel", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.generate.spectral", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.haptic.mihs", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.haptic.signal", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.avif", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.jp2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.jpeg", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.jpegai", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.jxl", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.image.png", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.mesh.draco", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.mesh.glb", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.bmshj2018", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.cmic", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.dac", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.dcvc_fm", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.dcvc_rt", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.elic", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.encodec32", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.glc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_hyper", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "image", + "units": 1, + "width": 16, + "height": 16, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 5752873, + "sd_uw": 190720, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 81396467, + "sd_uw": 7534287, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801368, + "completions": 252270 + }, + { + "idle": { + "n": 10, + "mean_uw": 2863021, + "sd_uw": 267834, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 74519537, + "sd_uw": 8689385, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801354, + "completions": 245626 + }, + { + "idle": { + "n": 10, + "mean_uw": 5099191, + "sd_uw": 369917, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 72229472, + "sd_uw": 6217482, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801802, + "completions": 241918 + }, + { + "idle": { + "n": 10, + "mean_uw": 5235726, + "sd_uw": 470097, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 72830024, + "sd_uw": 8642120, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801541, + "completions": 243771 + }, + { + "idle": { + "n": 10, + "mean_uw": 2828821, + "sd_uw": 183689, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 70479120, + "sd_uw": 6894670, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801840, + "completions": 234093 + }, + { + "idle": { + "n": 10, + "mean_uw": 6981503, + "sd_uw": 4573841, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 68581248, + "sd_uw": 7532166, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801586, + "completions": 238752 + }, + { + "idle": { + "n": 10, + "mean_uw": 5455574, + "sd_uw": 445142, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 70487124, + "sd_uw": 7871999, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801694, + "completions": 241401 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 723, + 840 + ], + "joules_micro": 777, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 43469, + "window_us": 2801541 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_mlicpp", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.int_synth", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "image", + "units": 1, + "width": 128, + "height": 128, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2867746, + "sd_uw": 204192, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 83147615, + "sd_uw": 6884562, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2802965, + "completions": 501 + }, + { + "idle": { + "n": 10, + "mean_uw": 2833703, + "sd_uw": 118913, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 79850871, + "sd_uw": 8274467, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2803314, + "completions": 498 + }, + { + "idle": { + "n": 10, + "mean_uw": 2865891, + "sd_uw": 210753, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 77481624, + "sd_uw": 8508407, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2803402, + "completions": 488 + }, + { + "idle": { + "n": 10, + "mean_uw": 2926340, + "sd_uw": 229983, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 76306869, + "sd_uw": 7815068, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2803078, + "completions": 482 + }, + { + "idle": { + "n": 10, + "mean_uw": 2903662, + "sd_uw": 331850, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 75861912, + "sd_uw": 8557537, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2802721, + "completions": 488 + }, + { + "idle": { + "n": 10, + "mean_uw": 2979508, + "sd_uw": 241184, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 76269973, + "sd_uw": 8669333, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2805486, + "completions": 482 + }, + { + "idle": { + "n": 10, + "mean_uw": 6262995, + "sd_uw": 409331, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 74427784, + "sd_uw": 8984181, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2802947, + "completions": 483 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 392325, + 445587 + ], + "joules_micro": 423233, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 36329, + "window_us": 2803078 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.mbt2018_mean", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.mimi", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.mlicpp", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.mlicv2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.snac", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.video_bmshj2018", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.neural.wavtokenizer", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.pc.gpcc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.pc.vpcc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.prior.nnc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.prior.set", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.quantum.circuit", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.quantum.circuit2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.semantic.clip", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.semantic.perco2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.splat.4d", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.splat.int_codec", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.splat.sog", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.splat.spz", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.tensor.nnc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.ternary.tt", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.text.xz", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.text.zstd", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av1.lossless", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.av2", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.avc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.avs3", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.evc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.evc.baseline", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.hevc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.int_hyper", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_work_item", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.int_motion", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "frame", + "units": 8, + "width": 96, + "height": 96, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 5983633, + "sd_uw": 1886298, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 84638153, + "sd_uw": 6615764, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801031, + "completions": 8390 + }, + { + "idle": { + "n": 10, + "mean_uw": 5045647, + "sd_uw": 277289, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 70249519, + "sd_uw": 4568862, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801750, + "completions": 7807 + }, + { + "idle": { + "n": 10, + "mean_uw": 3751016, + "sd_uw": 2091932, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 71923824, + "sd_uw": 8231893, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801581, + "completions": 7913 + }, + { + "idle": { + "n": 10, + "mean_uw": 3109171, + "sd_uw": 696388, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 70337616, + "sd_uw": 8426739, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801655, + "completions": 7874 + }, + { + "idle": { + "n": 10, + "mean_uw": 2969623, + "sd_uw": 350019, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 74895496, + "sd_uw": 7067420, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801574, + "completions": 8068 + }, + { + "idle": { + "n": 10, + "mean_uw": 6008833, + "sd_uw": 141880, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 68355281, + "sd_uw": 7507654, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801496, + "completions": 7840 + }, + { + "idle": { + "n": 10, + "mean_uw": 4170965, + "sd_uw": 1363143, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 70756960, + "sd_uw": 8452642, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801877, + "completions": 7889 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 22267, + 26247 + ], + "joules_micro": 23908, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 48057, + "window_us": 2801655 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.video.j2k", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vp8", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vp9", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.video.vvc", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.world.live", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.world.model", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": "linux-rapl", + "unit": "frame", + "units": 9, + "width": 64, + "height": 64, + "fabrics": [ + "cpu" + ], + "coverage": "Partial", + "workers": 8, + "rounds": [ + { + "idle": { + "n": 10, + "mean_uw": 2745954, + "sd_uw": 203343, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 77629587, + "sd_uw": 8311743, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801818, + "completions": 893 + }, + { + "idle": { + "n": 10, + "mean_uw": 5585367, + "sd_uw": 478203, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 75018955, + "sd_uw": 9533070, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801586, + "completions": 877 + }, + { + "idle": { + "n": 10, + "mean_uw": 2915619, + "sd_uw": 223091, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 76142937, + "sd_uw": 10046338, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801597, + "completions": 876 + }, + { + "idle": { + "n": 10, + "mean_uw": 2915670, + "sd_uw": 240263, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 73588049, + "sd_uw": 9555602, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801580, + "completions": 872 + }, + { + "idle": { + "n": 10, + "mean_uw": 6199678, + "sd_uw": 188434, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 75527113, + "sd_uw": 9254711, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801645, + "completions": 881 + }, + { + "idle": { + "n": 10, + "mean_uw": 2932060, + "sd_uw": 278298, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 73767503, + "sd_uw": 8701483, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801758, + "completions": 874 + }, + { + "idle": { + "n": 10, + "mean_uw": 2948120, + "sd_uw": 160990, + "live_intervals": 10, + "longest_live_run": 10 + }, + "busy": { + "n": 14, + "mean_uw": 71350710, + "sd_uw": 7825886, + "live_intervals": 14, + "longest_live_run": 14 + }, + "window_us": 2801841, + "completions": 867 + } + ], + "status": "figure", + "reason": null, + "median": 3, + "range": [ + 219470, + 233902 + ], + "joules_micro": 226021, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 36249, + "window_us": 2801580 + }, + "energy_counter_filtering": "Unknown", + "energy_calibration_ref": null + }, + { + "capability": "wai.world.replay", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + }, + { + "capability": "wai.world.scene", + "path": "software", + "decoder": "builtin:wai@0.3.0", + "profile": null, + "unit": "byte", + "units": 1, + "width": null, + "height": null, + "fabrics": [], + "coverage": null, + "workers": 0, + "status": "absent", + "reason": "no_decoder", + "rounds": [], + "median": null, + "range": null, + "joules_micro": 0, + "energy_provenance": null, + "energy_uncertainty": null, + "energy_counter_filtering": null, + "energy_calibration_ref": null + } + ], + "sig": "e08003dc9bc3d95056d13a61637cb4a8d5d46cb02c2b91e0dfa5fed743eae725454886edbdaa930af5c3787d3afe7eeeb84ddd3db67025f092b0cb05584fed01" +} -- 2.54.0 (Apple Git-157) From 2bce0addaa9bf87a86739fa927901669a08e1ff0 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 25 Oct 2026 04:04:05 -0400 Subject: [PATCH 075/242] ci: the fast-kernel tests run the ops corpus, and every x86_64 kernel set on the AVX-512 host The kernel steps of wai-conformance run tests/int_ops_conformance.rs on each leg. A new job on the linux-amd host runs the same tests in both profiles there, whose processor runs every x86_64 kernel set, AVX-512 among them, which no other leg's does; the host declares its sets, and the_kernel_sets_found_are_the_declared_ones fails where the sets found differ, so a missing set is a failure and not a skip. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/wai-conformance.yml | 54 +++++++++++++++++++++++++-- wai/wai-rs/src/int_kernels.rs | 14 +++++++ 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index f63a7827c..fc602ce32 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -418,13 +418,17 @@ jobs: # int_video_ops.rs and wir1_ops.rs hold WIV1 and WIR1, which decode through the # operator set's stage operators with no graph (integer-operators §13.5), to their # stage forms, goldens and refusals; `i32_forms` builds E9's 32-bit form, which they - # and int_ops_differential.rs hold to the exact one. + # and int_ops_differential.rs hold to the exact one. tests/int_ops_conformance.rs + # decodes every graph vector of ops-conformance/ through the fast kernels and holds + # each to the integers it states. Each of these runs every kernel set the leg's + # processor runs (int_kernels::equivalence_policies: the wide kernels, and NEON on + # arm64, AVX2 with or without VNNI on x86_64); int-kernels-amd below runs AVX-512. - name: Fast integer kernels ≡ reference kernels (overflow checks on) working-directory: wai/wai-rs - run: cargo test --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int,generate,score,staged,stage_measure,legacy_int,i32_forms --lib --test int_kernels_vectors --test int_kernels_geometry --test int_synth_ops --test int_ops_differential --test int_splat_ops --test int_audio_ops --test int_hyper_ops --test int_mlicpp --test int_mlicpp_preflight --test int_mlicpp_ops --test int_world_ops --test int_world_memory --test jpegai_ops --test jpegai_nets_ops --test int_video_ops --test wir1_ops --test int_audio --test int_splat --test generate --test cli_generate_score --test int_payload --test int_payload_preflight --test int_payload_cost --test int_set_cache --test int_video_enc_memory --test cli_stage --test staged_resources --test stage_measure --test stage_measure_bounds --test int_video --test int_video_halfpel --test int_video_keyframe --test int_world --test int_world2 --test jpegai_bitstream_calibration + run: cargo test --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int,generate,score,staged,stage_measure,legacy_int,i32_forms --lib --test int_kernels_vectors --test int_ops_conformance --test int_kernels_geometry --test int_synth_ops --test int_ops_differential --test int_splat_ops --test int_audio_ops --test int_hyper_ops --test int_mlicpp --test int_mlicpp_preflight --test int_mlicpp_ops --test int_world_ops --test int_world_memory --test jpegai_ops --test jpegai_nets_ops --test int_video_ops --test wir1_ops --test int_audio --test int_splat --test generate --test cli_generate_score --test int_payload --test int_payload_preflight --test int_payload_cost --test int_set_cache --test int_video_enc_memory --test cli_stage --test staged_resources --test stage_measure --test stage_measure_bounds --test int_video --test int_video_halfpel --test int_video_keyframe --test int_world --test int_world2 --test jpegai_bitstream_calibration - name: Fast integer kernels ≡ reference kernels (release, overflow checks off) working-directory: wai/wai-rs - run: cargo test --release --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int,generate,score,staged,stage_measure,legacy_int,i32_forms --lib --test int_kernels_vectors --test int_kernels_geometry --test int_synth_ops --test int_ops_differential --test int_splat_ops --test int_audio_ops --test int_hyper_ops --test int_mlicpp --test int_mlicpp_preflight --test int_mlicpp_ops --test int_world_ops --test int_world_memory --test jpegai_ops --test jpegai_nets_ops --test int_video_ops --test wir1_ops --test int_audio --test int_splat --test generate --test cli_generate_score --test int_payload --test int_payload_preflight --test int_payload_cost --test int_set_cache --test int_video_enc_memory --test cli_stage --test staged_resources --test stage_measure --test stage_measure_bounds --test manifest_parse_memory --test manifest_reader_equivalence --test int_video --test int_video_halfpel --test int_video_keyframe --test int_world --test int_world2 --test jpegai_bitstream_calibration + run: cargo test --release --no-default-features --features neural_int,video_int,audio_int,world_int,splat_int,generate,score,staged,stage_measure,legacy_int,i32_forms --lib --test int_kernels_vectors --test int_ops_conformance --test int_kernels_geometry --test int_synth_ops --test int_ops_differential --test int_splat_ops --test int_audio_ops --test int_hyper_ops --test int_mlicpp --test int_mlicpp_preflight --test int_mlicpp_ops --test int_world_ops --test int_world_memory --test jpegai_ops --test jpegai_nets_ops --test int_video_ops --test wir1_ops --test int_audio --test int_splat --test generate --test cli_generate_score --test int_payload --test int_payload_preflight --test int_payload_cost --test int_set_cache --test int_video_enc_memory --test cli_stage --test staged_resources --test stage_measure --test stage_measure_bounds --test manifest_parse_memory --test manifest_reader_equivalence --test int_video --test int_video_halfpel --test int_video_keyframe --test int_world --test int_world2 --test jpegai_bitstream_calibration # The integer decode path builds under each of its features alone, so a sink can take # one medium without the others, and for wasm32 (a browser sink); `int_payload` arms a # medium only under its feature, and none needs `neural`. So does the staged-measurement @@ -676,6 +680,50 @@ jobs: # above runs natively. # One leg: wasm32 is the same machine on every host. Linux, so it does not queue on the # single macOS runner. + # The fast integer kernels on the AVX-512 host (labelled linux-amd), whose processor runs + # every x86_64 kernel set (wide, AVX2, AVX2 with VNNI, AVX-512, AVX-512 with VNNI), none + # of whose AVX-512 kernels the other legs' processors run. The same tests as the kernel + # steps of cargo-classes, in both profiles; the runner declares its sets, and a set the + # tests did not find fails the job instead of passing as a skip. + int-kernels-amd: + name: fast integer kernels, every x86_64 kernel set (AVX-512 silicon) + needs: relevant + if: needs.relevant.outputs.run == 'true' + runs-on: linux-amd + timeout-minutes: 90 + env: + CARGO_PROFILE_DEV_DEBUG: '0' + CARGO_BUILD_JOBS: '12' + steps: + - name: Fetch the source + shell: bash + run: | + set -euo pipefail + want="${{ github.event.pull_request.head.sha || github.sha }}" + rm -rf src && mkdir src && cd src + git init -q + # Fetch the commit itself, not the ref. The host names its own route to the + # forge (WAI_FORGE_URL). + git fetch -q --depth 1 "${WAI_FORGE_URL:-http://100.64.224.0:3400}/Transaction-Science/open-standards.git" "$want" + git checkout -q FETCH_HEAD + got="$(git rev-parse HEAD)" + [ "$got" = "$want" ] || { echo "::error::fetched $got, not $want"; exit 1; } + - name: Fast integer kernels ≡ reference kernels, every kernel set, both profiles + shell: bash + working-directory: src/wai/wai-rs + run: | + set -euo pipefail + export PATH="$HOME/.cargo/bin:$PATH" + export CARGO_TARGET_DIR="$HOME/hw-target/wai-rs-int-kernels" + # The sets this host's processor runs, declared here (or by the runner); the + # library test the_kernel_sets_found_are_the_declared_ones fails on any other. + export WAI_INT_ISAS_EXPECT="${WAI_INT_AMD_ISAS_EXPECT:-wide,avx2,avx2-vnni,avx512,avx512-vnni}" + F=neural_int,video_int,audio_int,world_int,splat_int,generate,score,staged,stage_measure,legacy_int,i32_forms + T="--test int_kernels_vectors --test int_kernels_geometry --test int_synth_ops --test int_splat_ops --test int_audio_ops --test int_hyper_ops --test int_mlicpp_ops --test int_world_ops --test jpegai_ops --test jpegai_nets_ops --test int_video_ops --test wir1_ops --test int_ops_conformance" + for p in "" --release; do + nice -n 10 cargo test --locked $p --no-default-features --features $F --lib $T + done + quantum-wasip2: needs: relevant if: needs.relevant.outputs.run == 'true' diff --git a/wai/wai-rs/src/int_kernels.rs b/wai/wai-rs/src/int_kernels.rs index af749b272..a024d91d4 100644 --- a/wai/wai-rs/src/int_kernels.rs +++ b/wai/wai-rs/src/int_kernels.rs @@ -2215,6 +2215,20 @@ mod tests { } } + /// The kernel sets this processor runs, by name. Where the runner declares them + /// (`WAI_INT_ISAS_EXPECT`, the names comma-separated in [`Isa::ALL`]'s order), they + /// must be exactly those: a set the equivalence tests were meant to run on this + /// hardware and did not is a failure, not a quiet skip. + #[test] + fn the_kernel_sets_found_are_the_declared_ones() { + let found: Vec<&str> = isas().into_iter().map(Isa::name).collect(); + println!("kernel sets found: {}", found.join(",")); + if let Ok(want) = std::env::var("WAI_INT_ISAS_EXPECT") { + assert_eq!(found.join(","), want.trim(), "the kernel sets found are not the declared ones"); + } + assert_eq!(found.first(), Some(&"wide"), "the wide set runs everywhere"); + } + /// The kernel sets with narrow kernels this processor runs. fn narrow_isas() -> Vec { isas().into_iter().filter(|&s| s != Isa::Wide).collect() -- 2.54.0 (Apple Git-157) From 61759c51b54498cf19e5327e3d8586d4a0ce5a78 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 25 Oct 2026 04:06:15 -0400 Subject: [PATCH 076/242] =?UTF-8?q?wai:=20the=20int=5Fhyper=20decode=20on?= =?UTF-8?q?=20the=20narrow=20kernel=20sets,=20measured;=20determinism-tier?= =?UTF-8?q?s=20=C2=A77=20quotes=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit research/determinism-cost.md §4.6, in the same record and by the same procedure: the 192 payloads decoded again on every platform with every kernel set it runs (15 new runs, 0 decodes differing from the record's), and the record's time and energy runs repeated in one session per host beside the record's own build and the float decoder. On x86_64 host A the integer decode on the narrow kernels took 144.8 ms and 12.43 J of marginal package energy at quality 6 against 210.7 ms and 14.03 J for the float decode beside it (the record's build: 728.0 ms, 92.14 J). On the build host its own CPU energy fell from 11.01 J to 6.43 J at quality 6 and is still above the float decode's 1.28 J by that count. Host B's integer decode was measured before and after, without a float decode beside it. The scripts that took the readings are in runs/; decode-throughput.md notes the new sets; the changelog records the change. Co-Authored-By: Claude Opus 5.5 --- wai/CHANGELOG.md | 24 + wai/extensions/determinism-tiers.md | 24 +- wai/research/decode-throughput.md | 5 + wai/research/determinism-cost.md | 151 +- wai/research/determinism-cost/record.json | 9490 ++++++++++++++++- wai/research/determinism-cost/runs/README.md | 17 + .../runs/linux-a-narrow-build.sh | 10 + .../runs/linux-a-narrow-decodes.sh | 23 + .../runs/linux-a-narrow-energy.sh | 30 + .../runs/linux-a-narrow-jobs.sh | 9 + .../runs/linux-b-narrow-decodes.sh | 21 + .../runs/linux-b-narrow-energy.sh | 23 + .../runs/macos-narrow-build.sh | 11 + .../runs/macos-narrow-decodes.sh | 28 + .../runs/macos-narrow-energy.sh | 28 + 15 files changed, 9872 insertions(+), 22 deletions(-) create mode 100644 wai/research/determinism-cost/runs/linux-a-narrow-build.sh create mode 100644 wai/research/determinism-cost/runs/linux-a-narrow-decodes.sh create mode 100644 wai/research/determinism-cost/runs/linux-a-narrow-energy.sh create mode 100644 wai/research/determinism-cost/runs/linux-a-narrow-jobs.sh create mode 100644 wai/research/determinism-cost/runs/linux-b-narrow-decodes.sh create mode 100644 wai/research/determinism-cost/runs/linux-b-narrow-energy.sh create mode 100644 wai/research/determinism-cost/runs/macos-narrow-build.sh create mode 100644 wai/research/determinism-cost/runs/macos-narrow-decodes.sh create mode 100644 wai/research/determinism-cost/runs/macos-narrow-energy.sh diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index d8dacc709..df538fd51 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -6,6 +6,30 @@ wire format may change and such changes are recorded here. ## v0.4.0 — current +### The integer decoders' narrow kernel sets: the same bytes, measured again + +- **Kernel sets** (`wai-rs` `int_kernels::Isa`, chosen by the processor's features or by + `WAI_INT_ISA`): narrow convolution kernels on NEON, AVX2, AVX2 with VNNI, AVX-512 and + AVX-512 with VNNI. Each convolution's sums are formed from 16-bit operands (an input past + 2^15 − 1 split into two 15-bit limbs) in 32-bit partial sums over blocks of taps sized so + that no partial sum leaves `i32`, then in 64-bit sums under the bound the interpreter + already requires; the inverse GDN's norms run in 64-bit SIMD lanes on x86_64, its roots + are seeded from the IEEE-754 square root and corrected in integers, and `rescale`, + `clamp`, `relu` and `leaky` run on the fast workers, the requantisation in `i64` where its + values fit. The wide kernels stay as the `wide` set, and run wherever a narrow plan does + not apply. No output byte, hash or conformance vector changes. +- **Tests.** Every decoder-level equivalence test runs each kernel set the processor runs + (`int_kernels::equivalence_policies`); `tests/int_ops_conformance.rs` decodes every graph + vector of `ops-conformance/` through the fast kernels under each and holds it to the + stated integers; random geometry against the exact sums, the block and limb edges, the + seeded root, the requantisation and the element-wise operators each have their own test. +- **Measured** ([research/determinism-cost.md](research/determinism-cost.md) §4.6, the same + record and procedure): the 192 `int_hyper` payloads decoded again on every platform with + each kernel set, identical to the record's decodes; and the record's time and energy runs + repeated beside the record's own build and the float decoder, on x86_64 host A, x86_64 + host B (the integer decode only) and the build host. + [determinism-tiers](extensions/determinism-tiers.md) §7.1 quotes them. + ### What exactness costs: a measured record, and what WAI claims from it - **The record** ([research/determinism-cost.md](research/determinism-cost.md), diff --git a/wai/extensions/determinism-tiers.md b/wai/extensions/determinism-tiers.md index 73f74f786..bcd844387 100644 --- a/wai/extensions/determinism-tiers.md +++ b/wai/extensions/determinism-tiers.md @@ -714,14 +714,16 @@ record, recomputes each from the readings and checks it, in CI. +0.95 % for the same MS-SSIM (BD-rate, ITU-T VCEG-M33, over the eight qualities). - **The integer decode, across platforms.** Every payload decoded to the same bytes on - every platform reached: 0 of - 1728 decodes on - 9 platforms differed from the - build host's CPU decode, and 0 were + every platform reached, with every kernel set each runs: + 0 of + 4608 decodes, in + 24 runs of a platform and a kernel set, differed + from the build host's CPU decode, and 0 were refused. The platforms: an arm64 desktop SoC's CPU (all cores, one core) and its GPU through Metal; WebAssembly under wasmtime on that machine and on an x86_64 one; and two x86_64 Linux hosts, each on its CPU and on its GPU through - Vulkan (PCI vendors 0x1002 and 0x10de). + Vulkan (PCI vendors 0x1002 and 0x10de). The kernel sets: the wide kernels, and the narrow + ones on NEON, AVX2, AVX2 with VNNI, AVX-512 and AVX-512 with VNNI. - **The float decode, across platforms.** Decoded again in a fresh process on the CPU of the machine that encoded them, with one thread and with the library's default thread count, the float streams gave the encoder's own bytes in @@ -754,8 +756,16 @@ record, recomputes each from the readings and checks it, in CI. 13.7 J for the float codec; with the integer decode's graphs on its integrated GPU, 845.4 ms and - 93.5 J. The - build host's figures, taken under heavy load from other work, are in the record. + 93.5 J. On + the narrow kernel sets of the record's §4.6, the same integer decode, giving the same + bytes, took 144.8 ms and + 12.43 J on host A's CPU, + against 210.7 ms and + 14.03 J for the float + decode measured beside it. On the build host, under heavy load from other work, the + integer decode's own CPU energy fell to + 6.43 J at quality 6 and stayed + above the float decode's 1.28 J. Each figure holds for the pictures, models, platforms and builds the record names, and for nothing else (§7.2). diff --git a/wai/research/decode-throughput.md b/wai/research/decode-throughput.md index 2b4552bb8..9856cddc4 100644 --- a/wai/research/decode-throughput.md +++ b/wai/research/decode-throughput.md @@ -8,6 +8,11 @@ ## 1. What changed +*Later:* the narrow kernel sets (`int_kernels::Isa`: NEON, AVX2 and AVX-512) were added +after this note, beside the kernels it describes, which remain as the `wide` set. What they +change for one decoder is measured in [determinism-cost](determinism-cost.md) §4.6; the +figures below are of the wide kernels. + The integer decoders (`int_synth`, `int_hyper_synth`, `int_audio`, `int_splat`, `int_world`, the neural video keyframe, and the JPEG-AI synthesis in `jpegai_synthesis` / `int_transformer`) now run their hot kernels through diff --git a/wai/research/determinism-cost.md b/wai/research/determinism-cost.md index 3a5a7e37b..c890f659c 100644 --- a/wai/research/determinism-cost.md +++ b/wai/research/determinism-cost.md @@ -6,7 +6,7 @@ > checks every figure this page and [determinism-tiers](../extensions/determinism-tiers.md) > §7 quote from it (each is followed by a `det` marker naming its place in the record). > What WAI claims, and does not, on the strength of this record is in determinism-tiers §7. -> Measured 2026-10-22. +> Measured 2026-10-22; the runs of §4.6, on the narrow kernel sets, 2026-10-24 and 2026-10-25. ## 1. What is measured, and what is not @@ -162,6 +162,12 @@ host B), the pinned versions of `tools/requirements-sota-int.txt` (its CPU-only host A, its GPU build on host B), with deterministic algorithms requested. The tags under which the record keeps each platform are in `determinism-cost/runs/README.md`. +§4.6's builds are the same, from the change that added the narrow kernel sets, made on each +host (the wasm32-wasip2 file again once, on the build host), beside each host's build of the +record's own commit; on host A the float stack was set up again from the same pinned +versions. Host B's GPU was not reached for §4.6 (its driver was not loaded when the runs +were taken), so its GPU decode is the record's only. + ## 4. Results Every figure here is the record's, and the verifier of §6 recomputes it; each is followed by a marker naming @@ -177,17 +183,25 @@ In brief: picture is +0.56 % by PSNR and +0.52 % by MS-SSIM: the export costs rate on every picture (§4.1, and each picture's figure in the record). -- **Bytes.** The integer decode gave the same bytes on every platform: - 0 of - 1728 decodes differed (§4.3). The +- **Bytes.** The integer decode gave the same bytes on every platform, under every kernel + set: 0 of + 4608 decodes differed (§4.3, §4.6). The float decode gave the encoder's bytes only on the encoder's own CPU. Everywhere else it gave other bytes for every stream, and its entropy decode diverged on many of them, from 76 of 192 on host B's CPU to 192 on its GPU (§4.4). -- **Time and energy.** On host A, where every round read clean, the integer decode took - longer and drew more package energy than the float decode on the same CPU, at both - qualities, and so did the integer decode with its graphs on the integrated GPU (§4.5). +- **Time and energy.** On host A, where every round read clean, the record's integer + decode took longer and drew more package energy than the float decode on the same CPU, at + both qualities, and so did the integer decode with its graphs on the integrated GPU + (§4.5). On the narrow kernel sets (§4.6) the same integer decode, giving the same bytes, + took 144.8 ms and + 12.43 J at quality 6 + against 210.7 ms and + 14.03 J for the float + decode measured beside it, and less time and energy than the float decode at quality 2 + as well. On the build host it still drew more of its own CPU energy than the float decode + (§4.6, and §5 on what that count covers there). ### 4.1 BD-rate, integer codec against float codec @@ -268,6 +282,118 @@ in the record, and nothing is drawn from them. The per-task counts there are the decoding process's own CPU energy, which the load on the host changes less than it changes the time, but does change; §5 states what they cover. +### 4.6 The integer decode on the narrow kernel sets + +The record's integer decoder (§4.3, §4.5) ran the wide kernels of `wai-rs`'s `int_kernels`: +32×32→64-bit multiply-accumulates, NEON on arm64 and portable Rust elsewhere. The runs of +this section are of the build that added the narrow kernel sets (`int_kernels::Isa`): +convolutions on 16-bit operands with 32-bit partial sums in blocks bounded so that no value +leaves its type (NEON on arm64; AVX2, AVX2 with VNNI, AVX-512 and AVX-512 with VNNI on +x86_64), the inverse GDN's norms and outputs in SIMD lanes, and the element-wise operators +and the requantisation in 64-bit arithmetic where their values fit. Every operator still +gives the interpreter's integers (the `int_kernels` documentation states how, and the tests +it names hold each set to the interpreter); the set is chosen by the processor's features, +or by `WAI_INT_ISA`. The wasm32-wasip2 build has no narrow set and runs the wide kernels, as +`wide` names below, with the build's other changes. + +**Bytes.** Every payload decoded again on each platform with each kernel set the platform +runs, against the record's build host CPU decode (the GPU decodes run both graphs on the +device and are unchanged by the kernel sets; the runs check it): + +| platform | kernel set | decodes | identical to the build host's CPU decode | differ | refused | +|---|---|---:|---:|---:|---:| +| `linux-x86_64-b-cpu` | `avx2` | 192 | 192 | 0 | 0 | +| `linux-x86_64-b-cpu` | `avx2-vnni` | 192 | 192 | 0 | 0 | +| `linux-x86_64-b-cpu` | `wide` | 192 | 192 | 0 | 0 | +| `linux-x86_64-cpu` | `avx2` | 192 | 192 | 0 | 0 | +| `linux-x86_64-cpu` | `avx2-vnni` | 192 | 192 | 0 | 0 | +| `linux-x86_64-cpu` | `avx512` | 192 | 192 | 0 | 0 | +| `linux-x86_64-cpu` | `avx512-vnni` | 192 | 192 | 0 | 0 | +| `linux-x86_64-cpu` | `wide` | 192 | 192 | 0 | 0 | +| `linux-x86_64-gpu-vulkan` | `avx512-vnni` | 192 | 192 | 0 | 0 | +| `macos-arm64-cpu-1t` | `neon` | 192 | 192 | 0 | 0 | +| `macos-arm64-cpu` | `neon` | 192 | 192 | 0 | 0 | +| `macos-arm64-cpu` | `wide` | 192 | 192 | 0 | 0 | +| `macos-arm64-gpu-metal` | `neon` | 192 | 192 | 0 | 0 | +| `wasm32-wasip2` | `wide` | 192 | 192 | 0 | 0 | +| `wasm32-wasip2-x86_64` | `wide` | 192 | 192 | 0 | 0 | + +**Time and energy.** The procedure of §2.6, on the same eight pictures. On each host the +runs were taken in one session: `-before` the record's own build of the integer decoder run +again, `-after` this section's build (its kernel set chosen by the processor: AVX-512 with +VNNI on host A, AVX2 with VNNI on host B, NEON on the build host; `-after-avx2` the AVX2 set +on host A), `-again` the float decoder run again. `linux-b` is host B, measured only for the +integer decode (its float stack was not set up again), under the load of the CI jobs it +also serves. + +| run | time (ms) | own CPU energy (J) | whole-device marginal energy (J) | whole-device rounds | load average | +|---|---|---|---|---|---| +| `linux-b-int-cpu-q2-after` | 170.2 (168.9–203.6) | — | rapl: 11.56 (9.31–11.91) | 7 of 7 used; 7 live; 2 relative-only, 5 clean | 5–6 | +| `linux-b-int-cpu-q2-before` | 714.0 (704.2–716.3) | — | rapl: 43.84 (42.71–44.30) | 7 of 7 used; 7 live; 7 clean | 2–9 | +| `linux-b-int-cpu-q6-after` | 294.3 (292.3–301.1) | — | rapl: 19.97 (18.83–20.20) | 7 of 7 used; 7 live; 7 clean | 6–11 | +| `linux-b-int-cpu-q6-before` | 1582.1 (1555.6–1598.9) | — | rapl: 84.92 (83.95–87.95) | 7 of 7 used; 7 live; 7 clean | 6–12 | +| `linux-float-cpu-q2-again` | 132.2 (123.2–139.7) | — | rapl: 9.26 (9.00–9.33) | 7 of 7 used; 7 live; 7 clean | 2–4 | +| `linux-float-cpu-q6-again` | 210.7 (200.8–226.1) | — | rapl: 14.03 (13.65–14.41) | 7 of 7 used; 7 live; 7 clean | 6–8 | +| `linux-int-cpu-q2-after` | 92.9 (90.8–93.1) | — | rapl: 7.31 (7.29–7.50) | 7 of 7 used; 7 live; 7 clean | 3–5 | +| `linux-int-cpu-q2-after-avx2` | 101.6 (101.3–103.0) | — | rapl: 9.27 (9.22–9.35) | 7 of 7 used; 7 live; 7 clean | 3–5 | +| `linux-int-cpu-q2-before` | 345.8 (345.2–346.4) | — | rapl: 42.25 (42.17–42.33) | 7 of 7 used; 7 live; 7 clean | 2–5 | +| `linux-int-cpu-q6-after` | 144.8 (142.7–147.7) | — | rapl: 12.43 (12.28–12.58) | 7 of 7 used; 7 live; 7 clean | 8–11 | +| `linux-int-cpu-q6-after-avx2` | 166.3 (164.7–166.8) | — | rapl: 16.46 (16.36–16.53) | 7 of 7 used; 7 live; 7 clean | 2–4 | +| `linux-int-cpu-q6-before` | 728.0 (725.8–729.8) | — | rapl: 92.14 (91.96–92.67) | 7 of 7 used; 7 live; 7 clean | 4–12 | +| `macos-float-cpu-q2-again` | 212.5 (140.4–330.5) | 0.92 (0.81–1.07) | — | — | 40–47 | +| `macos-float-cpu-q6-again` | 271.3 (215.7–598.7) | 1.28 (1.22–1.51) | — | — | 46–61 | +| `macos-int-cpu-q2-after` | 274.1 (98.3–385.0) | 2.96 (2.83–3.12) | — | — | 32–46 | +| `macos-int-cpu-q2-before` | 130.9 (106.2–338.3) | 4.76 (4.47–4.90) | — | — | 34–42 | +| `macos-int-cpu-q6-after` | 477.5 (392.1–909.3) | 6.43 (6.12–6.63) | — | — | 32–62 | +| `macos-int-cpu-q6-before` | 268.1 (228.4–409.0) | 11.01 (10.93–11.16) | — | — | 33–47 | + + +On host A every round of every run read clean. At quality 6 the narrow kernels took the +integer decode from 728.0 ms and +92.14 J to +144.8 ms and +12.43 J, against +210.7 ms and +14.03 J for the float decode; +at quality 2 from 345.8 ms and +42.25 J to +92.9 ms and +7.31 J, against +132.2 ms and +9.26 J. With the AVX2 set +on the same host the integer decode drew +9.27 J at quality 2 and +16.46 J at quality 6. +The record's own runs of 2026-10-22 (§4.5) and the `-before` runs agree within a few +tenths of a joule. + +On host B, under the load of the CI jobs it serves, the integer decode went from +1582.1 ms and +84.92 J to +294.3 ms and +19.97 J at quality 6, and +from 714.0 ms and +43.84 J to +170.2 ms and +11.56 J at quality 2, where +two of the seven rounds were relative-only against their idle brackets and the rest clean. No float decode was measured +beside them there. + +On the build host the narrow kernels lowered the decoder's own CPU energy from +11.01 J to +6.43 J at quality 6 and from +4.76 J to +2.96 J at quality 2. The float decode's +own CPU energy there was 1.28 J and +0.92 J: by that count the integer +decode still costs more on this host, and the count may leave out the float decode's matrix +unit (§5). The host was under heavy load from other work throughout (the load averages in +the table). Its times swing with that load from round to round and from run to run by more +than the kernels change them, so that a run's median time there says little: the `-after` +runs' medians lie above the `-before` runs' though their ranges overlap, while each run's +per-task counts stay within a few tenths of a joule of each other. The per-task count is the +build host's comparable figure. + ## 5. What this does and does not show - **One model, one export.** The integer codec is a post-training quantisation of one @@ -288,11 +414,12 @@ the time, but does change; §5 states what they cover. from other work throughout; the record keeps the load average of every round. The per-task count is the decoder's own CPU energy and nothing else; the GPU rail and the package counter cover their whole device, and a figure from them is the round's marginal - over its idle brackets. The integer decoder is CPU code with the fast kernels of - `int_kernels` (NEON on arm64, portable Rust elsewhere), and its GPU decode runs the - graphs the device takes through `wai-ops-gpu`; the float decode runs the library's own - kernels. The figures compare those implementations on those hosts, not integer and - float arithmetic in general. + over its idle brackets. The record's integer decoder is CPU code with the wide kernels of + `int_kernels` (NEON on arm64, portable Rust elsewhere), §4.6's with the narrow kernel sets + (NEON on arm64, AVX2 and AVX-512 on x86_64), and its GPU decode runs the graphs the + device takes through `wai-ops-gpu`; the float decode runs the library's own kernels. The + figures compare those implementations on those hosts, not integer and float arithmetic in + general. - **What the per-task count covers on the build host.** It is the kernel's account of the process's CPU energy. The float library's CPU kernels on that host may hand matrix work to a matrix unit the CPU clusters share, and this record does not establish whether the diff --git a/wai/research/determinism-cost/record.json b/wai/research/determinism-cost/record.json index 164ea964a..d6b0a1da7 100644 --- a/wai/research/determinism-cost/record.json +++ b/wai/research/determinism-cost/record.json @@ -8437,6 +8437,627 @@ "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" } }, + "linux-x86_64-b-cpu-isa-avx2": { + "platform": { + "fabric": "CPU", + "hardware": "x86_64 laptop CPU, 14 cores, 20 threads, AVX2; discrete GPU, PCI vendor 0x10de", + "kernels": "fast, kernel set avx2", + "measured": "2026-10-24", + "runtime": "native", + "rustc": "1.98.0", + "system": "Linux 7.0.0-38-generic, 2.39 glibc", + "target": "x86_64-unknown-linux-gnu", + "threads": "all (20)" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } + }, + "linux-x86_64-b-cpu-isa-avx2-vnni": { + "platform": { + "fabric": "CPU", + "hardware": "x86_64 laptop CPU, 14 cores, 20 threads, AVX2; discrete GPU, PCI vendor 0x10de", + "kernels": "fast, kernel set avx2-vnni", + "measured": "2026-10-24", + "runtime": "native", + "rustc": "1.98.0", + "system": "Linux 7.0.0-38-generic, 2.39 glibc", + "target": "x86_64-unknown-linux-gnu", + "threads": "all (20)" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } + }, + "linux-x86_64-b-cpu-isa-wide": { + "platform": { + "fabric": "CPU", + "hardware": "x86_64 laptop CPU, 14 cores, 20 threads, AVX2; discrete GPU, PCI vendor 0x10de", + "kernels": "fast, kernel set wide", + "measured": "2026-10-24", + "runtime": "native", + "rustc": "1.98.0", + "system": "Linux 7.0.0-38-generic, 2.39 glibc", + "target": "x86_64-unknown-linux-gnu", + "threads": "all (20)" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } + }, "linux-x86_64-b-gpu-vulkan": { "platform": { "fabric": "GPU, Vulkan, discrete, PCI vendor 0x10de", @@ -8849,6 +9470,1041 @@ "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" } }, + "linux-x86_64-cpu-isa-avx2": { + "platform": { + "fabric": "CPU", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512", + "kernels": "fast, kernel set avx2", + "measured": "2026-10-25", + "runtime": "native", + "rustc": "1.98.0", + "system": "Linux 7.0.0-38-generic, 2.43 glibc", + "target": "x86_64-unknown-linux-gnu", + "threads": "all (32)" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } + }, + "linux-x86_64-cpu-isa-avx2-vnni": { + "platform": { + "fabric": "CPU", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512", + "kernels": "fast, kernel set avx2-vnni", + "measured": "2026-10-25", + "runtime": "native", + "rustc": "1.98.0", + "system": "Linux 7.0.0-38-generic, 2.43 glibc", + "target": "x86_64-unknown-linux-gnu", + "threads": "all (32)" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } + }, + "linux-x86_64-cpu-isa-avx512": { + "platform": { + "fabric": "CPU", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512", + "kernels": "fast, kernel set avx512", + "measured": "2026-10-25", + "runtime": "native", + "rustc": "1.98.0", + "system": "Linux 7.0.0-38-generic, 2.43 glibc", + "target": "x86_64-unknown-linux-gnu", + "threads": "all (32)" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } + }, + "linux-x86_64-cpu-isa-avx512-vnni": { + "platform": { + "fabric": "CPU", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512", + "kernels": "fast, kernel set avx512-vnni", + "measured": "2026-10-25", + "runtime": "native", + "rustc": "1.98.0", + "system": "Linux 7.0.0-38-generic, 2.43 glibc", + "target": "x86_64-unknown-linux-gnu", + "threads": "all (32)" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } + }, + "linux-x86_64-cpu-isa-wide": { + "platform": { + "fabric": "CPU", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512", + "kernels": "fast, kernel set wide", + "measured": "2026-10-25", + "runtime": "native", + "rustc": "1.98.0", + "system": "Linux 7.0.0-38-generic, 2.43 glibc", + "target": "x86_64-unknown-linux-gnu", + "threads": "all (32)" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } + }, "linux-x86_64-gpu-vulkan": { "platform": { "fabric": "GPU, Vulkan, integrated, PCI vendor 0x1002", @@ -9055,6 +10711,213 @@ "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" } }, + "linux-x86_64-gpu-vulkan-isa-avx512-vnni": { + "platform": { + "fabric": "GPU, Vulkan, integrated, PCI vendor 0x1002", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512", + "kernels": "fast, kernel set avx512-vnni", + "measured": "2026-10-25", + "runtime": "wgpu 30.0.1, adapter: key=gpu-vulkan-1002 backend=vulkan adapter='Radeon 8060S Graphics (RADV STRIX_HALO)' type=integrated vendor=0x1002 device=0x1586 driver='radv' driver_info='Mesa 26.0.8-1ubuntu0.3'", + "rustc": "1.98.0", + "system": "Linux 7.0.0-38-generic, 2.43 glibc", + "target": "x86_64-unknown-linux-gnu", + "threads": "all (32) for graphs off the device" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } + }, "macos-arm64-cpu": { "platform": { "fabric": "CPU", @@ -9467,6 +11330,627 @@ "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" } }, + "macos-arm64-cpu-1t-isa-neon": { + "platform": { + "fabric": "CPU", + "hardware": "arm64 desktop SoC, 24 performance and 8 efficiency cores", + "kernels": "fast, kernel set neon", + "measured": "2026-10-24", + "runtime": "native", + "rustc": "1.98.0", + "system": "macOS 27.0.1", + "target": "aarch64-apple-darwin", + "threads": "1" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } + }, + "macos-arm64-cpu-isa-neon": { + "platform": { + "fabric": "CPU", + "hardware": "arm64 desktop SoC, 24 performance and 8 efficiency cores", + "kernels": "fast, kernel set neon", + "measured": "2026-10-24", + "runtime": "native", + "rustc": "1.98.0", + "system": "macOS 27.0.1", + "target": "aarch64-apple-darwin", + "threads": "all (32)" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } + }, + "macos-arm64-cpu-isa-wide": { + "platform": { + "fabric": "CPU", + "hardware": "arm64 desktop SoC, 24 performance and 8 efficiency cores", + "kernels": "fast, kernel set wide", + "measured": "2026-10-24", + "runtime": "native", + "rustc": "1.98.0", + "system": "macOS 27.0.1", + "target": "aarch64-apple-darwin", + "threads": "all (32)" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } + }, "macos-arm64-gpu-metal": { "platform": { "fabric": "GPU, Metal, integrated (80 cores)", @@ -9673,6 +12157,213 @@ "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" } }, + "macos-arm64-gpu-metal-isa-neon": { + "platform": { + "fabric": "GPU, Metal, integrated (80 cores)", + "hardware": "arm64 desktop SoC, 24 performance and 8 efficiency cores", + "kernels": "fast, kernel set neon", + "measured": "2026-10-24", + "runtime": "wgpu 30.0.1, key=gpu-metal-0", + "rustc": "1.98.0", + "system": "macOS 27.0.1", + "target": "aarch64-apple-darwin", + "threads": "all (32) for graphs off the device" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } + }, "wasm32-wasip2": { "platform": { "fabric": "CPU, WebAssembly", @@ -9879,6 +12570,213 @@ "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" } }, + "wasm32-wasip2-isa-wide": { + "platform": { + "fabric": "CPU, WebAssembly", + "hardware": "arm64 desktop SoC, 24 performance and 8 efficiency cores", + "kernels": "fast, kernel set wide (the only set on wasm)", + "measured": "2026-10-24", + "runtime": "wasmtime 49.0.3", + "rustc": "1.98.0", + "system": "macOS 27.0.1", + "target": "wasm32-wasip2", + "threads": "1 (eight decoder processes side by side)" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } + }, "wasm32-wasip2-x86_64": { "platform": { "fabric": "CPU, WebAssembly", @@ -10084,6 +12982,213 @@ "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" } + }, + "wasm32-wasip2-x86_64-isa-wide": { + "platform": { + "fabric": "CPU, WebAssembly", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512", + "kernels": "fast, kernel set wide (the only set on wasm)", + "measured": "2026-10-25", + "runtime": "wasmtime 49.0.3, the build host's .wasm", + "rustc": "1.98.0", + "system": "Linux 7.0.0-38-generic", + "target": "wasm32-wasip2", + "threads": "1 (sixteen decoder processes side by side)" + }, + "results": { + "10349e963c5c813d/q1": "30d207a4a618ec1dad9dcd547774cd9e063850d75a0df2219bda0a9a2dd0a813", + "10349e963c5c813d/q2": "30ca8104aeae905f9503dca1a7c64fb769f3ac85229da0a8742d1f6e6fe6cc5d", + "10349e963c5c813d/q3": "70b5379aec97de1cd2ac5dffeee9b41501d4271e2178aac11d54b87362a754ed", + "10349e963c5c813d/q4": "25a136524b30d79c3c185b023d967cd176d9e5b64d7709a04fc982076989846d", + "10349e963c5c813d/q5": "9af472eb04dcba5b6801fe1a0ea1b7972df91c578c6630c9fe5da5c0dca58390", + "10349e963c5c813d/q6": "8085b3c38efbccb7db51b423690f3b3aaee1f993fe32c886993c57f25143d14c", + "10349e963c5c813d/q7": "ee4cc89de327b1317cb4c433416d273646e7a049c0b0a57d55fcab1bd8e95bf0", + "10349e963c5c813d/q8": "f595dc9291782fa21099079d69c25560786767626c5c4545fea176d4028236e1", + "1071c68372cc5a01/q1": "cdbee88d561629a5d320ee34726d93fcb8fe1e591109409a45ad74200308b964", + "1071c68372cc5a01/q2": "b9509288a882675663fb026c2b103829d01da4c24982aa4698838eb50cc1ce65", + "1071c68372cc5a01/q3": "fac3858172c84d88e32dda2e95c9f7c0735867ecf6ea06fa112279b6a63c1f4d", + "1071c68372cc5a01/q4": "695bb227d9b1835e71738638a99b363f003a375e741835811e87a9586226453e", + "1071c68372cc5a01/q5": "bff58dbe5120b073d70563923998ed575c9e1363254e154ead9d92a3c5fe21d3", + "1071c68372cc5a01/q6": "750a451dfd89f055db3c0fa718a1e9ac7b5d65cdf4be53d0c3a993b5c39e6bc5", + "1071c68372cc5a01/q7": "1ee17a33bc520e9c48bb31471c3426ff837c4e543d474e4eb082daaf62294f83", + "1071c68372cc5a01/q8": "53a0aca9a491009c4be3581138b902ad7558ec0408e22ad07db97f5e117789d4", + "1a9258c365988961/q1": "539357ab14ee57157f11df31138779046b75be18b25bc7311982c2b5cafe0131", + "1a9258c365988961/q2": "abefe61e96fa49c8135f8db509be2f7067a8d806600e498c42d9df6f65c5d21c", + "1a9258c365988961/q3": "aef1a11a77264a0bb7e62a3a53152d89dcfde9909818a2ce102b47cb9a4a075d", + "1a9258c365988961/q4": "f1584fbf6a67af1191d7dd016f99bda8591f9bb1f19523a39e0a1bd7f0a75cf0", + "1a9258c365988961/q5": "9fe087fd3f09bc10308c9d49a85f86052ebd487b95bb003c42f6146e0bade0c5", + "1a9258c365988961/q6": "b4cc0fb7e075f6111f2340761f1eaa512754c0adfa56134d836273000af780a2", + "1a9258c365988961/q7": "212ccf932935df2cdf4ee468b076c95bb7819b6c3aa8b4d8437e0f8b4e56f030", + "1a9258c365988961/q8": "887c126321d0c65234a797ffca30b041991d11406c593938383e67e644ba80cc", + "1cee58eb1f2d9c7e/q1": "b5f53221783f46bf4b48677ecdfb57ff61fc957c0ed3d6eaa82c0ba7c2537ba0", + "1cee58eb1f2d9c7e/q2": "b48274ff97e95d65816af64c76795de34bb907572e2a23f027d8915f64ad4438", + "1cee58eb1f2d9c7e/q3": "c07f88069853cf13bab721d7724c32a035161d64f3b310db45fa2ff42d06d7c4", + "1cee58eb1f2d9c7e/q4": "fb23ba5734172cb0950e2852e2e3dc0c74648a5ce29fb4919d64f59de7644957", + "1cee58eb1f2d9c7e/q5": "45de17ae380a042993ce0e35a7ab758fbec48b6f611a96d33152ee9f4b1e3784", + "1cee58eb1f2d9c7e/q6": "5e9402540d56d76f884b7238f5f78143e45d1a35af201bb4f8567bd830cde7a8", + "1cee58eb1f2d9c7e/q7": "9b15e23c6a26141a1bad871a661841dbfd47ba86e65323ad5b033f7f4c102a24", + "1cee58eb1f2d9c7e/q8": "d05f0dfeebaa33a31e07d0aaa3554da14fb1ecf67813513fdbf05b34903f5064", + "363510303b715d4c/q1": "a906b9645c2e39c8b80978b3e0d1e7db82aea2e237c98b57ed85789d9d2415fa", + "363510303b715d4c/q2": "b7667fe5253273f2ac9ce87cc7499fd9a5c8a867826086a325485bdd17c11302", + "363510303b715d4c/q3": "282bdb9fb48738a92247cbbc78df393ab20a563734eab58c320304e3032add73", + "363510303b715d4c/q4": "83e32a11070b0c1346bf2f2abefcfff4da6dfeec9192ac0da0b463815ad70151", + "363510303b715d4c/q5": "75479ed5d2bbe013882e9bb2664b9aeec81295e2773d1244cb7aa03390b1c93d", + "363510303b715d4c/q6": "96cb1f53d63c2ec31b8a2f728a5f344f0b2b040bc8690358d29d68e1d9ed06f2", + "363510303b715d4c/q7": "a99d443772ba469a5ce5c24c1c239926f616c72748bf449272f45c2deb0a21df", + "363510303b715d4c/q8": "fd765c223f317eb35de000295c7e3b5c30fe9da45aa00bfe8a340610db146cdf", + "37afcc89fbdcb76d/q1": "bc7aa7d1ecd595b61ab6021cc7c07cd78229b96f18631700b5aabc861c914013", + "37afcc89fbdcb76d/q2": "f748c65801498cd2867635504546eb9d69d0ea32f32f8f2c13069bfab9335e9f", + "37afcc89fbdcb76d/q3": "c025fefa6e8732458afa9eba4adaea628990d43cf25595af8ad25b9dc25e9059", + "37afcc89fbdcb76d/q4": "e44eaf74e380e30d1d83a7b9a3a64b85df53191d75d405d9b1d3d8daab6a83d6", + "37afcc89fbdcb76d/q5": "7bf2f897df821a4f80a6575883463c0bd2704a19436260b175d2dbdc43e00d6d", + "37afcc89fbdcb76d/q6": "76dde996af4523a80b95aa1a5fb1b1c9265d5fe70f0c8295d20c18c8c82d0018", + "37afcc89fbdcb76d/q7": "13dd579804e326c33337f3a33ae67c128d416e8f656b1853f6ab2e9af95696b9", + "37afcc89fbdcb76d/q8": "b466d02db4b8a63828c6a7c8b734dac450a4f4e3429e897cce27a3f223f7065f", + "3b46c71e3b92a563/q1": "00619a18fe0f9807ac14a1ef4cee32ce61e0e10a4053fea82cdd8e6ccd51e8d6", + "3b46c71e3b92a563/q2": "ab7128479cc7b9b0c8cae8a4e37afffc44a60b7dfe3ca2cbcb838c9c496dfb98", + "3b46c71e3b92a563/q3": "50f0f0ff18ac8c0aaa6f8cafe740ab83238be10a7098d42d80144637f2881b54", + "3b46c71e3b92a563/q4": "1f94d8b24d64a112d6f9207e137872eb066d56b8927e178c7756e39cfb30bb8c", + "3b46c71e3b92a563/q5": "ab4925b2d0e54806e4f1601b20eb33f962995ad2e246d5d7fb561e19260eeafd", + "3b46c71e3b92a563/q6": "7885f55247ebeb05078a36a897c198487d7b53b1e82e60bb99235ed5743729c4", + "3b46c71e3b92a563/q7": "79b2874ee3098b1ec83987f7e8c0f7d8c64ffcd43ea5afed070c6e068883d3ad", + "3b46c71e3b92a563/q8": "3fa8474bf57a6c6a8f0b0f07797402dec563e9e470190feb8f40b41055c99bbe", + "4f4b74a79237e311/q1": "62dc3edf15b7da20896286f5e7dbe0b4cddb19191e78e08ea4824b3d38d78ab6", + "4f4b74a79237e311/q2": "7df1494f7fb4db638c1d103a85537239f01838a2443b3c7affd1880e5c776e8a", + "4f4b74a79237e311/q3": "66b6ded9c8efcd73689f98e197ee2d1ba48bbd2d4383e06fd2086ea4b01896ab", + "4f4b74a79237e311/q4": "9244b9f5306c59a839b694806e8a7b3d8bb627cfe69c8b7844e28d4e0117ca97", + "4f4b74a79237e311/q5": "0466fc7a0e347ad84e7eec93aaf40d00c8319468441a8501414441ac348a1012", + "4f4b74a79237e311/q6": "9de786058e49e29620b3110ca56437d2621d9ff0e635fc88a35a5625df5abe9a", + "4f4b74a79237e311/q7": "f77a8e2e82fe3ce05a6a88b802b059718eaf9c796356ac3b55e167b17e68dd16", + "4f4b74a79237e311/q8": "6c7216244755b50c88c0c368445732f701f9886f0d187438dc569994f7e53543", + "55a94550ff18f324/q1": "6e813c670bdfb73f6d70733c1e1d28c74768bcb4f3e1f0315df8efc59e022e95", + "55a94550ff18f324/q2": "72f8b0e75afe56849ac8266ed855ac34bed4bd7cafd6db07433452e913187e43", + "55a94550ff18f324/q3": "78624d1678b1b6f17335d4f6130976dc7f3a7626452add9d83b7b1216d83fd55", + "55a94550ff18f324/q4": "8c550069dc27015a53ac6a724dba45a116e4863f08742b409fa5eaf0ac38aa0b", + "55a94550ff18f324/q5": "93c0e479ed3dd648edcc90ae3de49fdae9d81d1edf0e839096d9c4780492f2ac", + "55a94550ff18f324/q6": "ef50ccbe604724223ca1d8790ea5d0387888b231aec4f9e16a09cb7cff1bd2ab", + "55a94550ff18f324/q7": "42f60826bb18ba768ca48c07b435fdf55c24185353a63e98fc96976554ab0a3a", + "55a94550ff18f324/q8": "ca647a5101f451ecad2db27fea18c2a4f9f125179fc174294bfbf08f5fee0dd3", + "6a4361c2fc194feb/q1": "e2e4896dd65df19bfd4fd328f0e60ccb1186cab96a3792bfc53ee029bbf45a29", + "6a4361c2fc194feb/q2": "9104074f9358ef3ea2535c61d7771301510f625a7266e8c4532ca21024ab693e", + "6a4361c2fc194feb/q3": "89ad6b42c5fdf6e11acd22d84405ea00fd123476a4c6f88b84a2cf40bf671f6c", + "6a4361c2fc194feb/q4": "cf4bfc0db74b24178800795a99cc25c18f56148e8b8d34ee74cb0145c574d742", + "6a4361c2fc194feb/q5": "e6c3f96bf274766d0cc5b8df4e71d056fd1404781912622ae1783b3026d6125d", + "6a4361c2fc194feb/q6": "8f7cf19312f1ece9f810369f9d51773db350c2eb5051ebe139c4d311ed146892", + "6a4361c2fc194feb/q7": "b555c16130b218b16655fc7a1f642bc946c1f0bd18945bc0c779fd619ff18742", + "6a4361c2fc194feb/q8": "66f84d090bb098b7d9419ae865a5016834008d9e971d3b2351f3b62a4f3df88d", + "7538cbb80cb91036/q1": "4444b16ac8e996980f44ddd68b011dd6683103d380c6a29576b5d06259e99ea6", + "7538cbb80cb91036/q2": "22e3ee960f83619c924f8d9fcc9dbcb80d365988c17d88f360242c660a405463", + "7538cbb80cb91036/q3": "f3c1adead57b10c8d4292c7c7ab17d9b08314801e2ef2611c9e797749132ae84", + "7538cbb80cb91036/q4": "61952aa6980121e93ff64b39e9918f29ba9b5c864ee8244fda9a11ad64edcc74", + "7538cbb80cb91036/q5": "51dab43f25de2e50c7e9a08d5f84a788d901c40fa7d26ae93479b07b148d7eb1", + "7538cbb80cb91036/q6": "ed352b0ce32816e81ae315e2938c67cc2fa0b22d87303877184aab5c8d80d1cb", + "7538cbb80cb91036/q7": "8eaf4e13160e87549fa143c42a751fd8b9175a97b3d3c69e8ddf46a6b47d5ff8", + "7538cbb80cb91036/q8": "ed370c652dba633c74232937e69c0171a74e3126c84849b3867fd2836861df44", + "7936814b58b5387f/q1": "88e007a7eae35786c7e431c559fa0dec7dc4ca4d68b8dec8c6e43c630beb1948", + "7936814b58b5387f/q2": "5ee65645591db00948dae47ea1c22d2f682975014c56239a75269b88272950d5", + "7936814b58b5387f/q3": "d8fd56676f812d30318e7945f9df8371d7ecd637ea5c0bf0752b14c9a090797f", + "7936814b58b5387f/q4": "e07a3afd28083504b9c94337774099d31e59fd01be1f5ec87fccaa6c39324dab", + "7936814b58b5387f/q5": "da0eee09ab143564210fa94e0ae3b5d4886275df958c7d0d17771fed5a3f4b2f", + "7936814b58b5387f/q6": "d00f72b31db5b52b6301e5bee57297fdfb4f82bcd5850ab7800cfc301f0fa23e", + "7936814b58b5387f/q7": "caa5965eba574565ac024cd275a9d229dcebfad9aecdc2f8170c3f8b093da114", + "7936814b58b5387f/q8": "edb8afbf71131e330806afe2d453d4002a874c8102566050406afd056c7a7dd4", + "9dfb70f5867c29ff/q1": "0131f23d467d3f548ca75d9b03627f49b7b56e6e8314c1b127bbd32fcc2a595f", + "9dfb70f5867c29ff/q2": "163b88e30e2d2493a3c5253320a6c27a48c11fbe9ca47c60286e5318bd0c3b7e", + "9dfb70f5867c29ff/q3": "ead412164cf21b8d49a2ed9d6379ef4e3abbcd729a2b4df5d62e0ac9054e5597", + "9dfb70f5867c29ff/q4": "2c4076b5f7832f082fb1b89e5d5c82d2d423544f803d526980412a63c029a78d", + "9dfb70f5867c29ff/q5": "1bc6a4fdeafe50acef1bb3400ecaed69004c628dbac970d978bcf3119117b36a", + "9dfb70f5867c29ff/q6": "08e8977d431404c42891519a5c63f7f2cbc8c39c4f05dc5278adc48990d435c8", + "9dfb70f5867c29ff/q7": "0ea76b77b72a77679837fb9caa80d1f84a54debd5aff0692e3fb2dc3f35374da", + "9dfb70f5867c29ff/q8": "51cc3ed7e881cb828ca1ed1c48c2a9959f825e8137f19c6eb7bea5747faec75e", + "a56e27cbf5f843c0/q1": "8686efbe35e30ccc1e7a900bb16219fdba2626c31cfcd2f19ec71c40d167aaa1", + "a56e27cbf5f843c0/q2": "f4e116cbce9e11c5f420f2be136692f37bb1cfb5ace800d1a96888152bc232b6", + "a56e27cbf5f843c0/q3": "d11d4589b45d51d9e78d9b3a4222eeae1e64e949ddde91653f73560808c58a61", + "a56e27cbf5f843c0/q4": "e4e74f231e381ed6c486291c7d1fb42605b707b419d2b1400b2d997a4ea3cfd2", + "a56e27cbf5f843c0/q5": "aaf796a0de6389a766ba2d49293f3d9902cc001062134ef8c67bf7cd1b84801a", + "a56e27cbf5f843c0/q6": "af2b15c226c59b2bef590b9e9ecde1ecc3d85daa5c6d6bfc70b34bf59fa9e356", + "a56e27cbf5f843c0/q7": "d5fa387ada2f40d0a5cfb9ac070a1350ca321c7a6068c120482df28c3e895b1f", + "a56e27cbf5f843c0/q8": "7f05b8567c6a2c69dac693e6b48d9920b74f4897dc8bd78e924ed53f076ecb75", + "a89c7268ccd4718b/q1": "b9437fd63e215c232e7c8832f18f0f55fa2e081eae3f301d54a7aa97cc1fb295", + "a89c7268ccd4718b/q2": "ff02a7341333702358b9ce9ee3b454ef11766d41b9189af2d944147948c60516", + "a89c7268ccd4718b/q3": "5e73edc9fa78fdefd2d260859cd9a3e6992e5a9d776fab0695718501f03e7767", + "a89c7268ccd4718b/q4": "5939a1f349ef6a20e64b3b47c152bd24e0439d60bb4606ba2e998e4cb3b8697e", + "a89c7268ccd4718b/q5": "7735106280ac93e168db5813bbff0afba1bf4fabd6c1a590ccb6b711216cf8a8", + "a89c7268ccd4718b/q6": "0e90c9fd2abf3a27ad896ee9501798d1d447821ce1e244f969a914f417862439", + "a89c7268ccd4718b/q7": "1cd95740d3efba12a7d5a597db70f2b9285d03137a78f092e9edac6805bed2dd", + "a89c7268ccd4718b/q8": "f16e87a7c596aaef754e03332661d31c9173b1b61c6a06330e46cff78841958c", + "ac958597c82073f6/q1": "f4f36a9083717b52dddfde153ac077d77a1143ecd17082d4446711fd07bd0abb", + "ac958597c82073f6/q2": "ec77e55fb266e7c52078f38b31c0529927f43903dcb8445d72a18e3f91ae2109", + "ac958597c82073f6/q3": "99b1f5b71ef2e3a080d7a41007855adfe5310702305ff5c020ad79b696fa1329", + "ac958597c82073f6/q4": "f6e58960eba925a1eff03567e5e25e41e05ce3a86cf38732458251b4b0a81047", + "ac958597c82073f6/q5": "1000c5d6162e742101d4abf21cca7b11e476d43b7380c7f39fdfd5c5425338ad", + "ac958597c82073f6/q6": "ba24e6e003599627d7229b434c9b83be2b3e63125208819bacf416090d82b675", + "ac958597c82073f6/q7": "9fd3c8cf8dbebbe8c1a763eb45ff1047f8af22f6a176c97f0b2ff9360bfa6089", + "ac958597c82073f6/q8": "17d0e6e94cfbaa8172d06d8525ff5a964b02fa9f6af6a501749223e4cbdcd906", + "b7450b264b1b0a41/q1": "29c3569b5d32a921d0a2ec82394d5c1a1e1caa9bf64880d1e6142e79aab5e58b", + "b7450b264b1b0a41/q2": "622a7053b712a746600078eefa3fff876743c1441ac56f33cbdb3c71f330d1ed", + "b7450b264b1b0a41/q3": "54f7cb81ff79896f69d87c560654d3cc83ce1cddb1c58df223b8b0222e8f3af2", + "b7450b264b1b0a41/q4": "859c8e43c46f1808d60cae2aca9fd7e89b46907630f865474ad841add5bdb452", + "b7450b264b1b0a41/q5": "7813dd29cfb7a6fca617a1050914b8f3b546828e4c3367754f2ce9fefd50d52b", + "b7450b264b1b0a41/q6": "e2550241d983282dcb2e4bf175c9190fd1331125ef148be1ac6fe8aea823b13c", + "b7450b264b1b0a41/q7": "07c04760b56681c7af9a29ef1ff8b26f2d0a4c0acd45289b0a3777ea9316ba86", + "b7450b264b1b0a41/q8": "82c17f7d094bf95e16bfb1e6633cc6bac0372ef3d22c817f28f3a782cba74ddd", + "b77d3f006f42414b/q1": "3cc82f8d221858b36ee4656eb3c86ebae436789cf9d9ec027b77cda2331828e8", + "b77d3f006f42414b/q2": "c68b8089c2bd8d71ca753a1e7bfb3a40dac42a2eb490ee5bbeeca16bb3e1d4f5", + "b77d3f006f42414b/q3": "f0290ba8edfb4a4be1271e6d35c6b4f09aefbf357133c440a03d3cc92828ce91", + "b77d3f006f42414b/q4": "e6492e56320efaa13429daeb4fe791701bfb3bdd368b8061615d7156cb15fded", + "b77d3f006f42414b/q5": "1752fb8c159a03fe9029849cc7b1d91048d84136594fe7a5791a8f6349bbfafd", + "b77d3f006f42414b/q6": "0e90082550451a50c2d3d2850534fc628df207a7ba70a4dd66608b2b5ede1f7d", + "b77d3f006f42414b/q7": "fc6b44877d09b5302f027654bfd7505c83aff1e37c724d0de7a78248b0f04516", + "b77d3f006f42414b/q8": "3bfcd8d0c710873a07c621eec95d794f438a1cee530c3677418f2a538216b58e", + "ba23983c76b4832e/q1": "f4ec7fb82bed71af07e88d26d6e379468fa924ce313bd1dab65a6b58b26790b7", + "ba23983c76b4832e/q2": "f362a3dbf20e80e52b2261ca9470007bbe948f88f0f032a24551c97b162dce2b", + "ba23983c76b4832e/q3": "a658caadf916f2e9c8613a58b5aa39f3090358bc145575c498d3129f0257cddc", + "ba23983c76b4832e/q4": "6ca2f8fdb0ceddbbfdcf8b22b8f860977bd9d0b4309d9eceeb75885406573c98", + "ba23983c76b4832e/q5": "6c072fcc663f6f9d630671cc8a229c29c52c34cebae4b5d3b81c4bb62aab482a", + "ba23983c76b4832e/q6": "bbd3d8386277ebfe18a3063e6042e907fed313acaa57f4b128b1a215ac0d9bbe", + "ba23983c76b4832e/q7": "caeed0acb9ecd3057d0a74f58885d95b4d345d90643be3219fe80f0a0666e625", + "ba23983c76b4832e/q8": "11a6c9141cd686a121122181de99262a85ef59a76f32f18968b01522200cc02a", + "bc34a3ce58dea09d/q1": "7b0cb74fc63e23e825c5544ff74feedd8e74edcc7cf4242f48677d16987f539f", + "bc34a3ce58dea09d/q2": "7548700dfe2d663fd46bedb7cdacb82ea3e06e1060dc6114c6cab6a24c662058", + "bc34a3ce58dea09d/q3": "f2c449095c1d5e79283777fe1fa209a27a28710d439e4d3a39129c7e0bc28ac5", + "bc34a3ce58dea09d/q4": "5da7032ec303a3ada3c2fdf7c7e159a090d269097ddc639f66a2427b58adef43", + "bc34a3ce58dea09d/q5": "8e540dd938bd3fb3d410b87ac4355db7bad43bd9c8b50ebb27fc42ed539dae39", + "bc34a3ce58dea09d/q6": "a9496ca488b6f47f98e9fe3491c1f852f93f90978d25f8dc6ab55174506b80ed", + "bc34a3ce58dea09d/q7": "4a624e2d9ccb71dcd0be25d5be2fc3a89d427eaa65b26cd8c631a6b47cc0ebe4", + "bc34a3ce58dea09d/q8": "2270859afa2e2f54add149939f07464630abd9bcda948d3ea5588b8067884443", + "d78c37c2f04f2376/q1": "bcc4f3eb51c1de9631ff36427c9cd360d4bf17bb41c8f1976b9adc06e612cd55", + "d78c37c2f04f2376/q2": "f07f00cfe2b15d4bedc354210fda1d1d189306db7f0e25700899c72c9bd48128", + "d78c37c2f04f2376/q3": "5511cdf563effd0abe09af8ec9ece0dabc6870d55258c1f1c049a15f47972b04", + "d78c37c2f04f2376/q4": "0f64ebf9a485156e9418f08c1818b238a4724e8d8c18989b24a983b168de6be2", + "d78c37c2f04f2376/q5": "1698124c1c086fb22aae9b2925e296b95e4f495e404a1d261dfac516fe42bafd", + "d78c37c2f04f2376/q6": "25811a8f92f939956be55f1c597af4a61d609635775e2f5359915c2c8053b89c", + "d78c37c2f04f2376/q7": "2946762779073705c14441b1908cae5ae2d56bb8bdafab39fcebcd528ec07313", + "d78c37c2f04f2376/q8": "dc23a0743f2cb80b3ce27c64552223f27170b397a4333a3c7b5dd3c00353fc12", + "e25ca1ff2f0c0cb5/q1": "b68a28b89f8851b156495278eb1f1f4b48b87f0e10b4fac70db643f04e799eaa", + "e25ca1ff2f0c0cb5/q2": "e4c6be5ef1b689fca645754940c0e81914b35d1ba57a54fc8c2a5ee6f37d0c76", + "e25ca1ff2f0c0cb5/q3": "fd38252d61f0cf71166945ac8be6734836ea6bb32cba774e1f99ea823db8b333", + "e25ca1ff2f0c0cb5/q4": "06f45162bb4b63d62df82adc0f63c367c1b751b14251b35c383eb7820f4fe23a", + "e25ca1ff2f0c0cb5/q5": "83c0a9de4c96c5d17d83fdb4962c6e39b2cfb060416a2ad8fbddc0ebe56cdb36", + "e25ca1ff2f0c0cb5/q6": "988031dd17726400441e3ae8c74a4f36d8ebd6ec8c478f3227e056f70552eab2", + "e25ca1ff2f0c0cb5/q7": "105e29d5c5ab450de516a6f9b05798d52135241897d21c46e2973d67238c7962", + "e25ca1ff2f0c0cb5/q8": "03642aebaa6c35feceb6a23c1a241cce7d05dec7eaff1f333c5732d8498f8e96", + "e3111a2fd4da24af/q1": "43f03746b60ff74873dee826a5e94923a4ee2515d607115512969b1818ec96d6", + "e3111a2fd4da24af/q2": "f73019841469c38347709223e2fefbab5e4fa78a0f93ab1479c1649308b06db2", + "e3111a2fd4da24af/q3": "ce7cc0ab81955bc1818d605e38023db40a1b8a24f57bd14c34eed3177b20e8a7", + "e3111a2fd4da24af/q4": "8922e23ff010fe0e34b0711dda6e407a214737848468047b6c92c29df94257df", + "e3111a2fd4da24af/q5": "949eb2c20a49b003d1759ade9435f311d224df58a074fc9dfa9c831f384605ba", + "e3111a2fd4da24af/q6": "4eabdec04d60dcbadbfc568f830220e908a46705e9e64930faf1bcf009f89db9", + "e3111a2fd4da24af/q7": "430b026e17a5aef863008ae4e912fb8da9d5a74c7b7c5d133cdfb8f3c6199f6f", + "e3111a2fd4da24af/q8": "07519bbad31554a7b6e6626ad57417e4dd6c559a78f5cbce0af6d828419d09f1", + "e3b946107c5d3441/q1": "347fc0f8ec32b56d6da03c17df64823c071abd36d3714f676517c1f95ddd1222", + "e3b946107c5d3441/q2": "75b30a9ffdd22b1fef52340fe798e8e43c0193a66c4767dd00e6946b04acdfb3", + "e3b946107c5d3441/q3": "85ec50d16053fed720dd4476c81ab35389c4830156443e99cb7eb74e18c25f79", + "e3b946107c5d3441/q4": "833a867437d2086f72c57bccdf9b87c55f01235410767fa7dacd8565c9f209c9", + "e3b946107c5d3441/q5": "f6f4ccdca324002eb5c296f7b44693f9d5c43ae1d823076d3ce9f3f1a0332259", + "e3b946107c5d3441/q6": "4b0312c8cad2fa20d8041e8b4e989aaa66df29e5230f48585853e538dc6ffec0", + "e3b946107c5d3441/q7": "5bf4a28411d58371785550a6bbe142b389c359db115a88e57b6055d557fe392f", + "e3b946107c5d3441/q8": "348d2f07314e1a4facd008492ba932cfed21cac0d6a1bf55d1d744a6d58b9cf1" + } } }, "measured": "2026-10-22", @@ -14605,6 +17710,27 @@ "missing": [], "refused": 0 }, + "linux-x86_64-b-cpu-isa-avx2": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 + }, + "linux-x86_64-b-cpu-isa-avx2-vnni": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 + }, + "linux-x86_64-b-cpu-isa-wide": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 + }, "linux-x86_64-b-gpu-vulkan": { "decodes": 192, "differ": 0, @@ -14619,6 +17745,41 @@ "missing": [], "refused": 0 }, + "linux-x86_64-cpu-isa-avx2": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 + }, + "linux-x86_64-cpu-isa-avx2-vnni": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 + }, + "linux-x86_64-cpu-isa-avx512": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 + }, + "linux-x86_64-cpu-isa-avx512-vnni": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 + }, + "linux-x86_64-cpu-isa-wide": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 + }, "linux-x86_64-gpu-vulkan": { "decodes": 192, "differ": 0, @@ -14626,6 +17787,13 @@ "missing": [], "refused": 0 }, + "linux-x86_64-gpu-vulkan-isa-avx512-vnni": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 + }, "macos-arm64-cpu": { "decodes": 192, "differ": 0, @@ -14640,6 +17808,27 @@ "missing": [], "refused": 0 }, + "macos-arm64-cpu-1t-isa-neon": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 + }, + "macos-arm64-cpu-isa-neon": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 + }, + "macos-arm64-cpu-isa-wide": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 + }, "macos-arm64-gpu-metal": { "decodes": 192, "differ": 0, @@ -14647,6 +17836,13 @@ "missing": [], "refused": 0 }, + "macos-arm64-gpu-metal-isa-neon": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 + }, "wasm32-wasip2": { "decodes": 192, "differ": 0, @@ -14654,19 +17850,33 @@ "missing": [], "refused": 0 }, + "wasm32-wasip2-isa-wide": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 + }, "wasm32-wasip2-x86_64": { "decodes": 192, "differ": 0, "identical": 192, "missing": [], "refused": 0 + }, + "wasm32-wasip2-x86_64-isa-wide": { + "decodes": 192, + "differ": 0, + "identical": 192, + "missing": [], + "refused": 0 } }, "integer_cross_platform_total": { - "decodes": 1728, + "decodes": 4608, "differ": 0, - "identical": 1728, - "platforms": 9, + "identical": 4608, + "platforms": 24, "refused": 0 }, "per_quality": { @@ -14968,6 +18178,198 @@ } }, "timing": { + "linux-b-int-cpu-q2-after": { + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 6.31787109375, + "mean": 5.411690848214286, + "median": 5.305908203125, + "min": 4.552734375, + "n": 14 + }, + "ms_per_decode": { + "max": 203.618790625, + "mean": 175.13868107142858, + "median": 170.168089, + "min": 168.93253575, + "n": 7 + }, + "rapl": { + "live_rounds": 7, + "marginal_j_per_decode": { + "max": 11.91398955217119, + "mean": 11.30112847909788, + "median": 11.55542563825561, + "min": 9.30512505473254, + "n": 7 + }, + "publishable": false, + "ratio": { + "max": 34.59609858039102, + "mean": 18.77466579044488, + "median": 12.057129843960505, + "min": 3.172305433752332, + "n": 7 + }, + "usable_rounds": 7, + "verdicts": [ + "relative-only", + "relative-only", + "clean", + "clean", + "clean", + "clean", + "clean" + ] + }, + "rounds": 7 + }, + "linux-b-int-cpu-q2-before": { + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 8.51416015625, + "mean": 5.539271763392857, + "median": 5.64111328125, + "min": 2.14208984375, + "n": 14 + }, + "ms_per_decode": { + "max": 716.311537375, + "mean": 711.7465751964285, + "median": 714.047132875, + "min": 704.196416875, + "n": 7 + }, + "rapl": { + "live_rounds": 7, + "marginal_j_per_decode": { + "max": 44.298017107683236, + "mean": 43.68857578226793, + "median": 43.83703099920065, + "min": 42.70937240219585, + "n": 7 + }, + "publishable": true, + "ratio": { + "max": 158.26833999599046, + "mean": 90.6223525418004, + "median": 76.92328605144846, + "min": 42.50152607722624, + "n": 7 + }, + "usable_rounds": 7, + "verdicts": [ + "clean", + "clean", + "clean", + "clean", + "clean", + "clean", + "clean" + ] + }, + "rounds": 7 + }, + "linux-b-int-cpu-q6-after": { + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 10.58984375, + "mean": 8.646798270089286, + "median": 9.196533203125, + "min": 6.28662109375, + "n": 14 + }, + "ms_per_decode": { + "max": 301.058542875, + "mean": 295.3070313035714, + "median": 294.2647715, + "min": 292.3225405, + "n": 7 + }, + "rapl": { + "live_rounds": 7, + "marginal_j_per_decode": { + "max": 20.200095530988946, + "mean": 19.649060078445853, + "median": 19.96514412401524, + "min": 18.830436308330306, + "n": 7 + }, + "publishable": true, + "ratio": { + "max": 226.79401075620092, + "mean": 109.01265685977195, + "median": 100.77265867650837, + "min": 36.57608099685956, + "n": 7 + }, + "usable_rounds": 7, + "verdicts": [ + "clean", + "clean", + "clean", + "clean", + "clean", + "clean", + "clean" + ] + }, + "rounds": 7 + }, + "linux-b-int-cpu-q6-before": { + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 11.8896484375, + "mean": 9.783063616071429, + "median": 9.995361328125, + "min": 6.43017578125, + "n": 14 + }, + "ms_per_decode": { + "max": 1598.859785625, + "mean": 1582.426545, + "median": 1582.12825175, + "min": 1555.6200745, + "n": 7 + }, + "rapl": { + "live_rounds": 7, + "marginal_j_per_decode": { + "max": 87.9470165514984, + "mean": 85.2563399834401, + "median": 84.91666904673801, + "min": 83.95295104597457, + "n": 7 + }, + "publishable": true, + "ratio": { + "max": 116.70614767370631, + "mean": 38.46007578754563, + "median": 27.103157528639382, + "min": 14.200800421446278, + "n": 7 + }, + "usable_rounds": 7, + "verdicts": [ + "clean", + "clean", + "clean", + "clean", + "clean", + "clean", + "clean" + ] + }, + "rounds": 7 + }, "linux-float-cpu-q2": { "decodes_per_round": [ 8 @@ -15016,6 +18418,54 @@ }, "rounds": 7 }, + "linux-float-cpu-q2-again": { + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 4.3291015625, + "mean": 3.2581263950892856, + "median": 3.22998046875, + "min": 2.31201171875, + "n": 14 + }, + "ms_per_decode": { + "max": 139.702198625, + "mean": 132.23214383928573, + "median": 132.161334, + "min": 123.209109875, + "n": 7 + }, + "rapl": { + "live_rounds": 7, + "marginal_j_per_decode": { + "max": 9.331129019102107, + "mean": 9.221351169278249, + "median": 9.2567916374487, + "min": 9.002951380050522, + "n": 7 + }, + "publishable": true, + "ratio": { + "max": 28.935426728747245, + "mean": 22.84957996129893, + "median": 26.642327535799964, + "min": 11.751804397780619, + "n": 7 + }, + "usable_rounds": 7, + "verdicts": [ + "clean", + "clean", + "clean", + "clean", + "clean", + "clean", + "clean" + ] + }, + "rounds": 7 + }, "linux-float-cpu-q6": { "decodes_per_round": [ 8 @@ -15064,6 +18514,54 @@ }, "rounds": 7 }, + "linux-float-cpu-q6-again": { + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 8.35400390625, + "mean": 7.277448381696429, + "median": 7.16064453125, + "min": 6.439453125, + "n": 14 + }, + "ms_per_decode": { + "max": 226.087378625, + "mean": 211.60572791071428, + "median": 210.7395815, + "min": 200.767790125, + "n": 7 + }, + "rapl": { + "live_rounds": 7, + "marginal_j_per_decode": { + "max": 14.405061943071814, + "mean": 14.054345538634882, + "median": 14.029845358090155, + "min": 13.651424928393718, + "n": 7 + }, + "publishable": true, + "ratio": { + "max": 37.46248026277542, + "mean": 27.523171716903942, + "median": 29.49929921641033, + "min": 20.629605824114357, + "n": 7 + }, + "usable_rounds": 7, + "verdicts": [ + "clean", + "clean", + "clean", + "clean", + "clean", + "clean", + "clean" + ] + }, + "rounds": 7 + }, "linux-int-cpu-q2": { "decodes_per_round": [ 8 @@ -15112,6 +18610,150 @@ }, "rounds": 7 }, + "linux-int-cpu-q2-after": { + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 5.021484375, + "mean": 3.729945591517857, + "median": 3.700927734375, + "min": 2.759765625, + "n": 14 + }, + "ms_per_decode": { + "max": 93.128922875, + "mean": 92.41553798214285, + "median": 92.877639125, + "min": 90.801236, + "n": 7 + }, + "rapl": { + "live_rounds": 7, + "marginal_j_per_decode": { + "max": 7.498649401719005, + "mean": 7.355090500205796, + "median": 7.305980228987391, + "min": 7.288426494213159, + "n": 7 + }, + "publishable": true, + "ratio": { + "max": 39.695034991220425, + "mean": 36.33742590857145, + "median": 35.58180302420941, + "min": 34.141298249561714, + "n": 7 + }, + "usable_rounds": 7, + "verdicts": [ + "clean", + "clean", + "clean", + "clean", + "clean", + "clean", + "clean" + ] + }, + "rounds": 7 + }, + "linux-int-cpu-q2-after-avx2": { + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 5.15380859375, + "mean": 3.9725864955357144, + "median": 4.01123046875, + "min": 2.9453125, + "n": 14 + }, + "ms_per_decode": { + "max": 102.987234125, + "mean": 101.8298362142857, + "median": 101.592205375, + "min": 101.347894875, + "n": 7 + }, + "rapl": { + "live_rounds": 7, + "marginal_j_per_decode": { + "max": 9.349684200823756, + "mean": 9.275578813038564, + "median": 9.26721775510252, + "min": 9.22425694647209, + "n": 7 + }, + "publishable": true, + "ratio": { + "max": 41.428640879418836, + "mean": 35.83544312815468, + "median": 36.32944187385824, + "min": 31.288851609074506, + "n": 7 + }, + "usable_rounds": 7, + "verdicts": [ + "clean", + "clean", + "clean", + "clean", + "clean", + "clean", + "clean" + ] + }, + "rounds": 7 + }, + "linux-int-cpu-q2-before": { + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 5.35693359375, + "mean": 2.8844517299107144, + "median": 2.404541015625, + "min": 1.78466796875, + "n": 14 + }, + "ms_per_decode": { + "max": 346.4173055, + "mean": 345.9116766428571, + "median": 345.79784875, + "min": 345.157087375, + "n": 7 + }, + "rapl": { + "live_rounds": 7, + "marginal_j_per_decode": { + "max": 42.32675059394088, + "mean": 42.25652987285611, + "median": 42.25068273607481, + "min": 42.171026263776646, + "n": 7 + }, + "publishable": true, + "ratio": { + "max": 54.21884066239948, + "mean": 44.74364560230337, + "median": 42.63695813561114, + "min": 38.52671125638245, + "n": 7 + }, + "usable_rounds": 7, + "verdicts": [ + "clean", + "clean", + "clean", + "clean", + "clean", + "clean", + "clean" + ] + }, + "rounds": 7 + }, "linux-int-cpu-q6": { "decodes_per_round": [ 8 @@ -15160,6 +18802,150 @@ }, "rounds": 7 }, + "linux-int-cpu-q6-after": { + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 10.94482421875, + "mean": 9.550467354910714, + "median": 9.491455078125, + "min": 7.83740234375, + "n": 14 + }, + "ms_per_decode": { + "max": 147.711069375, + "mean": 144.9237529642857, + "median": 144.8073045, + "min": 142.66714075, + "n": 7 + }, + "rapl": { + "live_rounds": 7, + "marginal_j_per_decode": { + "max": 12.576633003161303, + "mean": 12.435582049526337, + "median": 12.434038772355052, + "min": 12.28211975599754, + "n": 7 + }, + "publishable": true, + "ratio": { + "max": 42.491565879249976, + "mean": 27.583666243081776, + "median": 18.498999031649237, + "min": 17.230869759195638, + "n": 7 + }, + "usable_rounds": 7, + "verdicts": [ + "clean", + "clean", + "clean", + "clean", + "clean", + "clean", + "clean" + ] + }, + "rounds": 7 + }, + "linux-int-cpu-q6-after-avx2": { + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 3.5400390625, + "mean": 2.6285923549107144, + "median": 2.5869140625, + "min": 1.845703125, + "n": 14 + }, + "ms_per_decode": { + "max": 166.802687, + "mean": 165.9347685892857, + "median": 166.3103665, + "min": 164.711547375, + "n": 7 + }, + "rapl": { + "live_rounds": 7, + "marginal_j_per_decode": { + "max": 16.526754326438798, + "mean": 16.448848510253942, + "median": 16.462736340229185, + "min": 16.361402306231035, + "n": 7 + }, + "publishable": true, + "ratio": { + "max": 50.91843323597076, + "mean": 45.561823882438425, + "median": 44.73399075519865, + "min": 42.29223501960529, + "n": 7 + }, + "usable_rounds": 7, + "verdicts": [ + "clean", + "clean", + "clean", + "clean", + "clean", + "clean", + "clean" + ] + }, + "rounds": 7 + }, + "linux-int-cpu-q6-before": { + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 12.48828125, + "mean": 8.603480747767858, + "median": 8.598876953125, + "min": 4.15673828125, + "n": 14 + }, + "ms_per_decode": { + "max": 729.76330475, + "mean": 727.843071875, + "median": 728.036704875, + "min": 725.824944375, + "n": 7 + }, + "rapl": { + "live_rounds": 7, + "marginal_j_per_decode": { + "max": 92.66682489822979, + "mean": 92.20459799092066, + "median": 92.13604393438455, + "min": 91.95616077568707, + "n": 7 + }, + "publishable": true, + "ratio": { + "max": 59.20403134376092, + "mean": 36.07066810364132, + "median": 25.68800653051314, + "min": 22.545163475834574, + "n": 7 + }, + "usable_rounds": 7, + "verdicts": [ + "clean", + "clean", + "clean", + "clean", + "clean", + "clean", + "clean" + ] + }, + "rounds": 7 + }, "linux-int-gpu-q2": { "decodes_per_round": [ 8 @@ -15297,6 +19083,47 @@ "n": 7 } }, + "macos-float-cpu-q2-again": { + "billed_j_per_decode": { + "max": 0.0, + "mean": 0.0, + "median": 0.0, + "min": 0.0, + "n": 7 + }, + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 46.7666015625, + "mean": 42.13319614955357, + "median": 41.761474609375, + "min": 39.9404296875, + "n": 14 + }, + "ms_per_decode": { + "max": 330.544541625, + "mean": 216.3207782857143, + "median": 212.491635375, + "min": 140.412890625, + "n": 7 + }, + "rounds": 7, + "task_cpu_ms_per_decode": { + "max": 592.4307552083335, + "mean": 471.4041376488096, + "median": 450.0493697916665, + "min": 350.998739583334, + "n": 7 + }, + "task_j_per_decode": { + "max": 1.074406000375, + "mean": 0.9426954208214287, + "median": 0.923395809125, + "min": 0.8113225165, + "n": 7 + } + }, "macos-float-cpu-q6": { "billed_j_per_decode": { "max": 0.0, @@ -15338,6 +19165,47 @@ "n": 7 } }, + "macos-float-cpu-q6-again": { + "billed_j_per_decode": { + "max": 0.0, + "mean": 0.0, + "median": 0.0, + "min": 0.0, + "n": 7 + }, + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 61.3349609375, + "mean": 55.328055245535715, + "median": 57.103515625, + "min": 45.6298828125, + "n": 14 + }, + "ms_per_decode": { + "max": 598.72656775, + "mean": 365.55459673214284, + "median": 271.273072875, + "min": 215.70725, + "n": 7 + }, + "rounds": 7, + "task_cpu_ms_per_decode": { + "max": 694.2429479166665, + "mean": 584.293703125, + "median": 602.5459114583335, + "min": 501.5654895833335, + "n": 7 + }, + "task_j_per_decode": { + "max": 1.513852832375, + "mean": 1.3316935545535713, + "median": 1.27523351675, + "min": 1.2190905145, + "n": 7 + } + }, "macos-float-gpu-q2": { "billed_j_per_decode": { "max": 0.0, @@ -15519,6 +19387,88 @@ "n": 7 } }, + "macos-int-cpu-q2-after": { + "billed_j_per_decode": { + "max": 0.0, + "mean": 0.0, + "median": 0.0, + "min": 0.0, + "n": 7 + }, + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 46.0107421875, + "mean": 42.39100864955357, + "median": 44.16796875, + "min": 32.42138671875, + "n": 14 + }, + "ms_per_decode": { + "max": 385.021046875, + "mean": 253.90987498214284, + "median": 274.056776125, + "min": 98.28975, + "n": 7 + }, + "rounds": 7, + "task_cpu_ms_per_decode": { + "max": 1250.7815520833335, + "mean": 1198.005015625, + "median": 1199.969453125, + "min": 1132.3904895833339, + "n": 7 + }, + "task_j_per_decode": { + "max": 3.11988333, + "mean": 2.9576290205714284, + "median": 2.960101480125, + "min": 2.831257871375, + "n": 7 + } + }, + "macos-int-cpu-q2-before": { + "billed_j_per_decode": { + "max": 0.0, + "mean": 0.0, + "median": 0.0, + "min": 0.0, + "n": 7 + }, + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 41.8701171875, + "mean": 37.376743861607146, + "median": 36.19677734375, + "min": 33.625, + "n": 14 + }, + "ms_per_decode": { + "max": 338.2851875, + "mean": 172.40344344642858, + "median": 130.917890625, + "min": 106.20200525, + "n": 7 + }, + "rounds": 7, + "task_cpu_ms_per_decode": { + "max": 1784.0940052083336, + "mean": 1666.9424144345237, + "median": 1664.188677083333, + "min": 1533.02984375, + "n": 7 + }, + "task_j_per_decode": { + "max": 4.9039551885, + "mean": 4.732894807625001, + "median": 4.756581007875, + "min": 4.47185531475, + "n": 7 + } + }, "macos-int-cpu-q6": { "billed_j_per_decode": { "max": 0.0, @@ -15560,6 +19510,88 @@ "n": 7 } }, + "macos-int-cpu-q6-after": { + "billed_j_per_decode": { + "max": 0.0, + "mean": 0.0, + "median": 0.0, + "min": 0.0, + "n": 7 + }, + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 61.5234375, + "mean": 51.65087890625, + "median": 56.65380859375, + "min": 31.779296875, + "n": 14 + }, + "ms_per_decode": { + "max": 909.30403125, + "mean": 526.8106927142857, + "median": 477.4505365, + "min": 392.103776, + "n": 7 + }, + "rounds": 7, + "task_cpu_ms_per_decode": { + "max": 2514.32740625, + "mean": 2408.118041666666, + "median": 2382.575390625, + "min": 2327.572109375, + "n": 7 + }, + "task_j_per_decode": { + "max": 6.62603744725, + "mean": 6.41861390175, + "median": 6.434350634625, + "min": 6.123745524, + "n": 7 + } + }, + "macos-int-cpu-q6-before": { + "billed_j_per_decode": { + "max": 0.0, + "mean": 0.0, + "median": 0.0, + "min": 0.0, + "n": 7 + }, + "decodes_per_round": [ + 8 + ], + "load_avg_1min": { + "max": 46.78857421875, + "mean": 39.11363002232143, + "median": 38.6630859375, + "min": 33.4931640625, + "n": 14 + }, + "ms_per_decode": { + "max": 408.95956775, + "mean": 296.2571532678571, + "median": 268.1332395, + "min": 228.411010375, + "n": 7 + }, + "rounds": 7, + "task_cpu_ms_per_decode": { + "max": 3702.307703125, + "mean": 3497.9631004464286, + "median": 3462.708265625, + "min": 3407.762322916666, + "n": 7 + }, + "task_j_per_decode": { + "max": 11.1608135355, + "mean": 11.02529080325, + "median": 11.009661911875, + "min": 10.934179495875, + "n": 7 + } + }, "macos-int-gpu-q2": { "billed_j_per_decode": { "max": 0.0, @@ -15703,6 +19735,1814 @@ } }, "timing": [ + { + "child": [ + "nice", + "-n", + "19", + "idf/bin/native.c3", + "serve", + "idf/work/jobs.energy.q2.tsv" + ], + "counters": [ + "rapl" + ], + "host": { + "machine": "x86_64", + "node_cpu": 20, + "system": "Linux" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "linux-b-int-cpu-q2-after", + "meta": { + "fabric": "CPU", + "hardware": "x86_64 laptop CPU, 14 cores, 20 threads, AVX2", + "kernels": "narrow, kernel set auto", + "measured": "2026-10-25", + "nice": 19, + "path": "integer", + "pictures": 8, + "quality": 2, + "system": "Linux 7.0.0-38-generic", + "threads": "all (20)" + }, + "rounds": [ + { + "child_wall_ns": 1359561990, + "decodes": 8, + "load_avg_after": [ + 6.31787109375, + 2.65380859375, + 1.4228515625 + ], + "load_avg_before": [ + 6.31787109375, + 2.65380859375, + 1.4228515625 + ], + "rapl": { + "busy": { + "advanced": 13, + "intervals": 13, + "longest_run": 13, + "mean_w": 72.3831681493554, + "sd_w": 9.323274023673338, + "span_ns": 1305327855 + }, + "covered_ns": 1505777743, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 29.918938260683184, + "sd_w": 17.46300182901763, + "span_ns": 4810188319 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 3.074864015407014, + "sd_w": 0.5737427169453039, + "span_ns": 4916106747 + }, + "marginal_nj": 74441000437.86032, + "ratio": 3.200266916220906, + "verdict": "relative-only", + "window_nj": 99281667000 + }, + "window_ns": 1359742492 + }, + { + "child_wall_ns": 1628950325, + "decodes": 8, + "load_avg_after": [ + 5.74609375, + 2.6494140625, + 1.4345703125 + ], + "load_avg_before": [ + 5.8115234375, + 2.609375, + 1.4150390625 + ], + "rapl": { + "busy": { + "advanced": 16, + "intervals": 16, + "longest_run": 16, + "mean_w": 73.00518749322633, + "sd_w": 7.0382829479522435, + "span_ns": 1610760752 + }, + "covered_ns": 1811217941, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.295485542130384, + "sd_w": 0.7674022039723761, + "span_ns": 4908137886 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 29.918938260683184, + "sd_w": 17.46300182901763, + "span_ns": 4810188319 + }, + "marginal_nj": 92196708913.1833, + "ratio": 3.172305433752332, + "verdict": "relative-only", + "window_nj": 124087207000 + }, + "window_ns": 1629085321 + }, + { + "child_wall_ns": 1361344712, + "decodes": 8, + "load_avg_after": [ + 5.28564453125, + 2.60498046875, + 1.4267578125 + ], + "load_avg_before": [ + 5.28564453125, + 2.60498046875, + 1.4267578125 + ], + "rapl": { + "busy": { + "advanced": 12, + "intervals": 12, + "longest_run": 12, + "mean_w": 74.60179938908436, + "sd_w": 8.08115882247521, + "span_ns": 1209665938 + }, + "covered_ns": 1410177669, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.32902900991572, + "sd_w": 2.0028137551999707, + "span_ns": 4916261570 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.295485542130384, + "sd_w": 0.7674022039723761, + "span_ns": 4908137886 + }, + "marginal_nj": 95311916417.36952, + "ratio": 34.59609858039102, + "verdict": "clean", + "window_nj": 102803143000 + }, + "window_ns": 1361537853 + }, + { + "child_wall_ns": 1355657765, + "decodes": 8, + "load_avg_after": [ + 4.8623046875, + 2.5615234375, + 1.4189453125 + ], + "load_avg_before": [ + 4.8623046875, + 2.5615234375, + 1.4189453125 + ], + "rapl": { + "busy": { + "advanced": 12, + "intervals": 12, + "longest_run": 12, + "mean_w": 73.97001663678543, + "sd_w": 8.00670549588015, + "span_ns": 1204098882 + }, + "covered_ns": 1404467968, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 4.702601344933188, + "sd_w": 0.9946315284927512, + "span_ns": 4908937105 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.32902900991572, + "sd_w": 2.0028137551999707, + "span_ns": 4916261570 + }, + "marginal_nj": 92720695249.89912, + "ratio": 34.42866381376348, + "verdict": "clean", + "window_nj": 99765247000 + }, + "window_ns": 1355853727 + }, + { + "child_wall_ns": 1386416541, + "decodes": 8, + "load_avg_after": [ + 5.7900390625, + 2.8251953125, + 1.5166015625 + ], + "load_avg_before": [ + 4.552734375, + 2.53515625, + 1.41650390625 + ], + "rapl": { + "busy": { + "advanced": 13, + "intervals": 13, + "longest_run": 13, + "mean_w": 73.48373888881736, + "sd_w": 9.285110232232684, + "span_ns": 1303894444 + }, + "covered_ns": 1504356742, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.24166927367504, + "sd_w": 2.130773934665336, + "span_ns": 4909214650 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 4.702601344933188, + "sd_w": 0.9946315284927512, + "span_ns": 4908937105 + }, + "marginal_nj": 94772375725.3121, + "ratio": 32.15338918169835, + "verdict": "clean", + "window_nj": 102252241000 + }, + "window_ns": 1386530363 + }, + { + "child_wall_ns": 1364374521, + "decodes": 8, + "load_avg_after": [ + 5.326171875, + 2.77783203125, + 1.50830078125 + ], + "load_avg_before": [ + 5.326171875, + 2.77783203125, + 1.50830078125 + ], + "rapl": { + "busy": { + "advanced": 13, + "intervals": 13, + "longest_run": 13, + "mean_w": 72.62599799294892, + "sd_w": 9.304964543932263, + "span_ns": 1304946893 + }, + "covered_ns": 1505440676, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 6.430198187129499, + "sd_w": 5.65308139189029, + "span_ns": 4910079102 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.24166927367504, + "sd_w": 2.130773934665336, + "span_ns": 4909214650 + }, + "marginal_nj": 90977092979.81201, + "ratio": 11.814806763327573, + "verdict": "clean", + "window_nj": 99762745000 + }, + "window_ns": 1364588917 + }, + { + "child_wall_ns": 1351460286, + "decodes": 8, + "load_avg_after": [ + 5.1396484375, + 2.78173828125, + 1.5166015625 + ], + "load_avg_before": [ + 5.1396484375, + 2.78173828125, + 1.5166015625 + ], + "rapl": { + "busy": { + "advanced": 12, + "intervals": 12, + "longest_run": 12, + "mean_w": 72.97198823439753, + "sd_w": 8.319275670731466, + "span_ns": 1204835511 + }, + "covered_ns": 1405469033, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 3.1939055606691555, + "sd_w": 1.436200553831882, + "span_ns": 4912726876 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 6.430198187129499, + "sd_w": 5.65308139189029, + "span_ns": 4910079102 + }, + "marginal_nj": 92443405106.04488, + "ratio": 12.057129843960505, + "verdict": "clean", + "window_nj": 99206595000 + }, + "window_ns": 1351620286 + } + ], + "sample_s": 0.1 + }, + { + "child": [ + "nice", + "-n", + "19", + "idf/bin/base", + "serve", + "idf/work/jobs.energy.q2.tsv" + ], + "counters": [ + "rapl" + ], + "host": { + "machine": "x86_64", + "node_cpu": 20, + "system": "Linux" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "linux-b-int-cpu-q2-before", + "meta": { + "fabric": "CPU", + "hardware": "x86_64 laptop CPU, 14 cores, 20 threads, AVX2", + "kernels": "the record's (DET1 build, kernel set wide)", + "measured": "2026-10-25", + "nice": 19, + "path": "integer", + "pictures": 8, + "quality": 2, + "system": "Linux 7.0.0-38-generic", + "threads": "all (20)" + }, + "rounds": [ + { + "child_wall_ns": 5633571335, + "decodes": 8, + "load_avg_after": [ + 3.572265625, + 1.09423828125, + 0.85107421875 + ], + "load_avg_before": [ + 2.14208984375, + 0.77490234375, + 0.74755859375 + ], + "rapl": { + "busy": { + "advanced": 55, + "intervals": 55, + "longest_run": 55, + "mean_w": 68.16931852572284, + "sd_w": 8.297150515865225, + "span_ns": 5518539343 + }, + "covered_ns": 5721493260, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.749831063452623, + "sd_w": 0.5277152837029823, + "span_ns": 4909653757 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.109033826142344, + "sd_w": 0.4990313284873856, + "span_ns": 4912957090 + }, + "marginal_nj": 354384136861.4659, + "ratio": 118.88965132993513, + "verdict": "clean", + "window_nj": 385448598000 + }, + "window_ns": 5633855296 + }, + { + "child_wall_ns": 5641926005, + "decodes": 8, + "load_avg_after": [ + 4.625, + 1.39013671875, + 0.94921875 + ], + "load_avg_before": [ + 3.2861328125, + 1.07568359375, + 0.84619140625 + ], + "rapl": { + "busy": { + "advanced": 55, + "intervals": 55, + "longest_run": 55, + "mean_w": 67.61419446259845, + "sd_w": 7.045950161169179, + "span_ns": 5519018978 + }, + "covered_ns": 5719717989, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.167700716404871, + "sd_w": 0.4997208187424342, + "span_ns": 4915160437 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.749831063452623, + "sd_w": 0.5277152837029823, + "span_ns": 4909653757 + }, + "marginal_nj": 352206836541.63495, + "ratio": 117.7821270146367, + "verdict": "clean", + "window_nj": 383429438000 + }, + "window_ns": 5642193986 + }, + { + "child_wall_ns": 5688637615, + "decodes": 8, + "load_avg_after": [ + 5.515625, + 1.67626953125, + 1.04638671875 + ], + "load_avg_before": [ + 4.25439453125, + 1.36669921875, + 0.94384765625 + ], + "rapl": { + "busy": { + "advanced": 56, + "intervals": 56, + "longest_run": 56, + "mean_w": 66.75757892845277, + "sd_w": 7.653396470270623, + "span_ns": 5620227464 + }, + "covered_ns": 5820739793, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.330426155475001, + "sd_w": 0.7996085275318868, + "span_ns": 4814687731 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.167700716404871, + "sd_w": 0.4997208187424342, + "span_ns": 4915160437 + }, + "marginal_nj": 352094452582.4431, + "ratio": 76.92328605144846, + "verdict": "clean", + "window_nj": 382647885000 + }, + "window_ns": 5688892506 + }, + { + "child_wall_ns": 5722488469, + "decodes": 8, + "load_avg_after": [ + 6.26904296875, + 1.953125, + 1.142578125 + ], + "load_avg_before": [ + 5.07373046875, + 1.6484375, + 1.04052734375 + ], + "rapl": { + "busy": { + "advanced": 56, + "intervals": 56, + "longest_run": 56, + "mean_w": 66.47973231270467, + "sd_w": 9.98483976720197, + "span_ns": 5620396121 + }, + "covered_ns": 5820724488, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.259358269794692, + "sd_w": 0.5328683612935503, + "span_ns": 4914505160 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.330426155475001, + "sd_w": 0.7996085275318868, + "span_ns": 4814687731 + }, + "marginal_nj": 348465053236.5958, + "ratio": 76.51849372958306, + "verdict": "clean", + "window_nj": 379285162000 + }, + "window_ns": 5722753871 + }, + { + "child_wall_ns": 5712377063, + "decodes": 8, + "load_avg_after": [ + 6.98681640625, + 2.2373046875, + 1.2431640625 + ], + "load_avg_before": [ + 5.7666015625, + 1.92041015625, + 1.13623046875 + ], + "rapl": { + "busy": { + "advanced": 56, + "intervals": 56, + "longest_run": 56, + "mean_w": 66.37716191581794, + "sd_w": 9.234684271647422, + "span_ns": 5622604290 + }, + "covered_ns": 5823318745, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.8032309638666435, + "sd_w": 1.3996228989057744, + "span_ns": 4910420327 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.259358269794692, + "sd_w": 0.5328683612935503, + "span_ns": 4914505160 + }, + "marginal_nj": 347038537373.6924, + "ratio": 43.47304359378272, + "verdict": "clean", + "window_nj": 379249029000 + }, + "window_ns": 5712486474 + }, + { + "child_wall_ns": 5730492299, + "decodes": 8, + "load_avg_after": [ + 8.51416015625, + 2.78662109375, + 1.43798828125 + ], + "load_avg_before": [ + 6.42724609375, + 2.19970703125, + 1.236328125 + ], + "rapl": { + "busy": { + "advanced": 56, + "intervals": 56, + "longest_run": 56, + "mean_w": 65.0354007493357, + "sd_w": 7.524421391086183, + "span_ns": 5617010778 + }, + "covered_ns": 5817452935, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.295352262551235, + "sd_w": 0.386297890940881, + "span_ns": 4907508671 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.8032309638666435, + "sd_w": 1.3996228989057744, + "span_ns": 4910420327 + }, + "marginal_nj": 341674979217.5668, + "ratio": 42.50152607722624, + "verdict": "clean", + "window_nj": 373957722000 + }, + "window_ns": 5730589080 + }, + { + "child_wall_ns": 5728315425, + "decodes": 8, + "load_avg_after": [ + 7.28466796875, + 2.7109375, + 1.427734375 + ], + "load_avg_before": [ + 7.83203125, + 2.740234375, + 1.43017578125 + ], + "rapl": { + "busy": { + "advanced": 56, + "intervals": 56, + "longest_run": 56, + "mean_w": 65.31962388595736, + "sd_w": 7.935765730939797, + "span_ns": 5619167754 + }, + "covered_ns": 5822509743, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 3.0664436230326624, + "sd_w": 0.33954351962008994, + "span_ns": 4915109416 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.295352262551235, + "sd_w": 0.386297890940881, + "span_ns": 4907508671 + }, + "marginal_nj": 350696247993.6052, + "ratio": 158.26833999599046, + "verdict": "clean", + "window_nj": 375039567000 + }, + "window_ns": 5728572014 + } + ], + "sample_s": 0.1 + }, + { + "child": [ + "nice", + "-n", + "19", + "idf/bin/native.c3", + "serve", + "idf/work/jobs.energy.q6.tsv" + ], + "counters": [ + "rapl" + ], + "host": { + "machine": "x86_64", + "node_cpu": 20, + "system": "Linux" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "linux-b-int-cpu-q6-after", + "meta": { + "fabric": "CPU", + "hardware": "x86_64 laptop CPU, 14 cores, 20 threads, AVX2", + "kernels": "narrow, kernel set auto", + "measured": "2026-10-25", + "nice": 19, + "path": "integer", + "pictures": 8, + "quality": 6, + "system": "Linux 7.0.0-38-generic", + "threads": "all (20)" + }, + "rounds": [ + { + "child_wall_ns": 2408468343, + "decodes": 8, + "load_avg_after": [ + 10.58984375, + 6.35205078125, + 3.09130859375 + ], + "load_avg_before": [ + 9.7705078125, + 6.12158203125, + 2.99951171875 + ], + "rapl": { + "busy": { + "advanced": 23, + "intervals": 23, + "longest_run": 23, + "mean_w": 68.46949157203451, + "sd_w": 10.277347521956411, + "span_ns": 2311007666 + }, + "covered_ns": 2511526371, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 6.009531295392971, + "sd_w": 0.6279361028070932, + "span_ns": 4910450049 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 3.5911076171552856, + "sd_w": 1.7407324781795799, + "span_ns": 4908308241 + }, + "marginal_nj": 152242827096.34314, + "ratio": 36.57608099685956, + "verdict": "clean", + "window_nj": 164298956000 + }, + "window_ns": 2408659354 + }, + { + "child_wall_ns": 2379101628, + "decodes": 8, + "load_avg_after": [ + 9.74169921875, + 6.24658203125, + 3.07421875 + ], + "load_avg_before": [ + 9.74169921875, + 6.24658203125, + 3.07421875 + ], + "rapl": { + "busy": { + "advanced": 23, + "intervals": 23, + "longest_run": 23, + "mean_w": 69.05575362551058, + "sd_w": 8.031081340273063, + "span_ns": 2313384920 + }, + "covered_ns": 2513879761, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.544394837956038, + "sd_w": 0.5164856191283907, + "span_ns": 4912315948 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 6.009531295392971, + "sd_w": 0.6279361028070932, + "span_ns": 4910450049 + }, + "marginal_nj": 150643490466.64246, + "ratio": 100.77265867650837, + "verdict": "clean", + "window_nj": 165166081000 + }, + "window_ns": 2379224763 + }, + { + "child_wall_ns": 2369706413, + "decodes": 8, + "load_avg_after": [ + 10.166015625, + 6.439453125, + 3.17041015625 + ], + "load_avg_before": [ + 8.96142578125, + 6.142578125, + 3.0576171875 + ], + "rapl": { + "busy": { + "advanced": 22, + "intervals": 22, + "longest_run": 22, + "mean_w": 69.45863547627785, + "sd_w": 7.289344594518438, + "span_ns": 2206576149 + }, + "covered_ns": 2407239628, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 3.0037528729008267, + "sd_w": 0.47614608822548027, + "span_ns": 4915053369 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.544394837956038, + "sd_w": 0.5164856191283907, + "span_ns": 4912315948 + }, + "marginal_nj": 155410929042.21393, + "ratio": 126.20789274027298, + "verdict": "clean", + "window_nj": 165699649000 + }, + "window_ns": 2369893089 + }, + { + "child_wall_ns": 2346983799, + "decodes": 8, + "load_avg_after": [ + 9.431640625, + 6.34912109375, + 3.15869140625 + ], + "load_avg_before": [ + 9.431640625, + 6.34912109375, + 3.15869140625 + ], + "rapl": { + "busy": { + "advanced": 22, + "intervals": 22, + "longest_run": 22, + "mean_w": 71.62043409711193, + "sd_w": 9.650029956799553, + "span_ns": 2208280501 + }, + "covered_ns": 2408690638, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 3.098550919819019, + "sd_w": 0.8735466061860632, + "span_ns": 4910066482 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 3.0037528729008267, + "sd_w": 0.47614608822548027, + "span_ns": 4915053369 + }, + "marginal_nj": 159721152992.12192, + "ratio": 78.49527628540396, + "verdict": "clean", + "window_nj": 167070434000 + }, + "window_ns": 2347173173 + }, + { + "child_wall_ns": 2338580324, + "decodes": 8, + "load_avg_after": [ + 7.9814453125, + 6.1396484375, + 3.12451171875 + ], + "load_avg_before": [ + 7.9814453125, + 6.1396484375, + 3.12451171875 + ], + "rapl": { + "busy": { + "advanced": 23, + "intervals": 23, + "longest_run": 23, + "mean_w": 71.99115889900354, + "sd_w": 9.330958256867275, + "span_ns": 2312780214 + }, + "covered_ns": 2513679434, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 2.9202139805002614, + "sd_w": 0.2677506869972257, + "span_ns": 4815978486 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 3.098550919819019, + "sd_w": 0.8735466061860632, + "span_ns": 4910066482 + }, + "marginal_nj": 160616709225.9932, + "ratio": 78.96748262811172, + "verdict": "clean", + "window_nj": 168181332000 + }, + "window_ns": 2338732235 + }, + { + "child_wall_ns": 2340235074, + "decodes": 8, + "load_avg_after": [ + 7.34228515625, + 6.03759765625, + 3.107421875 + ], + "load_avg_before": [ + 7.34228515625, + 6.03759765625, + 3.107421875 + ], + "rapl": { + "busy": { + "advanced": 23, + "intervals": 23, + "longest_run": 23, + "mean_w": 71.29054774435919, + "sd_w": 9.178855633124948, + "span_ns": 2311639696 + }, + "covered_ns": 2512304386, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 2.9527994324996745, + "sd_w": 0.3013926197166576, + "span_ns": 4916255885 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 2.9202139805002614, + "sd_w": 0.2677506869972257, + "span_ns": 4815978486 + }, + "marginal_nj": 160111491321.74173, + "ratio": 226.79401075620092, + "verdict": "clean", + "window_nj": 167488890000 + }, + "window_ns": 2340419336 + }, + { + "child_wall_ns": 2354118172, + "decodes": 8, + "load_avg_after": [ + 6.28662109375, + 5.8544921875, + 3.07861328125 + ], + "load_avg_before": [ + 6.28662109375, + 5.8544921875, + 3.07861328125 + ], + "rapl": { + "busy": { + "advanced": 22, + "intervals": 22, + "longest_run": 22, + "mean_w": 71.44700872264784, + "sd_w": 7.682630189687305, + "span_ns": 2206978649 + }, + "covered_ns": 2407435249, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 3.1242593420837608, + "sd_w": 0.5934362442888589, + "span_ns": 4907557033 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 2.9527994324996745, + "sd_w": 0.3013926197166576, + "span_ns": 4916255885 + }, + "marginal_nj": 161600764247.91156, + "ratio": 115.27519593504616, + "verdict": "clean", + "window_nj": 168915827000 + }, + "window_ns": 2354288077 + } + ], + "sample_s": 0.1 + }, + { + "child": [ + "nice", + "-n", + "19", + "idf/bin/base", + "serve", + "idf/work/jobs.energy.q6.tsv" + ], + "counters": [ + "rapl" + ], + "host": { + "machine": "x86_64", + "node_cpu": 20, + "system": "Linux" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "linux-b-int-cpu-q6-before", + "meta": { + "fabric": "CPU", + "hardware": "x86_64 laptop CPU, 14 cores, 20 threads, AVX2", + "kernels": "the record's (DET1 build, kernel set wide)", + "measured": "2026-10-25", + "nice": 19, + "path": "integer", + "pictures": 8, + "quality": 6, + "system": "Linux 7.0.0-38-generic", + "threads": "all (20)" + }, + "rounds": [ + { + "child_wall_ns": 12444960596, + "decodes": 8, + "load_avg_after": [ + 8.35693359375, + 3.81103515625, + 1.90625 + ], + "load_avg_before": [ + 6.43017578125, + 3.29345703125, + 1.720703125 + ], + "rapl": { + "busy": { + "advanced": 123, + "intervals": 123, + "longest_run": 123, + "mean_w": 62.73074412410557, + "sd_w": 7.139358011166378, + "span_ns": 12344704051 + }, + "covered_ns": 12545342679, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 6.685933685576195, + "sd_w": 0.3943242218774907, + "span_ns": 4915994646 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.540691054837212, + "sd_w": 0.48512810063950623, + "span_ns": 4914318770 + }, + "marginal_nj": 703576132411.9872, + "ratio": 116.70614767370631, + "verdict": "clean", + "window_nj": 780269731000 + }, + "window_ns": 12445188111 + }, + { + "child_wall_ns": 12594293870, + "decodes": 8, + "load_avg_after": [ + 10.41552734375, + 4.54443359375, + 2.1875 + ], + "load_avg_before": [ + 7.6875, + 3.74755859375, + 1.89599609375 + ], + "rapl": { + "busy": { + "advanced": 125, + "intervals": 125, + "longest_run": 125, + "mean_w": 61.01904205212868, + "sd_w": 7.201743467228498, + "span_ns": 12540479472 + }, + "covered_ns": 12740940978, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 6.122639016096691, + "sd_w": 2.0150698546315824, + "span_ns": 4912892414 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 6.685933685576195, + "sd_w": 0.3943242218774907, + "span_ns": 4915994646 + }, + "marginal_nj": 687491729597.7819, + "ratio": 27.103157528639382, + "verdict": "clean", + "window_nj": 769088364000 + }, + "window_ns": 12594478096 + }, + { + "child_wall_ns": 12657026014, + "decodes": 8, + "load_avg_after": [ + 10.3671875, + 4.91455078125, + 2.359375 + ], + "load_avg_before": [ + 9.5810546875, + 4.46875, + 2.17529296875 + ], + "rapl": { + "busy": { + "advanced": 125, + "intervals": 125, + "longest_run": 125, + "mean_w": 60.05728173049381, + "sd_w": 5.984476885459814, + "span_ns": 12546081919 + }, + "covered_ns": 12746695149, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 6.114013408616931, + "sd_w": 0.5716990043188389, + "span_ns": 4914110755 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 6.122639016096691, + "sd_w": 2.0150698546315824, + "span_ns": 4912892414 + }, + "marginal_nj": 685020166948.9519, + "ratio": 26.767784448841713, + "verdict": "clean", + "window_nj": 763008606000 + }, + "window_ns": 12657184570 + }, + { + "child_wall_ns": 12644560744, + "decodes": 8, + "load_avg_after": [ + 9.62353515625, + 5.0166015625, + 2.43408203125 + ], + "load_avg_before": [ + 9.53662109375, + 4.83251953125, + 2.3466796875 + ], + "rapl": { + "busy": { + "advanced": 125, + "intervals": 125, + "longest_run": 125, + "mean_w": 60.125709830505485, + "sd_w": 5.670041273322398, + "span_ns": 12541399319 + }, + "covered_ns": 12742036932, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 7.085831494500309, + "sd_w": 3.7229876620530935, + "span_ns": 4815086976 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 6.114013408616931, + "sd_w": 0.5716990043188389, + "span_ns": 4914110755 + }, + "marginal_nj": 679333352373.904, + "ratio": 14.377105764951507, + "verdict": "clean", + "window_nj": 763429808000 + }, + "window_ns": 12644773693 + }, + { + "child_wall_ns": 12725452632, + "decodes": 8, + "load_avg_after": [ + 11.32275390625, + 5.671875, + 2.70361328125 + ], + "load_avg_before": [ + 8.8525390625, + 4.93310546875, + 2.4208984375 + ], + "rapl": { + "busy": { + "advanced": 126, + "intervals": 126, + "longest_run": 126, + "mean_w": 59.511327427398975, + "sd_w": 7.371165858518164, + "span_ns": 12641435918 + }, + "covered_ns": 12841974384, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 6.198013839651901, + "sd_w": 0.5852469832487812, + "span_ns": 4916104148 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 7.085831494500309, + "sd_w": 3.7229876620530935, + "span_ns": 4815086976 + }, + "marginal_nj": 672158288248.8997, + "ratio": 14.200800421446278, + "verdict": "clean", + "window_nj": 757453689000 + }, + "window_ns": 12725625144 + }, + { + "child_wall_ns": 12790878285, + "decodes": 8, + "load_avg_after": [ + 11.8896484375, + 6.05322265625, + 2.875 + ], + "load_avg_before": [ + 10.416015625, + 5.57763671875, + 2.68896484375 + ], + "rapl": { + "busy": { + "advanced": 127, + "intervals": 127, + "longest_run": 127, + "mean_w": 58.84087906843554, + "sd_w": 6.48674025626582, + "span_ns": 12738201058 + }, + "covered_ns": 12941704489, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 6.075745763525714, + "sd_w": 1.5034877728234803, + "span_ns": 4813302079 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 6.198013839651901, + "sd_w": 0.5852469832487812, + "span_ns": 4916104148 + }, + "marginal_nj": 675151761123.3247, + "ratio": 35.05449144283433, + "verdict": "clean", + "window_nj": 754573446000 + }, + "window_ns": 12791031763 + }, + { + "child_wall_ns": 12758714379, + "decodes": 8, + "load_avg_after": [ + 11.5458984375, + 6.33056640625, + 3.03271484375 + ], + "load_avg_before": [ + 10.9375, + 5.95263671875, + 2.859375 + ], + "rapl": { + "busy": { + "advanced": 127, + "intervals": 127, + "longest_run": 127, + "mean_w": 59.09145108634329, + "sd_w": 6.384702919759169, + "span_ns": 12754531773 + }, + "covered_ns": 12954811057, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 6.8298055817459655, + "sd_w": 0.4899639420732794, + "span_ns": 4811785715 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 6.075745763525714, + "sd_w": 1.5034877728234803, + "span_ns": 4813302079 + }, + "marginal_nj": 671623608367.7966, + "ratio": 35.011043232399864, + "verdict": "clean", + "window_nj": 755218098000 + }, + "window_ns": 12758856513 + } + ], + "sample_s": 0.1 + }, + { + "child": [ + "nice", + "-n", + "10", + "idf/venv/bin/python", + "-I", + "tools/wai_determinism_cost.py", + "fserve", + "idf/work", + "2", + "--device", + "cpu", + "--ids", + "a56e27cbf5f843c0,4f4b74a79237e311,e25ca1ff2f0c0cb5,e3b946107c5d3441,10349e963c5c813d,363510303b715d4c,b77d3f006f42414b,ba23983c76b4832e" + ], + "counters": [ + "rapl" + ], + "host": { + "machine": "x86_64", + "node_cpu": 32, + "system": "Linux" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "linux-float-cpu-q2-again", + "meta": { + "fabric": "CPU", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512, integrated GPU PCI vendor 0x1002", + "measured": "2026-10-25", + "nice": 10, + "path": "float", + "pictures": 8, + "quality": 2, + "system": "Linux 7.0", + "threads": "the library default" + }, + "rounds": [ + { + "child_note": "mismatches=5", + "child_wall_ns": 985672879, + "decodes": 8, + "load_avg_after": [ + 4.3291015625, + 3.69091796875, + 4.8125 + ], + "load_avg_before": [ + 4.3291015625, + 3.69091796875, + 4.8125 + ], + "rapl": { + "busy": { + "advanced": 9, + "intervals": 9, + "longest_run": 9, + "mean_w": 79.09516630159673, + "sd_w": 4.574299845141006, + "span_ns": 902451982 + }, + "covered_ns": 1103900901, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.346396443221652, + "sd_w": 2.4663176592907132, + "span_ns": 4944974262 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.782785449883477, + "sd_w": 2.550010910161843, + "span_ns": 4846155347 + }, + "marginal_nj": 72023611040.40417, + "ratio": 28.83539637498153, + "verdict": "clean", + "window_nj": 78166368000 + }, + "window_ns": 986015205 + }, + { + "child_note": "mismatches=5", + "child_wall_ns": 1079256815, + "decodes": 8, + "load_avg_after": [ + 3.81689453125, + 3.60205078125, + 4.771484375 + ], + "load_avg_before": [ + 4.0625, + 3.64599609375, + 4.7919921875 + ], + "rapl": { + "busy": { + "advanced": 9, + "intervals": 9, + "longest_run": 9, + "mean_w": 74.72823275032894, + "sd_w": 5.0042454033378405, + "span_ns": 902886754 + }, + "covered_ns": 1103521399, + "idle_after": { + "advanced": 47, + "intervals": 49, + "longest_run": 38, + "mean_w": 5.603096788782531, + "sd_w": 2.599378227794445, + "span_ns": 4947312562 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.346396443221652, + "sd_w": 2.4663176592907132, + "span_ns": 4944974262 + }, + "marginal_nj": 74092569955.13885, + "ratio": 26.642327535799964, + "verdict": "clean", + "window_nj": 80134070000 + }, + "window_ns": 1079533036 + }, + { + "child_note": "mismatches=5", + "child_wall_ns": 1066222170, + "decodes": 8, + "load_avg_after": [ + 3.51123046875, + 3.5419921875, + 4.74560546875 + ], + "load_avg_before": [ + 3.51123046875, + 3.5419921875, + 4.74560546875 + ], + "rapl": { + "busy": { + "advanced": 10, + "intervals": 10, + "longest_run": 10, + "mean_w": 74.88042461541387, + "sd_w": 6.180992508492328, + "span_ns": 1002691683 + }, + "covered_ns": 1203613497, + "idle_after": { + "advanced": 47, + "intervals": 48, + "longest_run": 34, + "mean_w": 5.369362255369238, + "sd_w": 2.4446257723715967, + "span_ns": 4845857643 + }, + "idle_before": { + "advanced": 47, + "intervals": 49, + "longest_run": 38, + "mean_w": 5.603096788782531, + "sd_w": 2.599378227794445, + "span_ns": 4947312562 + }, + "marginal_nj": 74054333099.5896, + "ratio": 26.696459311432527, + "verdict": "clean", + "window_nj": 80657633000 + }, + "window_ns": 1066513038 + }, + { + "child_note": "mismatches=5", + "child_wall_ns": 1051641502, + "decodes": 8, + "load_avg_after": [ + 3.22998046875, + 3.48291015625, + 4.7197265625 + ], + "load_avg_before": [ + 3.22998046875, + 3.48291015625, + 4.7197265625 + ], + "rapl": { + "busy": { + "advanced": 10, + "intervals": 10, + "longest_run": 10, + "mean_w": 76.09747168280282, + "sd_w": 2.9856882085667675, + "span_ns": 1003026624 + }, + "covered_ns": 1204333695, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.353001278905441, + "sd_w": 2.1674165317139336, + "span_ns": 4845035462 + }, + "idle_before": { + "advanced": 47, + "intervals": 48, + "longest_run": 34, + "mean_w": 5.369362255369238, + "sd_w": 2.4446257723715967, + "span_ns": 4845857643 + }, + "marginal_nj": 74649032152.81686, + "ratio": 28.935426728747245, + "verdict": "clean", + "window_nj": 81105684000 + }, + "window_ns": 1051815069 + }, + { + "child_note": "mismatches=5", + "child_wall_ns": 1117617589, + "decodes": 8, + "load_avg_after": [ + 2.97119140625, + 3.4248046875, + 4.6943359375 + ], + "load_avg_before": [ + 2.97119140625, + 3.4248046875, + 4.6943359375 + ], + "rapl": { + "busy": { + "advanced": 10, + "intervals": 10, + "longest_run": 10, + "mean_w": 72.37604375772294, + "sd_w": 7.616924029254277, + "span_ns": 1005963711 + }, + "covered_ns": 1207363216, + "idle_after": { + "advanced": 48, + "intervals": 49, + "longest_run": 46, + "mean_w": 6.858536605624688, + "sd_w": 5.6391574070083506, + "span_ns": 4943220499 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.353001278905441, + "sd_w": 2.1674165317139336, + "span_ns": 4845035462 + }, + "marginal_nj": 73288086173.71393, + "ratio": 11.751804397780619, + "verdict": "clean", + "window_nj": 80659967000 + }, + "window_ns": 1117887258 + }, + { + "child_note": "mismatches=5", + "child_wall_ns": 1057290672, + "decodes": 8, + "load_avg_after": [ + 2.513671875, + 3.3115234375, + 4.6435546875 + ], + "load_avg_before": [ + 2.513671875, + 3.3115234375, + 4.6435546875 + ], + "rapl": { + "busy": { + "advanced": 9, + "intervals": 9, + "longest_run": 9, + "mean_w": 77.3962607912245, + "sd_w": 4.9932764996918175, + "span_ns": 902766705 + }, + "covered_ns": 1103369250, + "idle_after": { + "advanced": 48, + "intervals": 49, + "longest_run": 27, + "mean_w": 5.610333931491213, + "sd_w": 2.8864789665640354, + "span_ns": 4944505927 + }, + "idle_before": { + "advanced": 48, + "intervals": 49, + "longest_run": 46, + "mean_w": 6.858536605624688, + "sd_w": 5.6391574070083506, + "span_ns": 4943220499 + }, + "marginal_nj": 74368379833.55766, + "ratio": 12.619230212341044, + "verdict": "clean", + "window_nj": 81247264000 + }, + "window_ns": 1057504676 + }, + { + "child_note": "mismatches=5", + "child_wall_ns": 1047298428, + "decodes": 8, + "load_avg_after": [ + 2.31201171875, + 3.25634765625, + 4.6181640625 + ], + "load_avg_before": [ + 2.31201171875, + 3.25634765625, + 4.6181640625 + ], + "rapl": { + "busy": { + "advanced": 9, + "intervals": 9, + "longest_run": 9, + "mean_w": 76.18628783227611, + "sd_w": 4.842365526579245, + "span_ns": 902721423 + }, + "covered_ns": 1103762463, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.518656193695118, + "sd_w": 2.1491361718837965, + "span_ns": 4947214648 + }, + "idle_before": { + "advanced": 48, + "intervals": 49, + "longest_run": 27, + "mean_w": 5.610333931491213, + "sd_w": 2.8864789665640354, + "span_ns": 4944505927 + }, + "marginal_nj": 73919653224.36082, + "ratio": 24.466415168009583, + "verdict": "clean", + "window_nj": 80061534000 + }, + "window_ns": 1047565822 + } + ], + "sample_s": 0.1 + }, { "child": [ "nice", @@ -16074,6 +21914,378 @@ ], "sample_s": 0.1 }, + { + "child": [ + "nice", + "-n", + "10", + "idf/venv/bin/python", + "-I", + "tools/wai_determinism_cost.py", + "fserve", + "idf/work", + "6", + "--device", + "cpu", + "--ids", + "a56e27cbf5f843c0,4f4b74a79237e311,e25ca1ff2f0c0cb5,e3b946107c5d3441,10349e963c5c813d,363510303b715d4c,b77d3f006f42414b,ba23983c76b4832e" + ], + "counters": [ + "rapl" + ], + "host": { + "machine": "x86_64", + "node_cpu": 32, + "system": "Linux" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "linux-float-cpu-q6-again", + "meta": { + "fabric": "CPU", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512, integrated GPU PCI vendor 0x1002", + "measured": "2026-10-25", + "nice": 10, + "path": "float", + "pictures": 8, + "quality": 6, + "system": "Linux 7.0", + "threads": "the library default" + }, + "rounds": [ + { + "child_note": "mismatches=8", + "child_wall_ns": 1772754277, + "decodes": 8, + "load_avg_after": [ + 8.35400390625, + 6.140625, + 5.54443359375 + ], + "load_avg_before": [ + 8.35400390625, + 6.140625, + 5.54443359375 + ], + "rapl": { + "busy": { + "advanced": 16, + "intervals": 16, + "longest_run": 16, + "mean_w": 66.54032202415299, + "sd_w": 8.216075620676314, + "span_ns": 1606388796 + }, + "covered_ns": 1806988505, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.631710419151305, + "sd_w": 1.6592331446689232, + "span_ns": 4944882682 + }, + "idle_before": { + "advanced": 47, + "intervals": 49, + "longest_run": 23, + "mean_w": 5.8035129309277895, + "sd_w": 2.633233453497287, + "span_ns": 4945913584 + }, + "marginal_nj": 109211399427.14975, + "ratio": 23.09810786747097, + "verdict": "clean", + "window_nj": 119543058000 + }, + "window_ns": 1773242136 + }, + { + "child_note": "mismatches=8", + "child_wall_ns": 1640349307, + "decodes": 8, + "load_avg_after": [ + 7.6845703125, + 6.03857421875, + 5.5146484375 + ], + "load_avg_before": [ + 7.6845703125, + 6.03857421875, + 5.5146484375 + ], + "rapl": { + "busy": { + "advanced": 15, + "intervals": 15, + "longest_run": 15, + "mean_w": 74.20595742987284, + "sd_w": 6.709427146845371, + "span_ns": 1504222322 + }, + "covered_ns": 1705287958, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.471017528780137, + "sd_w": 2.277755274527458, + "span_ns": 4947515302 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.631710419151305, + "sd_w": 1.6592331446689232, + "span_ns": 4944882682 + }, + "marginal_nj": 112238762864.72124, + "ratio": 30.141338810048488, + "verdict": "clean", + "window_nj": 121705437000 + }, + "window_ns": 1640620027 + }, + { + "child_note": "mismatches=8", + "child_wall_ns": 1686304193, + "decodes": 8, + "load_avg_after": [ + 7.7841796875, + 6.10546875, + 5.54150390625 + ], + "load_avg_before": [ + 7.06884765625, + 5.93798828125, + 5.48486328125 + ], + "rapl": { + "busy": { + "advanced": 16, + "intervals": 16, + "longest_run": 16, + "mean_w": 72.81223836914387, + "sd_w": 7.194607348489351, + "span_ns": 1604680908 + }, + "covered_ns": 1806018698, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.76909043942293, + "sd_w": 1.5833364635752225, + "span_ns": 4843168595 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.471017528780137, + "sd_w": 2.277755274527458, + "span_ns": 4947515302 + }, + "marginal_nj": 113529228420.94324, + "ratio": 29.49929921641033, + "verdict": "clean", + "window_nj": 123679151000 + }, + "window_ns": 1686638744 + }, + { + "child_note": "mismatches=8", + "child_wall_ns": 1649754984, + "decodes": 8, + "load_avg_after": [ + 7.16064453125, + 6.00390625, + 5.51171875 + ], + "load_avg_before": [ + 7.16064453125, + 6.00390625, + 5.51171875 + ], + "rapl": { + "busy": { + "advanced": 16, + "intervals": 16, + "longest_run": 16, + "mean_w": 73.47195488328789, + "sd_w": 9.113741970714711, + "span_ns": 1604758467 + }, + "covered_ns": 1806064828, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.571573254396072, + "sd_w": 1.8098540876309634, + "span_ns": 4845363539 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.76909043942293, + "sd_w": 1.5833364635752225, + "span_ns": 4843168595 + }, + "marginal_nj": 111705892088.20847, + "ratio": 37.46248026277542, + "verdict": "clean", + "window_nj": 121946879000 + }, + "window_ns": 1650010716 + }, + { + "child_note": "mismatches=8", + "child_wall_ns": 1808699029, + "decodes": 8, + "load_avg_after": [ + 7.3408203125, + 6.07177734375, + 5.53857421875 + ], + "load_avg_before": [ + 6.5869140625, + 5.90380859375, + 5.48193359375 + ], + "rapl": { + "busy": { + "advanced": 17, + "intervals": 17, + "longest_run": 17, + "mean_w": 69.4341458585427, + "sd_w": 10.146696096481788, + "span_ns": 1708930176 + }, + "covered_ns": 1910384907, + "idle_after": { + "advanced": 48, + "intervals": 49, + "longest_run": 41, + "mean_w": 5.490378100756141, + "sd_w": 3.097643780777862, + "span_ns": 4944175928 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.571573254396072, + "sd_w": 1.8098540876309634, + "span_ns": 4845363539 + }, + "marginal_nj": 115240495544.57451, + "ratio": 20.629605824114357, + "verdict": "clean", + "window_nj": 125806788000 + }, + "window_ns": 1808901942 + }, + { + "child_note": "mismatches=8", + "child_wall_ns": 1685916652, + "decodes": 8, + "load_avg_after": [ + 6.9130859375, + 6.00390625, + 5.51953125 + ], + "load_avg_before": [ + 6.9130859375, + 6.00390625, + 5.51953125 + ], + "rapl": { + "busy": { + "advanced": 16, + "intervals": 16, + "longest_run": 16, + "mean_w": 72.3731725001311, + "sd_w": 10.426987924973814, + "span_ns": 1604453948 + }, + "covered_ns": 1805945887, + "idle_after": { + "advanced": 46, + "intervals": 48, + "longest_run": 40, + "mean_w": 5.551828290055532, + "sd_w": 2.260454824226986, + "span_ns": 4845809752 + }, + "idle_before": { + "advanced": 48, + "intervals": 49, + "longest_run": 41, + "mean_w": 5.490378100756141, + "sd_w": 3.097643780777862, + "span_ns": 4944175928 + }, + "marginal_nj": 114154222392.55428, + "ratio": 21.581587179122888, + "verdict": "clean", + "window_nj": 124125036000 + }, + "window_ns": 1686137859 + }, + { + "child_note": "mismatches=8", + "child_wall_ns": 1606142321, + "decodes": 8, + "load_avg_after": [ + 6.439453125, + 5.92041015625, + 5.4951171875 + ], + "load_avg_before": [ + 6.439453125, + 5.92041015625, + 5.4951171875 + ], + "rapl": { + "busy": { + "advanced": 15, + "intervals": 15, + "longest_run": 15, + "mean_w": 75.33501936352732, + "sd_w": 8.895373292110323, + "span_ns": 1505294814 + }, + "covered_ns": 1706730549, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.44932309860231, + "sd_w": 2.3085932218465652, + "span_ns": 4944881308 + }, + "idle_before": { + "advanced": 46, + "intervals": 48, + "longest_run": 40, + "mean_w": 5.551828290055532, + "sd_w": 2.260454824226986, + "span_ns": 4845809752 + }, + "marginal_nj": 110963349425.40195, + "ratio": 30.249782858385164, + "verdict": "clean", + "window_nj": 120351350000 + }, + "window_ns": 1606414263 + } + ], + "sample_s": 0.1 + }, { "child": [ "nice", @@ -16445,6 +22657,1087 @@ ], "sample_s": 0.1 }, + { + "child": [ + "nice", + "-n", + "10", + "env", + "WAI_INT_ISA=avx2", + "idf/bin/native.c3", + "serve", + "idf/work/jobs.energy.q2.tsv" + ], + "counters": [ + "rapl" + ], + "host": { + "machine": "x86_64", + "node_cpu": 32, + "system": "Linux" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "linux-int-cpu-q2-after-avx2", + "meta": { + "fabric": "CPU", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512, integrated GPU PCI vendor 0x1002", + "kernels": "narrow, kernel set avx2", + "measured": "2026-10-25", + "nice": 10, + "path": "integer", + "pictures": 8, + "quality": 2, + "system": "Linux 7.0", + "threads": "all (32)" + }, + "rounds": [ + { + "child_wall_ns": 810783159, + "decodes": 8, + "load_avg_after": [ + 5.15380859375, + 5.662109375, + 5.4169921875 + ], + "load_avg_before": [ + 5.15380859375, + 5.662109375, + 5.4169921875 + ], + "rapl": { + "busy": { + "advanced": 7, + "intervals": 7, + "longest_run": 7, + "mean_w": 96.2048454011974, + "sd_w": 3.3406684550448538, + "span_ns": 702584184 + }, + "covered_ns": 903538891, + "idle_after": { + "advanced": 48, + "intervals": 49, + "longest_run": 40, + "mean_w": 5.870283217441899, + "sd_w": 2.4515022800934303, + "span_ns": 4946027762 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.568103064977202, + "sd_w": 2.2066332426132207, + "span_ns": 4846212931 + }, + "marginal_nj": 73794055571.77672, + "ratio": 36.910286804440304, + "verdict": "clean", + "window_nj": 78961569000 + }, + "window_ns": 811035795 + }, + { + "child_wall_ns": 811027309, + "decodes": 8, + "load_avg_after": [ + 4.74072265625, + 5.56787109375, + 5.3876953125 + ], + "load_avg_before": [ + 4.74072265625, + 5.56787109375, + 5.3876953125 + ], + "rapl": { + "busy": { + "advanced": 7, + "intervals": 7, + "longest_run": 7, + "mean_w": 97.14361701505254, + "sd_w": 1.772442720983797, + "span_ns": 702078460 + }, + "covered_ns": 903548919, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.267798746617962, + "sd_w": 1.7564401959180251, + "span_ns": 4844739633 + }, + "idle_before": { + "advanced": 48, + "intervals": 49, + "longest_run": 40, + "mean_w": 5.870283217441899, + "sd_w": 2.4515022800934303, + "span_ns": 4946027762 + }, + "marginal_nj": 74137742040.82016, + "ratio": 37.354473123122105, + "verdict": "clean", + "window_nj": 79169643000 + }, + "window_ns": 811132878 + }, + { + "child_wall_ns": 812730111, + "decodes": 8, + "load_avg_after": [ + 4.36083984375, + 5.47509765625, + 5.3583984375 + ], + "load_avg_before": [ + 4.36083984375, + 5.47509765625, + 5.3583984375 + ], + "rapl": { + "busy": { + "advanced": 7, + "intervals": 7, + "longest_run": 7, + "mean_w": 96.2579454000471, + "sd_w": 0.951444417098347, + "span_ns": 703808744 + }, + "covered_ns": 904512279, + "idle_after": { + "advanced": 47, + "intervals": 49, + "longest_run": 27, + "mean_w": 5.486235589538308, + "sd_w": 2.5015778813096663, + "span_ns": 4955658234 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.267798746617962, + "sd_w": 1.7564401959180251, + "span_ns": 4844739633 + }, + "marginal_nj": 73990375947.07951, + "ratio": 36.32944187385824, + "verdict": "clean", + "window_nj": 78853954000 + }, + "window_ns": 812831062 + }, + { + "child_wall_ns": 823897873, + "decodes": 8, + "load_avg_after": [ + 4.01123046875, + 5.3837890625, + 5.32958984375 + ], + "load_avg_before": [ + 4.01123046875, + 5.3837890625, + 5.32958984375 + ], + "rapl": { + "busy": { + "advanced": 7, + "intervals": 7, + "longest_run": 7, + "mean_w": 95.30704215064539, + "sd_w": 2.85470160103962, + "span_ns": 703039812 + }, + "covered_ns": 904271751, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.392676551442514, + "sd_w": 1.9148771311638038, + "span_ns": 4947405286 + }, + "idle_before": { + "advanced": 47, + "intervals": 49, + "longest_run": 27, + "mean_w": 5.486235589538308, + "sd_w": 2.5015778813096663, + "span_ns": 4955658234 + }, + "marginal_nj": 74458496534.65005, + "ratio": 35.92436068114979, + "verdict": "clean", + "window_nj": 79377243000 + }, + "window_ns": 824030372 + }, + { + "child_wall_ns": 816045442, + "decodes": 8, + "load_avg_after": [ + 3.39404296875, + 5.2060546875, + 5.27197265625 + ], + "load_avg_before": [ + 3.39404296875, + 5.2060546875, + 5.27197265625 + ], + "rapl": { + "busy": { + "advanced": 7, + "intervals": 7, + "longest_run": 7, + "mean_w": 96.86618202347114, + "sd_w": 0.738102206022766, + "span_ns": 701632712 + }, + "covered_ns": 902972345, + "idle_after": { + "advanced": 47, + "intervals": 48, + "longest_run": 44, + "mean_w": 5.534186094411732, + "sd_w": 2.8913898209265607, + "span_ns": 4845157729 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.392676551442514, + "sd_w": 1.9148771311638038, + "span_ns": 4947405286 + }, + "marginal_nj": 74797473606.59004, + "ratio": 31.61204692601897, + "verdict": "clean", + "window_nj": 79730801000 + }, + "window_ns": 816134379 + }, + { + "child_wall_ns": 815249291, + "decodes": 8, + "load_avg_after": [ + 3.2021484375, + 5.1357421875, + 5.2490234375 + ], + "load_avg_before": [ + 3.2021484375, + 5.1357421875, + 5.2490234375 + ], + "rapl": { + "busy": { + "advanced": 7, + "intervals": 7, + "longest_run": 7, + "mean_w": 95.98559743068432, + "sd_w": 2.6479232652986893, + "span_ns": 702685366 + }, + "covered_ns": 903179717, + "idle_after": { + "advanced": 48, + "intervals": 49, + "longest_run": 46, + "mean_w": 5.500474665037592, + "sd_w": 2.190454832850759, + "span_ns": 4946770161 + }, + "idle_before": { + "advanced": 47, + "intervals": 48, + "longest_run": 44, + "mean_w": 5.534186094411732, + "sd_w": 2.8913898209265607, + "span_ns": 4845157729 + }, + "marginal_nj": 74364608109.04478, + "ratio": 31.288851609074506, + "verdict": "clean", + "window_nj": 79347749000 + }, + "window_ns": 815386499 + }, + { + "child_wall_ns": 812737643, + "decodes": 8, + "load_avg_after": [ + 2.9453125, + 5.05029296875, + 5.220703125 + ], + "load_avg_before": [ + 2.9453125, + 5.05029296875, + 5.220703125 + ], + "rapl": { + "busy": { + "advanced": 7, + "intervals": 7, + "longest_run": 7, + "mean_w": 96.35239798396681, + "sd_w": 1.9075658780540345, + "span_ns": 703392226 + }, + "covered_ns": 904914343, + "idle_after": { + "advanced": 48, + "intervals": 49, + "longest_run": 43, + "mean_w": 5.709188037373005, + "sd_w": 1.9783279319838327, + "span_ns": 4944368152 + }, + "idle_before": { + "advanced": 48, + "intervals": 49, + "longest_run": 46, + "mean_w": 5.500474665037592, + "sd_w": 2.190454832850759, + "span_ns": 4946770161 + }, + "marginal_nj": 73889661720.19826, + "ratio": 41.428640879418836, + "verdict": "clean", + "window_nj": 78961554000 + }, + "window_ns": 812862268 + } + ], + "sample_s": 0.1 + }, + { + "child": [ + "nice", + "-n", + "10", + "env", + "WAI_INT_ISA=auto", + "idf/bin/native.c3", + "serve", + "idf/work/jobs.energy.q2.tsv" + ], + "counters": [ + "rapl" + ], + "host": { + "machine": "x86_64", + "node_cpu": 32, + "system": "Linux" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "linux-int-cpu-q2-after", + "meta": { + "fabric": "CPU", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512, integrated GPU PCI vendor 0x1002", + "kernels": "narrow, kernel set auto", + "measured": "2026-10-25", + "nice": 10, + "path": "integer", + "pictures": 8, + "quality": 2, + "system": "Linux 7.0", + "threads": "all (32)" + }, + "rounds": [ + { + "child_wall_ns": 743021113, + "decodes": 8, + "load_avg_after": [ + 4.556640625, + 3.6953125, + 4.875 + ], + "load_avg_before": [ + 4.16943359375, + 3.60546875, + 4.8525390625 + ], + "rapl": { + "busy": { + "advanced": 7, + "intervals": 7, + "longest_run": 7, + "mean_w": 86.4216867703884, + "sd_w": 3.59871365187376, + "span_ns": 701929190 + }, + "covered_ns": 904717335, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.598874261663465, + "sd_w": 2.03833717619498, + "span_ns": 4844932573 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.420768213183027, + "sd_w": 1.9992110936973961, + "span_ns": 4844334563 + }, + "marginal_nj": 59989195213.75204, + "ratio": 39.695034991220425, + "verdict": "clean", + "window_nj": 64974026000 + }, + "window_ns": 743268432 + }, + { + "child_wall_ns": 744301196, + "decodes": 8, + "load_avg_after": [ + 4.19140625, + 3.6337890625, + 4.8486328125 + ], + "load_avg_before": [ + 4.19140625, + 3.6337890625, + 4.8486328125 + ], + "rapl": { + "busy": { + "advanced": 7, + "intervals": 7, + "longest_run": 7, + "mean_w": 84.71644067700356, + "sd_w": 3.3047163040748804, + "span_ns": 702642530 + }, + "covered_ns": 903964879, + "idle_after": { + "advanced": 47, + "intervals": 48, + "longest_run": 43, + "mean_w": 5.792051801997809, + "sd_w": 2.066637842330055, + "span_ns": 4843881520 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.598874261663465, + "sd_w": 2.03833717619498, + "span_ns": 4844932573 + }, + "marginal_nj": 58784794449.582245, + "ratio": 38.236490219340894, + "verdict": "clean", + "window_nj": 63933293000 + }, + "window_ns": 744420161 + }, + { + "child_wall_ns": 738040307, + "decodes": 8, + "load_avg_after": [ + 3.85546875, + 3.5732421875, + 4.822265625 + ], + "load_avg_before": [ + 3.85546875, + 3.5732421875, + 4.822265625 + ], + "rapl": { + "busy": { + "advanced": 7, + "intervals": 7, + "longest_run": 7, + "mean_w": 85.38069574965527, + "sd_w": 4.267797908626952, + "span_ns": 702389677 + }, + "covered_ns": 903955095, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.876220867615998, + "sd_w": 2.1564478333308648, + "span_ns": 4845361331 + }, + "idle_before": { + "advanced": 47, + "intervals": 48, + "longest_run": 43, + "mean_w": 5.792051801997809, + "sd_w": 2.066637842330055, + "span_ns": 4843881520 + }, + "marginal_nj": 58313277735.22668, + "ratio": 36.887773580861534, + "verdict": "clean", + "window_nj": 63587075000 + }, + "window_ns": 738168359 + }, + { + "child_wall_ns": 745031383, + "decodes": 8, + "load_avg_after": [ + 3.54638671875, + 3.513671875, + 4.7958984375 + ], + "load_avg_before": [ + 3.54638671875, + 3.513671875, + 4.7958984375 + ], + "rapl": { + "busy": { + "advanced": 6, + "intervals": 6, + "longest_run": 6, + "mean_w": 83.39751601328508, + "sd_w": 3.2093027613535163, + "span_ns": 601588953 + }, + "covered_ns": 802652322, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.523982983771433, + "sd_w": 2.275760386135545, + "span_ns": 4945645959 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.876220867615998, + "sd_w": 2.1564478333308648, + "span_ns": 4845361331 + }, + "marginal_nj": 58307411953.70527, + "ratio": 34.141298249561714, + "verdict": "clean", + "window_nj": 62882612000 + }, + "window_ns": 745157342 + }, + { + "child_wall_ns": 726409888, + "decodes": 8, + "load_avg_after": [ + 3.26220703125, + 3.455078125, + 4.77001953125 + ], + "load_avg_before": [ + 3.26220703125, + 3.455078125, + 4.77001953125 + ], + "rapl": { + "busy": { + "advanced": 6, + "intervals": 6, + "longest_run": 6, + "mean_w": 84.18981831229966, + "sd_w": 4.30166250347791, + "span_ns": 602026784 + }, + "covered_ns": 802636365, + "idle_after": { + "advanced": 48, + "intervals": 49, + "longest_run": 28, + "mean_w": 5.521531690969246, + "sd_w": 2.096736599876804, + "span_ns": 4946231797 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.523982983771433, + "sd_w": 2.275760386135545, + "span_ns": 4945645959 + }, + "marginal_nj": 58354377125.95599, + "ratio": 34.567374251782894, + "verdict": "clean", + "window_nj": 62787143000 + }, + "window_ns": 726530357 + }, + { + "child_wall_ns": 744025340, + "decodes": 8, + "load_avg_after": [ + 3.00048828125, + 3.3974609375, + 4.744140625 + ], + "load_avg_before": [ + 3.00048828125, + 3.3974609375, + 4.744140625 + ], + "rapl": { + "busy": { + "advanced": 6, + "intervals": 6, + "longest_run": 6, + "mean_w": 84.53966072744286, + "sd_w": 3.0270006497946746, + "span_ns": 601859667 + }, + "covered_ns": 802481007, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.798320764047975, + "sd_w": 2.237582866901529, + "span_ns": 4943680256 + }, + "idle_before": { + "advanced": 48, + "intervals": 49, + "longest_run": 28, + "mean_w": 5.521531690969246, + "sd_w": 2.096736599876804, + "span_ns": 4946231797 + }, + "marginal_nj": 59688169701.40318, + "ratio": 35.25220704302326, + "verdict": "clean", + "window_nj": 64230153000 + }, + "window_ns": 744118417 + }, + { + "child_wall_ns": 734440900, + "decodes": 8, + "load_avg_after": [ + 5.021484375, + 3.80029296875, + 4.85986328125 + ], + "load_avg_before": [ + 2.759765625, + 3.3408203125, + 4.71826171875 + ], + "rapl": { + "busy": { + "advanced": 6, + "intervals": 6, + "longest_run": 6, + "mean_w": 85.32641117857419, + "sd_w": 3.911419019502624, + "span_ns": 602564637 + }, + "covered_ns": 804442580, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.620035952228466, + "sd_w": 1.7652649004006904, + "span_ns": 4946044625 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.798320764047975, + "sd_w": 2.237582866901529, + "span_ns": 4943680256 + }, + "marginal_nj": 58447841831.899124, + "ratio": 35.58180302420941, + "verdict": "clean", + "window_nj": 63040548000 + }, + "window_ns": 734546520 + } + ], + "sample_s": 0.1 + }, + { + "child": [ + "nice", + "-n", + "10", + "idf/bin/base", + "serve", + "idf/work/jobs.energy.q2.tsv" + ], + "counters": [ + "rapl" + ], + "host": { + "machine": "x86_64", + "node_cpu": 32, + "system": "Linux" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "linux-int-cpu-q2-before", + "meta": { + "fabric": "CPU", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512, integrated GPU PCI vendor 0x1002", + "kernels": "the record's (DET1 build, kernel set wide)", + "measured": "2026-10-25", + "nice": 10, + "path": "integer", + "pictures": 8, + "quality": 2, + "system": "Linux 7.0", + "threads": "all (32)" + }, + "rounds": [ + { + "child_wall_ns": 2770072917, + "decodes": 8, + "load_avg_after": [ + 2.5048828125, + 3.32470703125, + 4.84912109375 + ], + "load_avg_before": [ + 2.54931640625, + 3.34716796875, + 4.86474609375 + ], + "rapl": { + "busy": { + "advanced": 27, + "intervals": 27, + "longest_run": 27, + "mean_w": 127.72600112682908, + "sd_w": 19.84564931051117, + "span_ns": 2718049587 + }, + "covered_ns": 2919905788, + "idle_after": { + "advanced": 45, + "intervals": 48, + "longest_run": 16, + "mean_w": 5.4968792757392695, + "sd_w": 2.8777934873465076, + "span_ns": 4845601457 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.720596050703681, + "sd_w": 1.6054162633030213, + "span_ns": 4944086554 + }, + "marginal_nj": 338145024433.786, + "ratio": 42.434338669730884, + "verdict": "clean", + "window_nj": 354522010000 + }, + "window_ns": 2770228321 + }, + { + "child_wall_ns": 2766259526, + "decodes": 8, + "load_avg_after": [ + 2.30419921875, + 3.26904296875, + 4.82275390625 + ], + "load_avg_before": [ + 2.30419921875, + 3.26904296875, + 4.82275390625 + ], + "rapl": { + "busy": { + "advanced": 27, + "intervals": 27, + "longest_run": 27, + "mean_w": 128.28265030651932, + "sd_w": 19.63190584125974, + "span_ns": 2714479787 + }, + "covered_ns": 2915605574, + "idle_after": { + "advanced": 47, + "intervals": 48, + "longest_run": 24, + "mean_w": 5.667700451444533, + "sd_w": 2.009008482418042, + "span_ns": 4844321363 + }, + "idle_before": { + "advanced": 45, + "intervals": 48, + "longest_run": 16, + "mean_w": 5.4968792757392695, + "sd_w": 2.8777934873465076, + "span_ns": 4845601457 + }, + "marginal_nj": 337915674558.0278, + "ratio": 42.63695813561114, + "verdict": "clean", + "window_nj": 354191430000 + }, + "window_ns": 2766425280 + }, + { + "child_wall_ns": 2766382790, + "decodes": 8, + "load_avg_after": [ + 2.10986328125, + 3.19384765625, + 4.78125 + ], + "load_avg_before": [ + 2.11962890625, + 3.21435546875, + 4.79638671875 + ], + "rapl": { + "busy": { + "advanced": 27, + "intervals": 27, + "longest_run": 27, + "mean_w": 128.4192740213022, + "sd_w": 20.273247971092847, + "span_ns": 2712348452 + }, + "covered_ns": 2913762100, + "idle_after": { + "advanced": 46, + "intervals": 48, + "longest_run": 24, + "mean_w": 5.522066835026831, + "sd_w": 3.1880320528973014, + "span_ns": 4846841850 + }, + "idle_before": { + "advanced": 47, + "intervals": 48, + "longest_run": 24, + "mean_w": 5.667700451444533, + "sd_w": 2.009008482418042, + "span_ns": 4844321363 + }, + "marginal_nj": 337927233086.42993, + "ratio": 38.52671125638245, + "verdict": "clean", + "window_nj": 354229393000 + }, + "window_ns": 2766484683 + }, + { + "child_wall_ns": 2765995164, + "decodes": 8, + "load_avg_after": [ + 1.9404296875, + 3.140625, + 4.75537109375 + ], + "load_avg_before": [ + 1.9404296875, + 3.140625, + 4.75537109375 + ], + "rapl": { + "busy": { + "advanced": 27, + "intervals": 27, + "longest_run": 27, + "mean_w": 128.60677100340885, + "sd_w": 20.64397476860909, + "span_ns": 2715434054 + }, + "covered_ns": 2916672639, + "idle_after": { + "advanced": 47, + "intervals": 48, + "longest_run": 40, + "mean_w": 5.647943740439421, + "sd_w": 2.028004231236925, + "span_ns": 4845668329 + }, + "idle_before": { + "advanced": 46, + "intervals": 48, + "longest_run": 24, + "mean_w": 5.522066835026831, + "sd_w": 3.1880320528973014, + "span_ns": 4846841850 + }, + "marginal_nj": 338005461888.59845, + "ratio": 38.588622596774975, + "verdict": "clean", + "window_nj": 354295094000 + }, + "window_ns": 2766100213 + }, + { + "child_wall_ns": 2761256699, + "decodes": 8, + "load_avg_after": [ + 2.92333984375, + 3.302734375, + 4.7900390625 + ], + "load_avg_before": [ + 1.78466796875, + 3.08837890625, + 4.7294921875 + ], + "rapl": { + "busy": { + "advanced": 27, + "intervals": 27, + "longest_run": 27, + "mean_w": 128.92715572302077, + "sd_w": 20.645464208828685, + "span_ns": 2711271708 + }, + "covered_ns": 2915150399, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.671626526293012, + "sd_w": 2.273515425333318, + "span_ns": 4844075267 + }, + "idle_before": { + "advanced": 47, + "intervals": 48, + "longest_run": 40, + "mean_w": 5.647943740439421, + "sd_w": 2.028004231236925, + "span_ns": 4845668329 + }, + "marginal_nj": 337368210110.2132, + "ratio": 54.21884066239948, + "verdict": "clean", + "window_nj": 353867335000 + }, + "window_ns": 2761370264 + }, + { + "child_wall_ns": 2769748352, + "decodes": 8, + "load_avg_after": [ + 5.35693359375, + 3.79150390625, + 4.93212890625 + ], + "load_avg_before": [ + 2.68896484375, + 3.24755859375, + 4.76416015625 + ], + "rapl": { + "busy": { + "advanced": 27, + "intervals": 27, + "longest_run": 27, + "mean_w": 128.2115126999112, + "sd_w": 20.397906687546204, + "span_ns": 2718442359 + }, + "covered_ns": 2921093764, + "idle_after": { + "advanced": 47, + "intervals": 48, + "longest_run": 33, + "mean_w": 5.674966419732039, + "sd_w": 2.5372391227905013, + "span_ns": 4844498574 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.671626526293012, + "sd_w": 2.273515425333318, + "span_ns": 4844075267 + }, + "marginal_nj": 338390064051.3599, + "ratio": 48.29588789098007, + "verdict": "clean", + "window_nj": 354962295000 + }, + "window_ns": 2769855604 + }, + { + "child_wall_ns": 2771338444, + "decodes": 8, + "load_avg_after": [ + 4.927734375, + 3.728515625, + 4.9052734375 + ], + "load_avg_before": [ + 4.927734375, + 3.728515625, + 4.9052734375 + ], + "rapl": { + "busy": { + "advanced": 27, + "intervals": 27, + "longest_run": 27, + "mean_w": 128.52443755241583, + "sd_w": 20.939757070678315, + "span_ns": 2717483196 + }, + "covered_ns": 2919849673, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.240603923380608, + "sd_w": 2.0412078991082954, + "span_ns": 4843453350 + }, + "idle_before": { + "advanced": 47, + "intervals": 48, + "longest_run": 33, + "mean_w": 5.674966419732039, + "sd_w": 2.5372391227905013, + "span_ns": 4844498574 + }, + "marginal_nj": 338614004751.52704, + "ratio": 48.50416000424453, + "verdict": "clean", + "window_nj": 354549917000 + }, + "window_ns": 2771472388 + } + ], + "sample_s": 0.1 + }, { "child": [ "nice", @@ -16802,6 +24095,1087 @@ ], "sample_s": 0.1 }, + { + "child": [ + "nice", + "-n", + "10", + "env", + "WAI_INT_ISA=avx2", + "idf/bin/native.c3", + "serve", + "idf/work/jobs.energy.q6.tsv" + ], + "counters": [ + "rapl" + ], + "host": { + "machine": "x86_64", + "node_cpu": 32, + "system": "Linux" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "linux-int-cpu-q6-after-avx2", + "meta": { + "fabric": "CPU", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512, integrated GPU PCI vendor 0x1002", + "kernels": "narrow, kernel set avx2", + "measured": "2026-10-25", + "nice": 10, + "path": "integer", + "pictures": 8, + "quality": 6, + "system": "Linux 7.0", + "threads": "all (32)" + }, + "rounds": [ + { + "child_wall_ns": 1334396600, + "decodes": 8, + "load_avg_after": [ + 2.3720703125, + 4.81884765625, + 5.1416015625 + ], + "load_avg_before": [ + 2.49169921875, + 4.8837890625, + 5.1640625 + ], + "rapl": { + "busy": { + "advanced": 12, + "intervals": 12, + "longest_run": 12, + "mean_w": 105.06554975633155, + "sd_w": 17.06603947976447, + "span_ns": 1203194639 + }, + "covered_ns": 1404945467, + "idle_after": { + "advanced": 47, + "intervals": 48, + "longest_run": 36, + "mean_w": 5.552564436228556, + "sd_w": 2.3517686535936413, + "span_ns": 4844064552 + }, + "idle_before": { + "advanced": 48, + "intervals": 49, + "longest_run": 48, + "mean_w": 5.6554298573885635, + "sd_w": 2.3316821660364844, + "span_ns": 4946056355 + }, + "marginal_nj": 132214034611.51038, + "ratio": 42.29223501960529, + "verdict": "clean", + "window_nj": 140087345000 + }, + "window_ns": 1334676237 + }, + { + "child_wall_ns": 1321158747, + "decodes": 8, + "load_avg_after": [ + 2.181640625, + 4.73876953125, + 5.11376953125 + ], + "load_avg_before": [ + 2.181640625, + 4.73876953125, + 5.11376953125 + ], + "rapl": { + "busy": { + "advanced": 13, + "intervals": 13, + "longest_run": 13, + "mean_w": 105.21964843996503, + "sd_w": 15.769859144297717, + "span_ns": 1305971787 + }, + "covered_ns": 1507324775, + "idle_after": { + "advanced": 47, + "intervals": 48, + "longest_run": 24, + "mean_w": 5.6411247939530575, + "sd_w": 2.132714912938306, + "span_ns": 4843332215 + }, + "idle_before": { + "advanced": 47, + "intervals": 48, + "longest_run": 36, + "mean_w": 5.552564436228556, + "sd_w": 2.3517686535936413, + "span_ns": 4844064552 + }, + "marginal_nj": 130891218449.84828, + "ratio": 42.360800954058426, + "verdict": "clean", + "window_nj": 139327481000 + }, + "window_ns": 1321254397 + }, + { + "child_wall_ns": 1317692379, + "decodes": 8, + "load_avg_after": [ + 2.0068359375, + 4.65966796875, + 5.0859375 + ], + "load_avg_before": [ + 2.0068359375, + 4.65966796875, + 5.0859375 + ], + "rapl": { + "busy": { + "advanced": 12, + "intervals": 12, + "longest_run": 12, + "mean_w": 106.76866832662797, + "sd_w": 14.501721318083993, + "span_ns": 1206257703 + }, + "covered_ns": 1406797367, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.395597729871265, + "sd_w": 1.9454438323533803, + "span_ns": 4946475012 + }, + "idle_before": { + "advanced": 47, + "intervals": 48, + "longest_run": 24, + "mean_w": 5.6411247939530575, + "sd_w": 2.132714912938306, + "span_ns": 4843332215 + }, + "marginal_nj": 131075291906.58717, + "ratio": 47.474843660759234, + "verdict": "clean", + "window_nj": 138838508000 + }, + "window_ns": 1317839716 + }, + { + "child_wall_ns": 1330482932, + "decodes": 8, + "load_avg_after": [ + 3.5400390625, + 4.88818359375, + 5.1552734375 + ], + "load_avg_before": [ + 1.845703125, + 4.58203125, + 5.05859375 + ], + "rapl": { + "busy": { + "advanced": 12, + "intervals": 12, + "longest_run": 12, + "mean_w": 105.6403682370797, + "sd_w": 15.612702912136916, + "span_ns": 1207403424 + }, + "covered_ns": 1408422213, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.535746872780954, + "sd_w": 2.2393418124474405, + "span_ns": 4946517364 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.395597729871265, + "sd_w": 1.9454438323533803, + "span_ns": 4946475012 + }, + "marginal_nj": 131701890721.83348, + "ratio": 44.73399075519865, + "verdict": "clean", + "window_nj": 139399865000 + }, + "window_ns": 1330611855 + }, + { + "child_wall_ns": 1333849988, + "decodes": 8, + "load_avg_after": [ + 3.25634765625, + 4.806640625, + 5.12744140625 + ], + "load_avg_before": [ + 3.25634765625, + 4.806640625, + 5.12744140625 + ], + "rapl": { + "busy": { + "advanced": 12, + "intervals": 12, + "longest_run": 12, + "mean_w": 104.87969467781394, + "sd_w": 16.733458957725876, + "span_ns": 1204739872 + }, + "covered_ns": 1405922187, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.647314581979674, + "sd_w": 1.9663138796958608, + "span_ns": 4845180635 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.535746872780954, + "sd_w": 2.2393418124474405, + "span_ns": 4946517364 + }, + "marginal_nj": 131952999891.08377, + "ratio": 44.33810122176872, + "verdict": "clean", + "window_nj": 139814257000 + }, + "window_ns": 1333968171 + }, + { + "child_wall_ns": 1334421496, + "decodes": 8, + "load_avg_after": [ + 3.0751953125, + 4.7431640625, + 5.10498046875 + ], + "load_avg_before": [ + 3.0751953125, + 4.7431640625, + 5.10498046875 + ], + "rapl": { + "busy": { + "advanced": 13, + "intervals": 13, + "longest_run": 13, + "mean_w": 105.73856609611872, + "sd_w": 16.193007525389216, + "span_ns": 1305518338 + }, + "covered_ns": 1507984644, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.5865736017451, + "sd_w": 1.910960258108282, + "span_ns": 4843539461 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.647314581979674, + "sd_w": 1.9663138796958608, + "span_ns": 4845180635 + }, + "marginal_nj": 132108752563.265, + "ratio": 50.91843323597076, + "verdict": "clean", + "window_nj": 140579018000 + }, + "window_ns": 1334518178 + }, + { + "child_wall_ns": 1320344899, + "decodes": 8, + "load_avg_after": [ + 2.68212890625, + 4.60302734375, + 5.05517578125 + ], + "load_avg_before": [ + 2.82861328125, + 4.6640625, + 5.0771484375 + ], + "rapl": { + "busy": { + "advanced": 12, + "intervals": 12, + "longest_run": 12, + "mean_w": 105.80191323784481, + "sd_w": 16.59197576609029, + "span_ns": 1209064314 + }, + "covered_ns": 1409529769, + "idle_after": { + "advanced": 48, + "intervals": 49, + "longest_run": 34, + "mean_w": 5.515419051390976, + "sd_w": 2.141456423249381, + "span_ns": 4944427454 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.5865736017451, + "sd_w": 1.910960258108282, + "span_ns": 4843539461 + }, + "marginal_nj": 131191328430.09271, + "ratio": 46.81436232970787, + "verdict": "clean", + "window_nj": 139015623000 + }, + "window_ns": 1320476507 + } + ], + "sample_s": 0.1 + }, + { + "child": [ + "nice", + "-n", + "10", + "env", + "WAI_INT_ISA=auto", + "idf/bin/native.c3", + "serve", + "idf/work/jobs.energy.q6.tsv" + ], + "counters": [ + "rapl" + ], + "host": { + "machine": "x86_64", + "node_cpu": 32, + "system": "Linux" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "linux-int-cpu-q6-after", + "meta": { + "fabric": "CPU", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512, integrated GPU PCI vendor 0x1002", + "kernels": "narrow, kernel set auto", + "measured": "2026-10-25", + "nice": 10, + "path": "integer", + "pictures": 8, + "quality": 6, + "system": "Linux 7.0", + "threads": "all (32)" + }, + "rounds": [ + { + "child_wall_ns": 1158458436, + "decodes": 8, + "load_avg_after": [ + 10.56787109375, + 5.98681640625, + 5.46240234375 + ], + "load_avg_before": [ + 10.56787109375, + 5.98681640625, + 5.46240234375 + ], + "rapl": { + "busy": { + "advanced": 10, + "intervals": 10, + "longest_run": 10, + "mean_w": 91.90783968707572, + "sd_w": 10.747030954132196, + "span_ns": 1006127563 + }, + "covered_ns": 1207611128, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 6.473828450701002, + "sd_w": 4.580235182225976, + "span_ns": 4943111546 + }, + "idle_before": { + "advanced": 48, + "intervals": 49, + "longest_run": 25, + "mean_w": 6.467517387130388, + "sd_w": 4.958378071574975, + "span_ns": 4944602067 + }, + "marginal_nj": 98615070377.46916, + "ratio": 17.230869759195638, + "verdict": "clean", + "window_nj": 106429127000 + }, + "window_ns": 1158717354 + }, + { + "child_wall_ns": 1161688661, + "decodes": 8, + "load_avg_after": [ + 10.94482421875, + 6.20458984375, + 5.5380859375 + ], + "load_avg_before": [ + 9.72119140625, + 5.88720703125, + 5.4326171875 + ], + "rapl": { + "busy": { + "advanced": 10, + "intervals": 10, + "longest_run": 10, + "mean_w": 90.86288318263702, + "sd_w": 10.520726413433888, + "span_ns": 1004149741 + }, + "covered_ns": 1204635094, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.792405513124854, + "sd_w": 2.0696909546182285, + "span_ns": 4945347257 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 6.473828450701002, + "sd_w": 4.580235182225976, + "span_ns": 4943111546 + }, + "marginal_nj": 98256958047.98032, + "ratio": 18.498999031649237, + "verdict": "clean", + "window_nj": 105645126000 + }, + "window_ns": 1161836510 + }, + { + "child_wall_ns": 1181688555, + "decodes": 8, + "load_avg_after": [ + 10.068359375, + 6.1015625, + 5.50830078125 + ], + "load_avg_before": [ + 10.068359375, + 6.1015625, + 5.50830078125 + ], + "rapl": { + "busy": { + "advanced": 11, + "intervals": 11, + "longest_run": 11, + "mean_w": 91.5254956761044, + "sd_w": 12.797095352953722, + "span_ns": 1105653776 + }, + "covered_ns": 1306912236, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.671576181066851, + "sd_w": 1.860704125298774, + "span_ns": 4845345387 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.792405513124854, + "sd_w": 2.0696909546182285, + "span_ns": 4945347257 + }, + "marginal_nj": 100613064025.29042, + "ratio": 41.45232631836759, + "verdict": "clean", + "window_nj": 108104273000 + }, + "window_ns": 1181848126 + }, + { + "child_wall_ns": 1166064833, + "decodes": 8, + "load_avg_after": [ + 9.26171875, + 6.0, + 5.478515625 + ], + "load_avg_before": [ + 9.26171875, + 6.0, + 5.478515625 + ], + "rapl": { + "busy": { + "advanced": 11, + "intervals": 11, + "longest_run": 11, + "mean_w": 93.23680766750573, + "sd_w": 9.859378906403586, + "span_ns": 1107446376 + }, + "covered_ns": 1308018974, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 6.741342705531669, + "sd_w": 5.038413808316888, + "span_ns": 4843861126 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.671576181066851, + "sd_w": 1.860704125298774, + "span_ns": 4845345387 + }, + "marginal_nj": 100587241286.80309, + "ratio": 17.273362517494263, + "verdict": "clean", + "window_nj": 108705408000 + }, + "window_ns": 1166196762 + }, + { + "child_wall_ns": 1157610055, + "decodes": 8, + "load_avg_after": [ + 8.52001953125, + 5.900390625, + 5.44873046875 + ], + "load_avg_before": [ + 8.52001953125, + 5.900390625, + 5.44873046875 + ], + "rapl": { + "busy": { + "advanced": 11, + "intervals": 11, + "longest_run": 11, + "mean_w": 93.51977276117492, + "sd_w": 11.439021129242132, + "span_ns": 1105530335 + }, + "covered_ns": 1306873114, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.791433022165463, + "sd_w": 2.039756353475535, + "span_ns": 4944938848 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 6.741342705531669, + "sd_w": 5.038413808316888, + "span_ns": 4843861126 + }, + "marginal_nj": 99472310178.84042, + "ratio": 17.317629757464058, + "verdict": "clean", + "window_nj": 107661684000 + }, + "window_ns": 1157732045 + }, + { + "child_wall_ns": 1141337126, + "decodes": 8, + "load_avg_after": [ + 9.9326171875, + 6.2705078125, + 5.5732421875 + ], + "load_avg_before": [ + 7.83740234375, + 5.80224609375, + 5.41943359375 + ], + "rapl": { + "busy": { + "advanced": 10, + "intervals": 10, + "longest_run": 10, + "mean_w": 92.2505891915746, + "sd_w": 11.050317939325655, + "span_ns": 1003973379 + }, + "covered_ns": 1206816015, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.3648624183349485, + "sd_w": 1.7806576571944348, + "span_ns": 4846226537 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.791433022165463, + "sd_w": 2.039756353475535, + "span_ns": 4944938848 + }, + "marginal_nj": 99081287997.1663, + "ratio": 42.491565879249976, + "verdict": "clean", + "window_nj": 105813086000 + }, + "window_ns": 1141442604 + }, + { + "child_wall_ns": 1148882500, + "decodes": 8, + "load_avg_after": [ + 9.21728515625, + 6.1826171875, + 5.54833984375 + ], + "load_avg_before": [ + 9.21728515625, + 6.1826171875, + 5.54833984375 + ], + "rapl": { + "busy": { + "advanced": 11, + "intervals": 11, + "longest_run": 11, + "mean_w": 93.2439656762214, + "sd_w": 11.86205419189256, + "span_ns": 1108120305 + }, + "covered_ns": 1309326838, + "idle_after": { + "advanced": 46, + "intervals": 48, + "longest_run": 22, + "mean_w": 5.608292536692201, + "sd_w": 2.260570069280584, + "span_ns": 4844812171 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.3648624183349485, + "sd_w": 1.7806576571944348, + "span_ns": 4846226537 + }, + "marginal_nj": 99766662859.92514, + "ratio": 38.82091043815166, + "verdict": "clean", + "window_nj": 106950386000 + }, + "window_ns": 1149016643 + } + ], + "sample_s": 0.1 + }, + { + "child": [ + "nice", + "-n", + "10", + "idf/bin/base", + "serve", + "idf/work/jobs.energy.q6.tsv" + ], + "counters": [ + "rapl" + ], + "host": { + "machine": "x86_64", + "node_cpu": 32, + "system": "Linux" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "linux-int-cpu-q6-before", + "meta": { + "fabric": "CPU", + "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512, integrated GPU PCI vendor 0x1002", + "kernels": "the record's (DET1 build, kernel set wide)", + "measured": "2026-10-25", + "nice": 10, + "path": "integer", + "pictures": 8, + "quality": 6, + "system": "Linux 7.0", + "threads": "all (32)" + }, + "rounds": [ + { + "child_wall_ns": 5816120505, + "decodes": 8, + "load_avg_after": [ + 6.15380859375, + 4.04736328125, + 4.841796875 + ], + "load_avg_before": [ + 4.15673828125, + 3.619140625, + 4.71484375 + ], + "rapl": { + "busy": { + "advanced": 57, + "intervals": 57, + "longest_run": 57, + "mean_w": 133.7629131909329, + "sd_w": 22.20543929964738, + "span_ns": 5715517821 + }, + "covered_ns": 5916833839, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.717887032262831, + "sd_w": 2.1617743090095813, + "span_ns": 4943921711 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 5.836431452122235, + "sd_w": 2.062668886534783, + "span_ns": 4845022275 + }, + "marginal_nj": 737823162702.5037, + "ratio": 59.20403134376092, + "verdict": "clean", + "window_nj": 772005654000 + }, + "window_ns": 5816396395 + }, + { + "child_wall_ns": 5812995498, + "decodes": 8, + "load_avg_after": [ + 7.77001953125, + 4.4453125, + 4.9619140625 + ], + "load_avg_before": [ + 5.66064453125, + 3.97998046875, + 4.8154296875 + ], + "rapl": { + "busy": { + "advanced": 57, + "intervals": 57, + "longest_run": 57, + "mean_w": 133.17799778696994, + "sd_w": 24.50447887708287, + "span_ns": 5719567920 + }, + "covered_ns": 5922984218, + "idle_after": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 6.695735422749844, + "sd_w": 4.963671942013216, + "span_ns": 4841585062 + }, + "idle_before": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.717887032262831, + "sd_w": 2.1617743090095813, + "span_ns": 4943921711 + }, + "marginal_nj": 735845073055.3748, + "ratio": 25.580092327367904, + "verdict": "clean", + "window_nj": 772607918000 + }, + "window_ns": 5813146093 + }, + { + "child_wall_ns": 5806599555, + "decodes": 8, + "load_avg_after": [ + 9.1376953125, + 4.830078125, + 5.08056640625 + ], + "load_avg_before": [ + 7.1474609375, + 4.37109375, + 4.93505859375 + ], + "rapl": { + "busy": { + "advanced": 57, + "intervals": 57, + "longest_run": 57, + "mean_w": 133.54264474554222, + "sd_w": 22.63987502150855, + "span_ns": 5732279167 + }, + "covered_ns": 5934650616, + "idle_after": { + "advanced": 47, + "intervals": 49, + "longest_run": 30, + "mean_w": 5.375879544813979, + "sd_w": 2.2580587687518743, + "span_ns": 4947248907 + }, + "idle_before": { + "advanced": 48, + "intervals": 48, + "longest_run": 48, + "mean_w": 6.695735422749844, + "sd_w": 4.963671942013216, + "span_ns": 4841585062 + }, + "marginal_nj": 736028591398.3163, + "ratio": 25.68800653051314, + "verdict": "clean", + "window_nj": 771849000000 + }, + "window_ns": 5806745169 + }, + { + "child_wall_ns": 5833911986, + "decodes": 8, + "load_avg_after": [ + 10.29541015625, + 5.2021484375, + 5.1982421875 + ], + "load_avg_before": [ + 8.40576171875, + 4.74951171875, + 5.05322265625 + ], + "rapl": { + "busy": { + "advanced": 57, + "intervals": 57, + "longest_run": 57, + "mean_w": 132.93576071187843, + "sd_w": 24.927938851689248, + "span_ns": 5721393818 + }, + "covered_ns": 5923011826, + "idle_after": { + "advanced": 47, + "intervals": 48, + "longest_run": 30, + "mean_w": 7.042184306920624, + "sd_w": 5.62101618477264, + "span_ns": 4841787090 + }, + "idle_before": { + "advanced": 47, + "intervals": 49, + "longest_run": 30, + "mean_w": 5.375879544813979, + "sd_w": 2.2580587687518743, + "span_ns": 4947248907 + }, + "marginal_nj": 737088351475.0764, + "ratio": 22.545163475834574, + "verdict": "clean", + "window_nj": 773864521000 + }, + "window_ns": 5834040038 + }, + { + "child_wall_ns": 5824293639, + "decodes": 8, + "load_avg_after": [ + 8.7919921875, + 5.04736328125, + 5.1474609375 + ], + "load_avg_before": [ + 9.470703125, + 5.11572265625, + 5.169921875 + ], + "rapl": { + "busy": { + "advanced": 57, + "intervals": 57, + "longest_run": 57, + "mean_w": 133.59438023733492, + "sd_w": 22.283710300449375, + "span_ns": 5725874300 + }, + "covered_ns": 5927780041, + "idle_after": { + "advanced": 48, + "intervals": 49, + "longest_run": 33, + "mean_w": 5.480800870593456, + "sd_w": 2.782948985237848, + "span_ns": 4945016684 + }, + "idle_before": { + "advanced": 47, + "intervals": 48, + "longest_run": 30, + "mean_w": 7.042184306920624, + "sd_w": 5.62101618477264, + "span_ns": 4841787090 + }, + "marginal_nj": 735649286205.4966, + "ratio": 22.65300142588511, + "verdict": "clean", + "window_nj": 772766037000 + }, + "window_ns": 5824411151 + }, + { + "child_wall_ns": 5838106438, + "decodes": 8, + "load_avg_after": [ + 11.91845703125, + 5.88720703125, + 5.4189453125 + ], + "load_avg_before": [ + 8.087890625, + 4.96337890625, + 5.11962890625 + ], + "rapl": { + "busy": { + "advanced": 57, + "intervals": 57, + "longest_run": 57, + "mean_w": 133.0967179566213, + "sd_w": 24.534811107992645, + "span_ns": 5724152213 + }, + "covered_ns": 5927745585, + "idle_after": { + "advanced": 46, + "intervals": 48, + "longest_run": 28, + "mean_w": 5.709477571210338, + "sd_w": 2.5082035365482502, + "span_ns": 4845294463 + }, + "idle_before": { + "advanced": 48, + "intervals": 49, + "longest_run": 33, + "mean_w": 5.480800870593456, + "sd_w": 2.782948985237848, + "span_ns": 4945016684 + }, + "marginal_nj": 741334599185.8384, + "ratio": 45.81527703599724, + "verdict": "clean", + "window_nj": 774501161000 + }, + "window_ns": 5838243898 + }, + { + "child_wall_ns": 5827184404, + "decodes": 8, + "load_avg_after": [ + 12.48828125, + 6.19140625, + 5.52197265625 + ], + "load_avg_before": [ + 10.9638671875, + 5.7890625, + 5.3896484375 + ], + "rapl": { + "busy": { + "advanced": 57, + "intervals": 57, + "longest_run": 57, + "mean_w": 133.5891755226034, + "sd_w": 23.50108802686957, + "span_ns": 5722230696 + }, + "covered_ns": 5922863376, + "idle_after": { + "advanced": 49, + "intervals": 49, + "longest_run": 49, + "mean_w": 5.586440435813172, + "sd_w": 1.9739808934092355, + "span_ns": 4945408575 + }, + "idle_before": { + "advanced": 46, + "intervals": 48, + "longest_run": 28, + "mean_w": 5.709477571210338, + "sd_w": 2.5082035365482502, + "span_ns": 4845294463 + }, + "marginal_nj": 739688423468.9508, + "ratio": 51.00910458613032, + "verdict": "clean", + "window_nj": 773140513000 + }, + "window_ns": 5827353152 + } + ], + "sample_s": 0.1 + }, { "child": [ "nice", @@ -17879,6 +26253,199 @@ ], "sample_s": 0.1 }, + { + "child": [ + "venvs/sota-int/bin/python", + "-I", + "wai/tools/wai_determinism_cost.py", + "fserve", + "det1/work", + "2", + "--device", + "cpu", + "--ids", + "a56e27cbf5f843c0,4f4b74a79237e311,e25ca1ff2f0c0cb5,e3b946107c5d3441,10349e963c5c813d,363510303b715d4c,b77d3f006f42414b,ba23983c76b4832e" + ], + "counters": [ + "task" + ], + "host": { + "machine": "arm64", + "node_cpu": 32, + "system": "Darwin" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "macos-float-cpu-q2-again", + "meta": { + "fabric": "CPU", + "hardware": "arm64 desktop SoC, 24 performance and 8 efficiency cores, 80-core integrated GPU", + "measured": "2026-10-25", + "path": "float", + "pictures": 8, + "quality": 2, + "system": "macOS 27.0.1", + "threads": "the library default" + }, + "rounds": [ + { + "child_note": "mismatches=0", + "child_wall_ns": 2644356333, + "decodes": 8, + "load_avg_after": [ + 45.423828125, + 32.49755859375, + 17.80810546875 + ], + "load_avg_before": [ + 46.7666015625, + 32.5400390625, + 17.736328125 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 3107679583.333332, + "energy_nj": 6874773372, + "serviced_nj": 0 + }, + "window_ns": 2644504000 + }, + { + "child_note": "mismatches=0", + "child_wall_ns": 1123303125, + "decodes": 8, + "load_avg_after": [ + 43.7880859375, + 32.373046875, + 17.85009765625 + ], + "load_avg_before": [ + 43.7880859375, + 32.373046875, + 17.85009765625 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 4406632333.333332, + "energy_nj": 8420728150, + "serviced_nj": 0 + }, + "window_ns": 1123448750 + }, + { + "child_note": "mismatches=0", + "child_wall_ns": 1699933083, + "decodes": 8, + "load_avg_after": [ + 42.603515625, + 32.31689453125, + 17.9150390625 + ], + "load_avg_before": [ + 42.603515625, + 32.31689453125, + 17.9150390625 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 3147551041.666668, + "energy_nj": 6693833363, + "serviced_nj": 0 + }, + "window_ns": 1700096167 + }, + { + "child_note": "mismatches=0", + "child_wall_ns": 1898487209, + "decodes": 8, + "load_avg_after": [ + 41.9375, + 32.4921875, + 18.14404296875 + ], + "load_avg_before": [ + 40.71337890625, + 32.095703125, + 17.92138671875 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 3600394958.333332, + "energy_nj": 7387166473, + "serviced_nj": 0 + }, + "window_ns": 1898613458 + }, + { + "child_note": "mismatches=0", + "child_wall_ns": 1375474750, + "decodes": 8, + "load_avg_after": [ + 39.9404296875, + 32.23486328125, + 18.13720703125 + ], + "load_avg_before": [ + 39.9404296875, + 32.23486328125, + 18.13720703125 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 4739446041.666668, + "energy_nj": 8595248003, + "serviced_nj": 0 + }, + "window_ns": 1375594125 + }, + { + "child_note": "mismatches=0", + "child_wall_ns": 1331195750, + "decodes": 8, + "load_avg_after": [ + 40.26513671875, + 32.43017578125, + 18.28857421875 + ], + "load_avg_before": [ + 40.26513671875, + 32.43017578125, + 18.28857421875 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 4588937833.333332, + "energy_nj": 8328614073, + "serviced_nj": 0 + }, + "window_ns": 1331308042 + }, + { + "child_note": "mismatches=0", + "child_wall_ns": 2041213334, + "decodes": 8, + "load_avg_after": [ + 41.58544921875, + 32.96142578125, + 18.6416015625 + ], + "load_avg_before": [ + 40.24365234375, + 32.5556640625, + 18.41552734375 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 2807989916.6666718, + "energy_nj": 6490580132, + "serviced_nj": 0 + }, + "window_ns": 2041335500 + } + ], + "sample_s": 0.1 + }, { "child": [ "venvs/sota-int/bin/python", @@ -18071,6 +26638,199 @@ ], "sample_s": 0.1 }, + { + "child": [ + "venvs/sota-int/bin/python", + "-I", + "wai/tools/wai_determinism_cost.py", + "fserve", + "det1/work", + "6", + "--device", + "cpu", + "--ids", + "a56e27cbf5f843c0,4f4b74a79237e311,e25ca1ff2f0c0cb5,e3b946107c5d3441,10349e963c5c813d,363510303b715d4c,b77d3f006f42414b,ba23983c76b4832e" + ], + "counters": [ + "task" + ], + "host": { + "machine": "arm64", + "node_cpu": 32, + "system": "Darwin" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "macos-float-cpu-q6-again", + "meta": { + "fabric": "CPU", + "hardware": "arm64 desktop SoC, 24 performance and 8 efficiency cores, 80-core integrated GPU", + "measured": "2026-10-25", + "path": "float", + "pictures": 8, + "quality": 6, + "system": "macOS 27.0.1", + "threads": "the library default" + }, + "rounds": [ + { + "child_note": "mismatches=0", + "child_wall_ns": 3554620625, + "decodes": 8, + "load_avg_after": [ + 58.43310546875, + 41.771484375, + 24.4609375 + ], + "load_avg_before": [ + 58.43310546875, + 41.771484375, + 24.4609375 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 4358264375.0, + "energy_nj": 10110349997, + "serviced_nj": 0 + }, + "window_ns": 3554892875 + }, + { + "child_note": "mismatches=0", + "child_wall_ns": 4789812542, + "decodes": 8, + "load_avg_after": [ + 57.57763671875, + 42.166015625, + 24.8046875 + ], + "load_avg_before": [ + 60.23974609375, + 42.42236328125, + 24.7919921875 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 4087513500.0, + "energy_nj": 9752724116, + "serviced_nj": 0 + }, + "window_ns": 4790067459 + }, + { + "child_note": "mismatches=0", + "child_wall_ns": 4441958125, + "decodes": 8, + "load_avg_after": [ + 61.3349609375, + 43.49072265625, + 25.47802734375 + ], + "load_avg_before": [ + 60.494140625, + 43.0263671875, + 25.2099609375 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 4012523916.666668, + "energy_nj": 9941771732, + "serviced_nj": 0 + }, + "window_ns": 4442092834 + }, + { + "child_note": "mismatches=0", + "child_wall_ns": 2170184583, + "decodes": 8, + "load_avg_after": [ + 56.62939453125, + 43.06396484375, + 25.53662109375 + ], + "load_avg_before": [ + 58.42529296875, + 43.18359375, + 25.47509765625 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 4991260208.333332, + "energy_nj": 10201868134, + "serviced_nj": 0 + }, + "window_ns": 2170361084 + }, + { + "child_note": "mismatches=0", + "child_wall_ns": 1752339500, + "decodes": 8, + "load_avg_after": [ + 53.85595703125, + 42.7138671875, + 25.515625 + ], + "load_avg_before": [ + 53.85595703125, + 42.7138671875, + 25.515625 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 4820367291.666668, + "energy_nj": 11303808401, + "serviced_nj": 0 + }, + "window_ns": 1752454292 + }, + { + "child_note": "mismatches=0", + "child_wall_ns": 1725658000, + "decodes": 8, + "load_avg_after": [ + 50.82421875, + 42.27001953125, + 25.45947265625 + ], + "load_avg_before": [ + 50.82421875, + 42.27001953125, + 25.45947265625 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 5553943583.333332, + "energy_nj": 12110822659, + "serviced_nj": 0 + }, + "window_ns": 1725761334 + }, + { + "child_note": "mismatches=0", + "child_wall_ns": 2036484042, + "decodes": 8, + "load_avg_after": [ + 45.6298828125, + 41.4375, + 25.3603515625 + ], + "load_avg_before": [ + 48.03515625, + 41.83349609375, + 25.40380859375 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 4896574500.0, + "energy_nj": 11153494016, + "serviced_nj": 0 + }, + "window_ns": 2036584334 + } + ], + "sample_s": 0.1 + }, { "child": [ "venvs/sota-int/bin/python", @@ -19167,6 +27927,368 @@ ], "sample_s": 0.1 }, + { + "child": [ + "env", + "WAI_INT_ISA=auto", + "idf/bin/native.c3", + "serve", + "det1/work/jobs.energy.q2.tsv" + ], + "counters": [ + "task" + ], + "host": { + "machine": "arm64", + "node_cpu": 32, + "system": "Darwin" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "macos-int-cpu-q2-after", + "meta": { + "fabric": "CPU", + "hardware": "arm64 desktop SoC, 24 performance and 8 efficiency cores, 80-core integrated GPU", + "kernels": "narrow, kernel set auto", + "measured": "2026-10-25", + "path": "integer", + "pictures": 8, + "quality": 2, + "system": "macOS 27.0.1", + "threads": "all (32)" + }, + "rounds": [ + { + "child_wall_ns": 3080168375, + "decodes": 8, + "load_avg_after": [ + 38.79345703125, + 28.3828125, + 15.2919921875 + ], + "load_avg_before": [ + 32.42138671875, + 26.9716796875, + 14.72216796875 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 10006252416.666668, + "energy_nj": 22650062971, + "serviced_nj": 0 + }, + "window_ns": 3081448541 + }, + { + "child_wall_ns": 2479591708, + "decodes": 8, + "load_avg_after": [ + 41.44140625, + 29.2783203125, + 15.76171875 + ], + "load_avg_before": [ + 39.1298828125, + 28.625, + 15.4541015625 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 9830265541.666664, + "energy_nj": 23297761317, + "serviced_nj": 0 + }, + "window_ns": 2479816042 + }, + { + "child_wall_ns": 1432425875, + "decodes": 8, + "load_avg_after": [ + 41.00537109375, + 29.3896484375, + 15.8798828125 + ], + "load_avg_before": [ + 41.00537109375, + 29.3896484375, + 15.8798828125 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 9444468791.666664, + "energy_nj": 23680811841, + "serviced_nj": 0 + }, + "window_ns": 1432517666 + }, + { + "child_wall_ns": 2192454209, + "decodes": 8, + "load_avg_after": [ + 44.8486328125, + 30.37890625, + 16.30810546875 + ], + "load_avg_before": [ + 44.8486328125, + 30.37890625, + 16.30810546875 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 9489308125.0, + "energy_nj": 23951203956, + "serviced_nj": 0 + }, + "window_ns": 2192604334 + }, + { + "child_wall_ns": 1235126416, + "decodes": 8, + "load_avg_after": [ + 45.40283203125, + 30.9619140625, + 16.67822265625 + ], + "load_avg_before": [ + 44.2197265625, + 30.48876953125, + 16.42919921875 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 9599755625.0, + "energy_nj": 23683061348, + "serviced_nj": 0 + }, + "window_ns": 1235239167 + }, + { + "child_wall_ns": 3012868416, + "decodes": 8, + "load_avg_after": [ + 46.0107421875, + 31.32763671875, + 16.890625 + ], + "load_avg_before": [ + 46.0107421875, + 31.32763671875, + 16.890625 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 9659106458.333328, + "energy_nj": 23405257079, + "serviced_nj": 0 + }, + "window_ns": 3013572250 + }, + { + "child_wall_ns": 786318000, + "decodes": 8, + "load_avg_after": [ + 44.16796875, + 31.18896484375, + 16.92626953125 + ], + "load_avg_before": [ + 44.16796875, + 31.18896484375, + 16.92626953125 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 9059123916.666672, + "energy_nj": 24959066640, + "serviced_nj": 0 + }, + "window_ns": 786380375 + } + ], + "sample_s": 0.1 + }, + { + "child": [ + "det1/bin/wai_det_cost.native", + "serve", + "det1/work/jobs.energy.q2.tsv" + ], + "counters": [ + "task" + ], + "host": { + "machine": "arm64", + "node_cpu": 32, + "system": "Darwin" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "macos-int-cpu-q2-before", + "meta": { + "fabric": "CPU", + "hardware": "arm64 desktop SoC, 24 performance and 8 efficiency cores, 80-core integrated GPU", + "kernels": "the record's (DET1 build, kernel set wide)", + "measured": "2026-10-25", + "path": "integer", + "pictures": 8, + "quality": 2, + "system": "macOS 27.0.1", + "threads": "all (32)" + }, + "rounds": [ + { + "child_wall_ns": 2706281500, + "decodes": 8, + "load_avg_after": [ + 41.8701171875, + 27.51220703125, + 14.2431640625 + ], + "load_avg_before": [ + 41.68505859375, + 27.23388671875, + 14.06787109375 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 14272752041.666668, + "energy_nj": 35774842518, + "serviced_nj": 0 + }, + "window_ns": 2706449459 + }, + { + "child_wall_ns": 999186375, + "decodes": 8, + "load_avg_after": [ + 40.11865234375, + 27.38720703125, + 14.27685546875 + ], + "load_avg_before": [ + 40.11865234375, + 27.38720703125, + 14.27685546875 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 12883771250.0, + "energy_nj": 38052648063, + "serviced_nj": 0 + }, + "window_ns": 999265792 + }, + { + "child_wall_ns": 849616042, + "decodes": 8, + "load_avg_after": [ + 38.10693359375, + 27.18115234375, + 14.28076171875 + ], + "load_avg_before": [ + 38.10693359375, + 27.18115234375, + 14.28076171875 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 12264238750.0, + "energy_nj": 38817611863, + "serviced_nj": 0 + }, + "window_ns": 849670709 + }, + { + "child_wall_ns": 940475083, + "decodes": 8, + "load_avg_after": [ + 35.85595703125, + 26.8955078125, + 14.25537109375 + ], + "load_avg_before": [ + 35.85595703125, + 26.8955078125, + 14.25537109375 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 13313509416.666664, + "energy_nj": 39231641508, + "serviced_nj": 0 + }, + "window_ns": 940555333 + }, + { + "child_wall_ns": 1789716500, + "decodes": 8, + "load_avg_after": [ + 36.53759765625, + 27.30224609375, + 14.544921875 + ], + "load_avg_before": [ + 33.625, + 26.58154296875, + 14.21826171875 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 13939930374.999992, + "energy_nj": 36774162145, + "serviced_nj": 0 + }, + "window_ns": 1789799542 + }, + { + "child_wall_ns": 1321974208, + "decodes": 8, + "load_avg_after": [ + 35.61328125, + 27.263671875, + 14.60595703125 + ], + "load_avg_before": [ + 35.61328125, + 27.263671875, + 14.60595703125 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 13584619875.0, + "energy_nj": 38026051779, + "serviced_nj": 0 + }, + "window_ns": 1322045208 + }, + { + "child_wall_ns": 1047343125, + "decodes": 8, + "load_avg_after": [ + 35.08349609375, + 27.29248046875, + 14.68994140625 + ], + "load_avg_before": [ + 35.08349609375, + 27.29248046875, + 14.68994140625 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 13089953500.0, + "energy_nj": 38365151351, + "serviced_nj": 0 + }, + "window_ns": 1047431875 + } + ], + "sample_s": 0.1 + }, { "child": [ "det1/bin/wai_det_cost.native", @@ -19345,6 +28467,368 @@ ], "sample_s": 0.1 }, + { + "child": [ + "env", + "WAI_INT_ISA=auto", + "idf/bin/native.c3", + "serve", + "det1/work/jobs.energy.q6.tsv" + ], + "counters": [ + "task" + ], + "host": { + "machine": "arm64", + "node_cpu": 32, + "system": "Darwin" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "macos-int-cpu-q6-after", + "meta": { + "fabric": "CPU", + "hardware": "arm64 desktop SoC, 24 performance and 8 efficiency cores, 80-core integrated GPU", + "kernels": "narrow, kernel set auto", + "measured": "2026-10-25", + "path": "integer", + "pictures": 8, + "quality": 6, + "system": "macOS 27.0.1", + "threads": "all (32)" + }, + "rounds": [ + { + "child_wall_ns": 3496535917, + "decodes": 8, + "load_avg_after": [ + 35.48046875, + 32.9921875, + 19.86767578125 + ], + "load_avg_before": [ + 31.779296875, + 32.232421875, + 19.525390625 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 19568227083.33333, + "energy_nj": 51522300504, + "serviced_nj": 0 + }, + "window_ns": 3496701333 + }, + { + "child_wall_ns": 3819604292, + "decodes": 8, + "load_avg_after": [ + 42.23193359375, + 34.5224609375, + 20.56396484375 + ], + "load_avg_before": [ + 39.20556640625, + 33.8056640625, + 20.2314453125 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 18620576875.0, + "energy_nj": 53008299578, + "serviced_nj": 0 + }, + "window_ns": 3819689958 + }, + { + "child_wall_ns": 7274432250, + "decodes": 8, + "load_avg_after": [ + 56.84619140625, + 37.8984375, + 21.92431640625 + ], + "load_avg_before": [ + 49.259765625, + 36.107421875, + 21.205078125 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 18925604916.66667, + "energy_nj": 51069735121, + "serviced_nj": 0 + }, + "window_ns": 7276328917 + }, + { + "child_wall_ns": 4118026083, + "decodes": 8, + "load_avg_after": [ + 61.5234375, + 39.52978515625, + 22.689453125 + ], + "load_avg_before": [ + 59.7412109375, + 38.81298828125, + 22.34033203125 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 18815059333.33333, + "energy_nj": 51474805077, + "serviced_nj": 0 + }, + "window_ns": 4118102667 + }, + { + "child_wall_ns": 3136830208, + "decodes": 8, + "load_avg_after": [ + 59.29345703125, + 39.76220703125, + 22.96728515625 + ], + "load_avg_before": [ + 59.3193359375, + 39.4375, + 22.75537109375 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 19060603125.0, + "energy_nj": 51362518423, + "serviced_nj": 0 + }, + "window_ns": 3136900417 + }, + { + "child_wall_ns": 3789236542, + "decodes": 8, + "load_avg_after": [ + 57.908203125, + 39.79931640625, + 23.07861328125 + ], + "load_avg_before": [ + 57.908203125, + 39.79931640625, + 23.07861328125 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 20114619250.0, + "energy_nj": 48989964192, + "serviced_nj": 0 + }, + "window_ns": 3789336750 + }, + { + "child_wall_ns": 3866733500, + "decodes": 8, + "load_avg_after": [ + 56.46142578125, + 40.07177734375, + 23.3701171875 + ], + "load_avg_before": [ + 56.15380859375, + 39.73583984375, + 23.154296875 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 19749919749.999985, + "energy_nj": 52014755603, + "serviced_nj": 0 + }, + "window_ns": 3866850541 + } + ], + "sample_s": 0.1 + }, + { + "child": [ + "det1/bin/wai_det_cost.native", + "serve", + "det1/work/jobs.energy.q6.tsv" + ], + "counters": [ + "task" + ], + "host": { + "machine": "arm64", + "node_cpu": 32, + "system": "Darwin" + }, + "idle_s": 5.0, + "jobs": 8, + "k": 1, + "label": "macos-int-cpu-q6-before", + "meta": { + "fabric": "CPU", + "hardware": "arm64 desktop SoC, 24 performance and 8 efficiency cores, 80-core integrated GPU", + "kernels": "the record's (DET1 build, kernel set wide)", + "measured": "2026-10-25", + "path": "integer", + "pictures": 8, + "quality": 6, + "system": "macOS 27.0.1", + "threads": "all (32)" + }, + "rounds": [ + { + "child_wall_ns": 3271676542, + "decodes": 8, + "load_avg_after": [ + 46.78857421875, + 34.39990234375, + 19.39697265625 + ], + "load_avg_before": [ + 38.41357421875, + 32.56689453125, + 18.6689453125 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 27770028833.33333, + "energy_nj": 88690085240, + "serviced_nj": 0 + }, + "window_ns": 3271774917 + }, + { + "child_wall_ns": 1827288083, + "decodes": 8, + "load_avg_after": [ + 44.48291015625, + 34.12744140625, + 19.388671875 + ], + "load_avg_before": [ + 44.48291015625, + 34.12744140625, + 19.388671875 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 27701666125.0, + "energy_nj": 87843971282, + "serviced_nj": 0 + }, + "window_ns": 1827582208 + }, + { + "child_wall_ns": 1858492709, + "decodes": 8, + "load_avg_after": [ + 40.9072265625, + 33.70947265625, + 19.41259765625 + ], + "load_avg_before": [ + 43.16259765625, + 34.025390625, + 19.43896484375 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 27615641500.0, + "energy_nj": 87742497468, + "serviced_nj": 0 + }, + "window_ns": 1858560875 + }, + { + "child_wall_ns": 1998211125, + "decodes": 8, + "load_avg_after": [ + 38.91259765625, + 33.4150390625, + 19.392578125 + ], + "load_avg_before": [ + 38.91259765625, + 33.4150390625, + 19.392578125 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 27262098583.33333, + "energy_nj": 88302491446, + "serviced_nj": 0 + }, + "window_ns": 1998295291 + }, + { + "child_wall_ns": 2145065916, + "decodes": 8, + "load_avg_after": [ + 37.12255859375, + 33.19189453125, + 19.47607421875 + ], + "load_avg_before": [ + 36.43701171875, + 32.99267578125, + 19.32568359375 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 29618461625.0, + "energy_nj": 89286508284, + "serviced_nj": 0 + }, + "window_ns": 2145133875 + }, + { + "child_wall_ns": 2501051375, + "decodes": 8, + "load_avg_after": [ + 35.1904296875, + 32.8564453125, + 19.43798828125 + ], + "load_avg_before": [ + 35.1904296875, + 32.8564453125, + 19.43798828125 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 27637746125.0, + "energy_nj": 88077295295, + "serviced_nj": 0 + }, + "window_ns": 2501137625 + }, + { + "child_wall_ns": 2988614833, + "decodes": 8, + "load_avg_after": [ + 34.09423828125, + 32.68310546875, + 19.5322265625 + ], + "load_avg_before": [ + 33.4931640625, + 32.54296875, + 19.40576171875 + ], + "task": { + "billed_nj": 0, + "cpu_ns": 28280290833.333344, + "energy_nj": 87473435967, + "serviced_nj": 0 + }, + "window_ns": 2988759625 + } + ], + "sample_s": 0.1 + }, { "child": [ "det1/bin/wai_det_cost.native", diff --git a/wai/research/determinism-cost/runs/README.md b/wai/research/determinism-cost/runs/README.md index 647a54c52..7d02d9d52 100644 --- a/wai/research/determinism-cost/runs/README.md +++ b/wai/research/determinism-cost/runs/README.md @@ -17,6 +17,15 @@ other two. | `linux-a-energy.sh` | x86_64 host A | the timed runs, with the package counter | | `linux-a-wasm.sh` | x86_64 host A | the build host's wasm32-wasip2 decoder under wasmtime on x86_64 | | `linux-b-decodes.sh` | x86_64 host B | `int`, `float`: the decodes on its CPU and its discrete GPU | +| `macos-narrow-build.sh` | build host | §4.6: the integer decoder of the narrow kernel sets, for the CPU, the GPU and wasm32-wasip2 | +| `macos-narrow-decodes.sh` | build host | §4.6: its decodes with each kernel set (`neon`, `neon1` on one core, `wide`), on the GPU and as wasm32-wasip2 | +| `macos-narrow-energy.sh` | build host | §4.6: the timed runs, `-before`, `-after` and `-again`, with the per-task count | +| `linux-a-narrow-jobs.sh` | x86_64 host A | §4.6: the job lists of that host's work directory | +| `linux-a-narrow-build.sh` | x86_64 host A | §4.6: the integer decoder for the CPU and the GPU | +| `linux-a-narrow-decodes.sh` | x86_64 host A | §4.6: its decodes with each kernel set, on the GPU, and the build host's wasm32-wasip2 file | +| `linux-a-narrow-energy.sh` | x86_64 host A | §4.6: the timed runs, with the package counter | +| `linux-b-narrow-decodes.sh` | x86_64 host B | §4.6: the build, then the decodes with each kernel set | +| `linux-b-narrow-energy.sh` | x86_64 host B | §4.6: the timed runs of the integer decode, with the package counter | The record names each platform by the tag its script wrote: `macos-arm64-…` for the build host, `wasm32-wasip2` for the WebAssembly build there (`wasm32-wasip2-x86_64` for the same @@ -26,3 +35,11 @@ host, `wasm32-wasip2` for the WebAssembly build there (`wasm32-wasip2-x86_64` fo On the build host the `float` and `wasm` steps ran detached, under the host's memory guard, beside the other steps: the float decodes from a copy of the checkout's `tools/wai_determinism_cost.py` and the export tool it imports, the same bytes. + +The §4.6 scripts ran from a checkout of the change that added the narrow kernel sets, in the +same way: the build host through the remote-build wrapper, in `$RB_ROOT/idf`, reading the +record's work directory there; the Linux hosts in `~/wai-build/idf` and `~/idf`, from a copy +of `wai/` and of the work directory's `sets/`, `int/`, `float/` and `pictures.json`. Each +host's `bin/base` (on the build host, the record's own `det1/bin/wai_det_cost.native`) is +the record's commit built the same way, which the `-before` runs time. The tags of §4.6 end +in `-isa-`. diff --git a/wai/research/determinism-cost/runs/linux-a-narrow-build.sh b/wai/research/determinism-cost/runs/linux-a-narrow-build.sh new file mode 100644 index 000000000..ab99b0203 --- /dev/null +++ b/wai/research/determinism-cost/runs/linux-a-narrow-build.sh @@ -0,0 +1,10 @@ +# Build the CPU and GPU (ops_gpu) integer decoders from ~/wai-build/idf/src into bin/{native,gpu}.. +set -euo pipefail +R=$HOME/wai-build/idf; W=$R/work; export CARGO_TARGET_DIR=$R/target # the toolchain: rustup 1.98.0 on PATH +TAG=${1:?tag} +cd $R/src/wai/wai-rs +nice -n 10 cargo build --locked --release --no-default-features --features neural_int --bin wai_det_cost -j 16 2>&1 | tail -1 +cp $CARGO_TARGET_DIR/release/wai_det_cost $R/bin/native.$TAG +nice -n 10 cargo build --locked --release --no-default-features --features neural_int,ops_gpu --bin wai_det_cost -j 16 2>&1 | tail -1 +cp $CARGO_TARGET_DIR/release/wai_det_cost $R/bin/gpu.$TAG +ls -la $R/bin diff --git a/wai/research/determinism-cost/runs/linux-a-narrow-decodes.sh b/wai/research/determinism-cost/runs/linux-a-narrow-decodes.sh new file mode 100644 index 000000000..63127e384 --- /dev/null +++ b/wai/research/determinism-cost/runs/linux-a-narrow-decodes.sh @@ -0,0 +1,23 @@ +# The 192 integer decodes on x86_64 host A with each kernel set, its integrated GPU, and the build host's wasm. +set -euo pipefail +R=$HOME/wai-build/idf; W=$R/work; export CARGO_TARGET_DIR=$R/target # the toolchain: rustup 1.98.0 on PATH +TAG=${1:?tag}; shift +X=$R/xplat; mkdir -p $X +OS="Linux $(uname -r), $(ldd --version 2>&1 | sed -n '1s/.* //p') glibc"; HW="x86_64 SoC, 16 cores, 32 threads, AVX-512" +meta() { printf '{"system": "%s", "hardware": "%s", "target": "%s", "fabric": "%s", "kernels": "%s", "threads": "%s", "rustc": "%s", "runtime": "%s", "measured": "%s"}\n' "$OS" "$HW" "$1" "$2" "$3" "$4" "1.98.0" "$5" "$(date +%F)"; } +for step in "$@"; do case $step in +avx512-vnni|avx512|avx2-vnni|avx2|wide) + WAI_INT_ISA=$step nice -n 10 $R/bin/native.$TAG decode $W/jobs.all.tsv > $X/linux-x86_64-cpu-isa-$step.tsv + meta x86_64-unknown-linux-gnu CPU "fast, kernel set $step" "all (32)" native > $X/linux-x86_64-cpu-isa-$step.json ;; +gpu) + WAI_INT_ISA=avx512-vnni WAI_DET_GPU_KEY=gpu-vulkan-1002 nice -n 10 $R/bin/gpu.$TAG --gpu decode $W/jobs.all.tsv > $X/linux-x86_64-gpu-vulkan-isa-avx512-vnni.raw 2> $X/linux-x86_64-gpu-vulkan-isa-avx512-vnni.err + cut -f1-4 $X/linux-x86_64-gpu-vulkan-isa-avx512-vnni.raw > $X/linux-x86_64-gpu-vulkan-isa-avx512-vnni.tsv + meta x86_64-unknown-linux-gnu "GPU, Vulkan, integrated, PCI vendor 0x1002" "fast, kernel set avx512-vnni" "all (32) for graphs off the device" "wgpu 30.0.1, $(grep -o 'adapter: .*' $X/linux-x86_64-gpu-vulkan-isa-avx512-vnni.err | head -1 | sed 's/"/'"'"'/g')" > $X/linux-x86_64-gpu-vulkan-isa-avx512-vnni.json ;; +wasm) + WT=$R/wasmtime/wasmtime-v49.0.3-x86_64-linux/wasmtime; Sd=$R/wasm-shards + rm -rf $Sd; mkdir -p $Sd + awk "{print > (\"$Sd/jobs.\" (NR-1)%16)}" $W/jobs.all.tsv + for f in $Sd/jobs.*; do nice -n 10 $WT run --dir $W::$W --dir $Sd::$Sd $R/bin/wasm.$TAG decode $f > $f.out & done; wait + cat $Sd/jobs.*.out > $X/wasm32-wasip2-x86_64-isa-wide.tsv + printf '{"system": "Linux %s", "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512", "target": "wasm32-wasip2", "fabric": "CPU, WebAssembly", "kernels": "fast, kernel set wide (the only set on wasm)", "threads": "1 (sixteen decoder processes side by side)", "rustc": "1.98.0", "runtime": "%s, the build host'"'"'s .wasm", "measured": "%s"}\n' "$(uname -r)" "$($WT --version | cut -d' ' -f1-2)" "$(date +%F)" > $X/wasm32-wasip2-x86_64-isa-wide.json ;; +esac; wc -l $X/*.tsv | tail -1; done diff --git a/wai/research/determinism-cost/runs/linux-a-narrow-energy.sh b/wai/research/determinism-cost/runs/linux-a-narrow-energy.sh new file mode 100644 index 000000000..50175077b --- /dev/null +++ b/wai/research/determinism-cost/runs/linux-a-narrow-energy.sh @@ -0,0 +1,30 @@ +# Time and energy on x86_64 host A, DET1's procedure (linux-a-energy.sh): package energy (powercap +# package-0) around each of 7 rounds of the 8 pictures, 5 s idle brackets, at q2 and q6. +# Variants: int-before (DET1's binary), int-after (this change, kernel set auto, or KSET), float (again), int-gpu-after. +set -euo pipefail +R=$HOME/wai-build/idf; W=$R/work; export CARGO_TARGET_DIR=$R/target # the toolchain: rustup 1.98.0 on PATH +TAG=${TAG:?binary tag}; PY=$R/venv/bin/python; O=$R/energy; mkdir -p $O +cd $R/src/wai +IDS=$(python3 -c "import json;print(','.join(p['id'] for p in json.load(open('$W/pictures.json'))['pictures'][:8]))") +E="python3 -I tools/wai_det_energy.py --rounds ${ROUNDS:-7} --k 1 --idle-s 5 --counter rapl" +HW='"system": "Linux 7.0", "hardware": "x86_64 SoC, 16 cores, 32 threads, AVX-512, integrated GPU PCI vendor 0x1002", "pictures": 8' +D=$(date +%F) +for q in ${QS:-2 6}; do + for v in ${VARIANTS:-int-before int-after float}; do + case $v in + int-before) L=linux-int-cpu-q$q-before; CH="$R/bin/base serve $W/jobs.energy.q$q.tsv"; M='"path": "integer", "fabric": "CPU", "threads": "all (32)", "kernels": "the record'"'"'s (DET1 build, kernel set wide)"' ;; + int-after) K=${KSET:-auto}; L=linux-int-cpu-q$q-after${KSET:+-$KSET}; CH="env WAI_INT_ISA=$K $R/bin/native.$TAG serve $W/jobs.energy.q$q.tsv"; M='"path": "integer", "fabric": "CPU", "threads": "all (32)", "kernels": "narrow, kernel set '$K'"' ;; + int-gpu-after) L=linux-int-gpu-q$q-after; CH="env WAI_DET_GPU_KEY=gpu-vulkan-1002 $R/bin/gpu.$TAG --gpu serve $W/jobs.energy.q$q.tsv"; M='"path": "integer", "fabric": "GPU (Vulkan) for the graphs it takes, CPU for the rest", "threads": "all (32)", "kernels": "narrow, kernel set auto"' ;; + float) L=linux-float-cpu-q$q-again; CH="$PY -I tools/wai_determinism_cost.py fserve $W $q --device cpu --ids $IDS"; M='"path": "float", "fabric": "CPU", "threads": "the library default"' ;; + esac + $E --label $L --meta "{$HW, $M, \"quality\": $q, \"nice\": 10, \"measured\": \"$D\"}" -- nice -n 10 $CH > $O/$L.json + python3 - $O/$L.json <<'PY' +import json, sys, statistics as st +r = json.load(open(sys.argv[1])) +ms = [x["child_wall_ns"] / x["decodes"] / 1e6 for x in r["rounds"]] +j = [x["rapl"]["marginal_nj"] / x["decodes"] / 1e9 for x in r["rounds"]] +v = [x["rapl"].get("verdict") for x in r["rounds"]] +print(f'{r["label"]}: {st.median(ms):.1f} ms ({min(ms):.1f}-{max(ms):.1f}), {st.median(j):.2f} J ({min(j):.2f}-{max(j):.2f}), verdicts {v}') +PY + done +done diff --git a/wai/research/determinism-cost/runs/linux-a-narrow-jobs.sh b/wai/research/determinism-cost/runs/linux-a-narrow-jobs.sh new file mode 100644 index 000000000..7a1272cc2 --- /dev/null +++ b/wai/research/determinism-cost/runs/linux-a-narrow-jobs.sh @@ -0,0 +1,9 @@ +# Jobs files for this host: the energy jobs (first 8 pictures) at q2 and q6, and every payload. +set -euo pipefail +R=$HOME/wai-build/idf; W=$R/work +IDS=$(python3 -c "import json;print(' '.join(p['id'] for p in json.load(open('$W/pictures.json'))['pictures'][:8]))") +ALL=$(python3 -c "import json;print(' '.join(p['id'] for p in json.load(open('$W/pictures.json'))['pictures']))") +for q in 2 6; do : > $W/jobs.energy.q$q.tsv; for i in $IDS; do printf '%s\t%s\t-\n' $W/sets/q$q $W/int/q$q/$i.wih1 >> $W/jobs.energy.q$q.tsv; done; done +: > $W/jobs.all.tsv +for q in 1 2 3 4 5 6 7 8; do for i in $ALL; do printf '%s\t%s\t-\n' $W/sets/q$q $W/int/q$q/$i.wih1 >> $W/jobs.all.tsv; done; done +wc -l $W/jobs.*.tsv diff --git a/wai/research/determinism-cost/runs/linux-b-narrow-decodes.sh b/wai/research/determinism-cost/runs/linux-b-narrow-decodes.sh new file mode 100644 index 000000000..ff0c0f635 --- /dev/null +++ b/wai/research/determinism-cost/runs/linux-b-narrow-decodes.sh @@ -0,0 +1,21 @@ +# x86_64 host B: build this change's decoder and DET1's, then the 192 integer decodes with each kernel set. +set -euo pipefail +R=$HOME/idf; W=$R/work; X=$R/xplat; mkdir -p $X $R/bin +export PATH=$HOME/.cargo/bin:$PATH CARGO_PROFILE_DEV_DEBUG=0 +TAG=${1:?tag} +cd $R/src/wai/wai-rs && CARGO_TARGET_DIR=$R/target nice -n 19 cargo build --locked --release --no-default-features --features neural_int --bin wai_det_cost -j 8 2>&1 | tail -1 +cp $R/target/release/wai_det_cost $R/bin/native.$TAG +python3 - "$W" > $W/jobs.all.tsv <<'PY' +import json, sys +w = sys.argv[1] +ids = [p["id"] for p in json.load(open(f"{w}/pictures.json"))["pictures"]] +for q in range(1, 9): + for i in ids: + print(f"{w}/sets/q{q}\t{w}/int/q{q}/{i}.wih1\t-") +PY +OS="Linux $(uname -r), $(ldd --version 2>&1 | sed -n '1s/.* //p') glibc"; HW="x86_64 laptop CPU, 14 cores, 20 threads, AVX2; discrete GPU, PCI vendor 0x10de" +for k in ${SETS:-avx2-vnni avx2 wide}; do + WAI_INT_ISA=$k nice -n 19 $R/bin/native.$TAG decode $W/jobs.all.tsv > $X/linux-x86_64-b-cpu-isa-$k.tsv + printf '{"system": "%s", "hardware": "%s", "target": "x86_64-unknown-linux-gnu", "fabric": "CPU", "kernels": "fast, kernel set %s", "threads": "all (20)", "rustc": "1.98.0", "runtime": "native", "measured": "%s"}\n' "$OS" "$HW" "$k" "$(date +%F)" > $X/linux-x86_64-b-cpu-isa-$k.json + wc -l $X/linux-x86_64-b-cpu-isa-$k.tsv +done diff --git a/wai/research/determinism-cost/runs/linux-b-narrow-energy.sh b/wai/research/determinism-cost/runs/linux-b-narrow-energy.sh new file mode 100644 index 000000000..61058c59b --- /dev/null +++ b/wai/research/determinism-cost/runs/linux-b-narrow-energy.sh @@ -0,0 +1,23 @@ +# Time and energy on x86_64 host B, DET1's procedure: package energy (powercap package-0) around +# each of 7 rounds of the 8 pictures, 5 s idle brackets, at q2 and q6; the integer decode only. +set -euo pipefail +R=$HOME/idf; W=$R/work; O=$R/energy; mkdir -p $O +export PATH=$HOME/.cargo/bin:$PATH CARGO_PROFILE_DEV_DEBUG=0 +TAG=${TAG:?tag} +[ -x $R/bin/base ] || { cd $R/src-base/wai/wai-rs && CARGO_TARGET_DIR=$R/target-base nice -n 19 cargo build --locked --release --no-default-features --features neural_int --bin wai_det_cost -j 8 2>&1 | tail -1; cp $R/target-base/release/wai_det_cost $R/bin/base; } +cd $R/src/wai +IDS=$(python3 -c "import json;print(' '.join(p['id'] for p in json.load(open('$W/pictures.json'))['pictures'][:8]))") +E="python3 -I tools/wai_det_energy.py --rounds ${ROUNDS:-7} --k 1 --idle-s 5 --counter rapl" +HW='"system": "Linux '"$(uname -r)"'", "hardware": "x86_64 laptop CPU, 14 cores, 20 threads, AVX2", "pictures": 8' +D=${MEASURED:-$(date +%F)} +for q in ${QS:-2 6}; do + : > $W/jobs.energy.q$q.tsv; for i in $IDS; do printf '%s\t%s\t-\n' $W/sets/q$q $W/int/q$q/$i.wih1 >> $W/jobs.energy.q$q.tsv; done + for v in ${VARIANTS:-int-before int-after}; do + case $v in + int-before) L=linux-b-int-cpu-q$q-before; CH="$R/bin/base serve $W/jobs.energy.q$q.tsv"; M='"path": "integer", "fabric": "CPU", "threads": "all (20)", "kernels": "the record'"'"'s (DET1 build, kernel set wide)"' ;; + int-after) L=linux-b-int-cpu-q$q-after; CH="$R/bin/native.$TAG serve $W/jobs.energy.q$q.tsv"; M='"path": "integer", "fabric": "CPU", "threads": "all (20)", "kernels": "narrow, kernel set auto"' ;; + esac + $E --label $L --meta "{$HW, $M, \"quality\": $q, \"nice\": 19, \"measured\": \"$D\"}" -- nice -n 19 $CH > $O/$L.json + done +done +ls -la $O diff --git a/wai/research/determinism-cost/runs/macos-narrow-build.sh b/wai/research/determinism-cost/runs/macos-narrow-build.sh new file mode 100644 index 000000000..c907108f8 --- /dev/null +++ b/wai/research/determinism-cost/runs/macos-narrow-build.sh @@ -0,0 +1,11 @@ +# Build the integer decoder for the CPU, for the GPU (ops_gpu) and for wasm32-wasip2 into $RB_ROOT/idf/bin/.. +set -euo pipefail +TAG=${1:?tag}; B=$RB_ROOT/idf/bin; mkdir -p $B +cd wai/wai-rs +cargo build --locked --release --no-default-features --features neural_int --bin wai_det_cost 2>&1 | tail -1 +cp $CARGO_TARGET_DIR/release/wai_det_cost $B/native.$TAG +cargo build --locked --release --no-default-features --features neural_int,ops_gpu --bin wai_det_cost 2>&1 | tail -1 +cp $CARGO_TARGET_DIR/release/wai_det_cost $B/gpu.$TAG +cargo build --locked --release --no-default-features --features neural_int --target wasm32-wasip2 --bin wai_det_cost 2>&1 | tail -1 +cp $CARGO_TARGET_DIR/wasm32-wasip2/release/wai_det_cost.wasm $B/wasm.$TAG +ls -la $B diff --git a/wai/research/determinism-cost/runs/macos-narrow-decodes.sh b/wai/research/determinism-cost/runs/macos-narrow-decodes.sh new file mode 100644 index 000000000..b38384c2e --- /dev/null +++ b/wai/research/determinism-cost/runs/macos-narrow-decodes.sh @@ -0,0 +1,28 @@ +# The 192 integer decodes on the build host's platforms with the narrow kernel sets (binaries of mac-build.sh ). +set -euo pipefail +TAG=${1:?binary tag}; shift +W=$RB_ROOT/det1/work; B=$RB_ROOT/idf/bin; X=$RB_ROOT/idf/xplat; mkdir -p $X +OS="macOS $(sw_vers -productVersion)"; HW="arm64 desktop SoC, 24 performance and 8 efficiency cores" +meta() { printf '{"system": "%s", "hardware": "%s", "target": "%s", "fabric": "%s", "kernels": "%s", "threads": "%s", "rustc": "%s", "runtime": "%s", "measured": "%s"}\n' "$OS" "$HW" "$1" "$2" "$3" "$4" "$(rustc --version | cut -d" " -f2)" "$5" "$(date +%F)"; } +for step in "$@"; do case $step in +neon) + WAI_INT_ISA=neon $B/native.$TAG decode $W/jobs.dash.tsv > $X/macos-arm64-cpu-isa-neon.tsv + meta aarch64-apple-darwin CPU "fast, kernel set neon" "all (32)" native > $X/macos-arm64-cpu-isa-neon.json ;; +neon1) + WAI_INT_ISA=neon WAI_INT_THREADS=1 $B/native.$TAG decode $W/jobs.dash.tsv > $X/macos-arm64-cpu-1t-isa-neon.tsv + meta aarch64-apple-darwin CPU "fast, kernel set neon" 1 native > $X/macos-arm64-cpu-1t-isa-neon.json ;; +wide) + WAI_INT_ISA=wide $B/native.$TAG decode $W/jobs.dash.tsv > $X/macos-arm64-cpu-isa-wide.tsv + meta aarch64-apple-darwin CPU "fast, kernel set wide" "all (32)" native > $X/macos-arm64-cpu-isa-wide.json ;; +gpu) + WAI_INT_ISA=neon $B/gpu.$TAG --gpu decode $W/jobs.dash.tsv > $X/macos-arm64-gpu-metal-isa-neon.raw 2> $X/macos-arm64-gpu-metal-isa-neon.err + cut -f1-4 $X/macos-arm64-gpu-metal-isa-neon.raw > $X/macos-arm64-gpu-metal-isa-neon.tsv + meta aarch64-apple-darwin "GPU, Metal, integrated (80 cores)" "fast, kernel set neon" "all (32) for graphs off the device" "wgpu 30.0.1, $(grep -o 'key=[^ ]*' $X/macos-arm64-gpu-metal-isa-neon.err | head -1)" > $X/macos-arm64-gpu-metal-isa-neon.json ;; +wasm) + S=$RB_ROOT/idf/wasm-shards; rm -rf $S; mkdir -p $S + awk "{print > (\"$S/jobs.\" (NR-1)%8)}" $W/jobs.dash.tsv + for f in $S/jobs.?; do wasmtime run --dir $W::$W --dir $S::$S $B/wasm.$TAG decode $f > $f.out & done; wait + cat $S/jobs.?.out > $X/wasm32-wasip2-isa-wide.tsv + meta wasm32-wasip2 "CPU, WebAssembly" "fast, kernel set wide (the only set on wasm)" "1 (eight decoder processes side by side)" "$(wasmtime --version | cut -d' ' -f1-2)" > $X/wasm32-wasip2-isa-wide.json ;; +esac; wc -l $X/*.tsv | tail -1; done +ls $X diff --git a/wai/research/determinism-cost/runs/macos-narrow-energy.sh b/wai/research/determinism-cost/runs/macos-narrow-energy.sh new file mode 100644 index 000000000..f2de4d9fb --- /dev/null +++ b/wai/research/determinism-cost/runs/macos-narrow-energy.sh @@ -0,0 +1,28 @@ +# Time and energy on the build host, DET1's procedure (macos-energy.sh): the decoder's own CPU +# energy (per-task count) over each of 7 rounds of the 8 pictures, at q2 and q6. +# Variants: int-before (DET1's binary), int-after (this change, kernel set auto, or KSET), float (again). +set -euo pipefail +TAG=${TAG:?binary tag} +W=$RB_ROOT/det1/work; B=$RB_ROOT/idf/bin; PY=$RB_ROOT/venvs/sota-int/bin/python; O=$RB_ROOT/idf/energy; mkdir -p $O +IDS=$(python3 -c "import json;print(','.join(p['id'] for p in json.load(open('$W/pictures.json'))['pictures'][:8]))") +E="python3 -I wai/tools/wai_det_energy.py --rounds ${ROUNDS:-7} --k 1 --idle-s 5" +HW='"system": "macOS 27.0.1", "hardware": "arm64 desktop SoC, 24 performance and 8 efficiency cores, 80-core integrated GPU", "pictures": 8' +D=${MEASURED:-$(date +%F)} +for q in ${QS:-2 6}; do + for v in ${VARIANTS:-int-before int-after float}; do + case $v in + int-before) L=macos-int-cpu-q$q-before; CH="$RB_ROOT/det1/bin/wai_det_cost.native serve $W/jobs.energy.q$q.tsv"; M='"path": "integer", "fabric": "CPU", "threads": "all (32)", "kernels": "the record'"'"'s (DET1 build, kernel set wide)"' ;; + int-after) K=${KSET:-auto}; L=macos-int-cpu-q$q-after${KSET:+-$KSET}; CH="env WAI_INT_ISA=$K $B/native.$TAG serve $W/jobs.energy.q$q.tsv"; M='"path": "integer", "fabric": "CPU", "threads": "all (32)", "kernels": "narrow, kernel set '$K'"' ;; + float) L=macos-float-cpu-q$q-again; CH="$PY -I wai/tools/wai_determinism_cost.py fserve $W $q --device cpu --ids $IDS"; M='"path": "float", "fabric": "CPU", "threads": "the library default"' ;; + esac + $E --label $L --counter task --meta "{$HW, $M, \"quality\": $q, \"measured\": \"$D\"}" -- $CH > $O/$L.json + python3 - $O/$L.json <<'PY' +import json, sys, statistics as st +r = json.load(open(sys.argv[1])) +ms = [x["child_wall_ns"] / x["decodes"] / 1e6 for x in r["rounds"]] +j = [x["task"]["energy_nj"] / x["decodes"] / 1e9 for x in r["rounds"]] +la = [x["load_avg_before"][0] for x in r["rounds"]] +print(f'{r["label"]}: {st.median(ms):.1f} ms ({min(ms):.1f}-{max(ms):.1f}), task {st.median(j):.2f} J ({min(j):.2f}-{max(j):.2f}), load {min(la):.0f}-{max(la):.0f}') +PY + done +done -- 2.54.0 (Apple Git-157) From c061fbb0f4da37bd1ab1f8af35bb5f8bb3683861 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 25 Oct 2026 04:07:03 -0400 Subject: [PATCH 077/242] wai-rs: the seeded root's correction is tested from seeds a correct square root never gives A correctly rounded square root seeds every u64 within one of its root, so the correction steps that undo a wrong seed ran in no test; removing the upward one passed them all. The root is now formed from any seed by isqrt_from, and the test seeds it up to a thousand below and above the root, and with the largest seed. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/int_kernels.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/wai/wai-rs/src/int_kernels.rs b/wai/wai-rs/src/int_kernels.rs index a024d91d4..0926c5c37 100644 --- a/wai/wai-rs/src/int_kernels.rs +++ b/wai/wai-rs/src/int_kernels.rs @@ -1549,7 +1549,13 @@ pub(crate) fn igdn_xm(x: &[i64], c: usize, hw: usize, frac: u32, gamma: &[i64], /// one or two from a correctly rounded root). #[inline(always)] pub(crate) fn isqrt_u64(n: u64) -> u64 { - let mut s = ((n as f64).sqrt() as u64).min(u32::MAX as u64); + isqrt_from(n, (n as f64).sqrt() as u64) +} + +/// `⌊√n⌋` from any seed: [`isqrt_u64`]'s correction, which alone decides the result. +#[inline(always)] +fn isqrt_from(n: u64, seed: u64) -> u64 { + let mut s = seed.min(u32::MAX as u64); while s * s > n { s -= 1; } @@ -2371,6 +2377,21 @@ mod tests { let sh = r.below(64) as u32; check(r.next() >> sh); } + // The correction decides the result whatever the seed: seeds below and above the + // root by up to a thousand, and the largest, each corrected to the root. (A + // correctly rounded square root seeds every `u64` within one of its root, so these + // are the seeds a platform whose square root were not would give.) + for _ in 0..20_000 { + let n = r.next() >> r.below(64) as u32; + let root = n.isqrt(); + for d in [-1000i64, -37, -2, -1, 1, 2, 37, 1000] { + let seed = root.saturating_add_signed(d); + assert_eq!(isqrt_from(n, seed), root, "n {n} seed {seed}"); + } + } + for n in [u64::MAX, u64::MAX - 1, (u32::MAX as u64) * (u32::MAX as u64)] { + assert_eq!(isqrt_from(n, u64::MAX), n.isqrt(), "{n}"); + } } /// The AVX-512 roots and products of the inverse GDN against the scalar form, on norms -- 2.54.0 (Apple Git-157) From 777dd239f470cbfc6aa7c3412c18c723ac5e15ee Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 25 Oct 2026 04:26:41 -0400 Subject: [PATCH 078/242] wai-rs: the SIMD inverse-GDN norms are tested norm by norm, and the random tests stay in Rng's range Falsifying the NEON fused-shift kernel (one lane's ssra made a rounding shift) passed every test: a norm a unit off seldom moves its integer root, so the output tests could not see it. the_simd_norms_are_the_scalar_norms holds each SIMD form of the norms (NEON at any gf and fused at 16, AVX2 with non-negative and signed gamma, AVX-512) to the scalar sum before any root is taken. The requantisation and element-wise tests drew magnitudes past Rng::signed's 2^62, which overflowed with overflow checks on; they now draw within it and list the ends of i64 instead. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/int_kernels.rs | 74 +++++++++++++++++++++++++++++++++++ wai/wai-rs/src/int_ops.rs | 25 +++++++++--- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/wai/wai-rs/src/int_kernels.rs b/wai/wai-rs/src/int_kernels.rs index 0926c5c37..790198c3e 100644 --- a/wai/wai-rs/src/int_kernels.rs +++ b/wai/wai-rs/src/int_kernels.rs @@ -2394,6 +2394,80 @@ mod tests { } } + /// Every SIMD form of the inverse GDN's norms against the scalar sum written out, + /// norm by norm, before any root is taken (a root can hide a norm that is a unit off): + /// NEON at any gf and its fused form at gf = 16; AVX2 with non-negative and with signed + /// γ; AVX-512. Shifts 0 to 31, γ and x² to the ends of `i32`, every position count up + /// to past two vector steps. + #[test] + fn the_simd_norms_are_the_scalar_norms() { + let mut r = testkit::Rng(0x16D_0002); + let scalar = |x2: &[i32], np: usize, gt: &[i32], b: usize, beta: &[i64], gf: u32| -> Vec { + let c = gt.len() / b; + let mut out = vec![0i64; b * np]; + for k in 0..b { + for p in 0..np { + let mut v = beta[k]; + for j in 0..c { + v += (gt[j * b + k] as i64 * x2[j * np + p] as i64) >> gf; + } + out[k * np + p] = v; + } + } + out + }; + let mut checked = 0; + for case in 0..400 { + let c = 1 + r.below(24); + let np = 1 + r.below(40); + let gf = [0u32, 1, 15, 16, 16, 17, 31][case % 7]; + let signed = case % 2 == 1; + let gmax = [1i64 << 8, 1 << 18, i32::MAX as i64][r.below(3)]; + let xmax = [255i64, 7527, 46340][r.below(3)]; + // Within the bound the callers prove (every partial sum in i64), with room. + if (c as u128) * (((gmax * xmax * xmax) as u128) >> gf) + (1 << 30) > (i64::MAX as u128) / 2 { + continue; + } + let x2: Vec = (0..c * np).map(|_| { let v = r.signed(xmax); (v * v) as i32 }).collect(); + let beta: Vec = (0..8).map(|_| r.signed(1 << 30)).collect(); + #[cfg(target_arch = "aarch64")] + { + let gt: Vec = (0..c * 4).map(|_| { let g = r.signed(gmax) as i32; if signed { g } else { g.abs() } }).collect(); + let want = scalar(&x2, np, >, 4, &beta[..4], gf); + let b4 = [beta[0], beta[1], beta[2], beta[3]]; + let mut got = vec![0i64; 4 * np]; + let done = neon::igdn_norms4(&x2, np, >, b4, gf, &mut got); + assert!((0..4).all(|k| got[k * np..k * np + done] == want[k * np..k * np + done]), "neon gf {gf} c {c} np {np}"); + if gf == 16 { + let mut got = vec![0i64; 4 * np]; + let done = neon::igdn_norms4_sra16(&x2, np, >, b4, &mut got); + assert!((0..4).all(|k| got[k * np..k * np + done] == want[k * np..k * np + done]), "neon ssra c {c} np {np}"); + } + checked += 1; + } + #[cfg(target_arch = "x86_64")] + for (isa, kind) in [(Isa::Avx2, x86::Kind::Avx2), (Isa::Avx512, x86::Kind::Avx512)] { + if !isa.supported() { + continue; + } + let b = x86::igdn_block(kind); + let gt: Vec = (0..c * b).map(|_| { let g = r.signed(gmax) as i32; if signed { g } else { g.abs() } }).collect(); + let want = scalar(&x2, np, >, b, &beta[..b], gf); + let mut got = vec![0i64; b * np]; + // SAFETY: the set is supported (checked above); the slices have the sizes + // the kernel reads; gf ≤ 31; the sums stay far inside i64. + let done = unsafe { x86::igdn_norms(kind, &x2, np, >, &beta[..b], gf, !signed, &mut got) }; + assert!((0..b).all(|k| got[k * np..k * np + done] == want[k * np..k * np + done]), "{isa:?} gf {gf} signed {signed} c {c} np {np}"); + checked += 1; + } + } + #[cfg(target_arch = "aarch64")] + assert!(checked >= 200, "only {checked} NEON runs checked"); + #[cfg(target_arch = "x86_64")] + assert!(!Isa::Avx2.supported() || checked >= 200, "only {checked} x86 runs checked"); + let _ = checked; + } + /// The AVX-512 roots and products of the inverse GDN against the scalar form, on norms /// of every magnitude to `i64::MAX` (negative ones clamp to 0), every perfect square /// and its neighbours near the powers of two, and inputs to ±(2^31 − 1). diff --git a/wai/wai-rs/src/int_ops.rs b/wai/wai-rs/src/int_ops.rs index e5908e099..ccb11e7a0 100644 --- a/wai/wai-rs/src/int_ops.rs +++ b/wai/wai-rs/src/int_ops.rs @@ -583,12 +583,13 @@ mod equivalence { }; for _ in 0..8 { let o = r.below(n); + // (`Rng::signed` takes magnitudes to 2^62; the ends of i64 are listed.) let v: i128 = match r.below(5) { 0 => r.signed(1 << 20) as i128, - 1 => r.signed(mag) as i128, + 1 => r.signed(mag.min(1 << 62)) as i128, 2 => [i64::MIN as i128, i64::MAX as i128, 0, -1, 1][r.below(5)], - 3 => (r.signed(i64::MAX) as i128) << 20, - _ => -((1i128 << r.below(62)) as i128), + 3 => (r.signed(1 << 62) as i128) << 20, + _ => -(1i128 << r.below(62)), }; assert_eq!(rq_fast(&q, o, v), wai_ops::interp::rq(&q, o, v), "{q:?} o {o} v {v}"); } @@ -628,7 +629,14 @@ mod equivalence { let g = graph(TensorRec::check(c, -xmax, xmax), code, c, p); let Ok(l) = load(&g) else { continue }; loaded += 1; - let x = Tensor::new(c, h, w, r.vec((c * h * w) as usize, xmax, 20)); + let mut data = r.vec((c * h * w) as usize, xmax.min(1 << 62), 20); + if xmax == i64::MAX { + // the ends of the range, which `Rng::signed` does not reach + let n = data.len(); + data[0] = i64::MAX; + data[n - 1] = -i64::MAX; + } + let x = Tensor::new(c, h, w, data); let want = with_policy(Policy::reference(), || run1(&l, x.clone(), &[])); for &pol in FAST.iter() { assert_eq!(with_policy(pol, || run1(&l, x.clone(), &[])), want, "{pol:?} operator {code}"); @@ -816,7 +824,14 @@ mod equivalence { let (xmax, gmax, bmax) = [(1i64 << 12, 1i64 << 12, 1i64 << 24), (1 << 20, 1 << 16, 1 << 40), (1 << 31, 1 << 20, 1 << 50), (1 << 40, 1 << 30, 1 << 60)][case % 4]; let p = Params::Igdn { gf, frac, g_bytes: 4, gamma: r.vec((c * c) as usize, gmax, 10), beta: r.vec(c as usize, bmax, 0) }; let g = graph(TensorRec::check(c, -xmax, xmax), opcode::IGDN, c, p); - let x = Tensor::new(c, h, w, r.vec((c * h * w) as usize, xmax, 20)); + let mut data = r.vec((c * h * w) as usize, xmax.min(1 << 62), 20); + if xmax == i64::MAX { + // the ends of the range, which `Rng::signed` does not reach + let n = data.len(); + data[0] = i64::MAX; + data[n - 1] = -i64::MAX; + } + let x = Tensor::new(c, h, w, data); let Ok(l) = load(&g) else { continue }; loaded += 1; let want = with_policy(Policy::reference(), || run1(&l, x.clone(), &[])); -- 2.54.0 (Apple Git-157) From aa7a8aca33a2bdf02700fc9081e5ce9acdd6a89b Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 25 Oct 2026 04:35:01 -0400 Subject: [PATCH 079/242] wai-rs: the narrow kernels' own clippy notes, cleared Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/int_kernels/narrow.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wai/wai-rs/src/int_kernels/narrow.rs b/wai/wai-rs/src/int_kernels/narrow.rs index fc5d7a57f..e30753c36 100644 --- a/wai/wai-rs/src/int_kernels/narrow.rs +++ b/wai/wai-rs/src/int_kernels/narrow.rs @@ -292,6 +292,7 @@ impl Ctx<'_> { /// in the low half); aarch64 as 8 16-bit weights, the lanes past `NB` zero, so that /// one vector load reads them. #[inline(always)] + #[allow(clippy::too_many_arguments)] fn push_tap(&self, taps: &mut Taps, off: usize, oc0: usize, icl: usize, second: bool, ky: usize, kx: usize) { let (g, wt) = (self.g, self.wt); #[cfg(target_arch = "x86_64")] @@ -434,7 +435,7 @@ impl Ctx<'_> { pub(super) fn tail_pairs(ws: &[i32], offs: &[usize], x: &[u32], base: usize, e0: usize, n: usize, sh: u32, acc: &mut [i64]) { for e in e0..n { let mut s = [0i64; NB]; - for (w, &o) in ws.chunks_exact(NB).zip(offs) { + for (w, &o) in ws.as_chunks::().0.iter().zip(offs) { let xv = x[o + base + e]; let (xl, xh) = (xv as u16 as i16 as i64, (xv >> 16) as u16 as i16 as i64); for j in 0..NB { @@ -454,7 +455,7 @@ pub(super) fn tail_pairs(ws: &[i32], offs: &[usize], x: &[u32], fn tail_single(ws: &[i16], offs: &[usize], x: &[i16], base: usize, e0: usize, n: usize, sh: u32, acc: &mut [i64]) { for e in e0..n { let mut s = [0i64; NB]; - for (w, &o) in ws.chunks_exact(8).zip(offs) { + for (w, &o) in ws.as_chunks::<8>().0.iter().zip(offs) { let xv = x[o + base + e] as i64; for j in 0..NB { s[j] += w[j] as i64 * xv; @@ -565,7 +566,7 @@ mod tests { let lmax = match l { Limb::Whole => xm, Limb::Hi => (xm >> 15) + 1, Limb::Lo => 32767 } as u128; let prod = wm as u128 * lmax; assert!(k >= 1 && (prod == 0 || per * k as u128 * prod <= i32::MAX as u128), "{xm} {wm} {l:?} {k}"); - total += g.terms_per_output() as u128 * prod << l.shift(); + total += (g.terms_per_output() as u128 * prod) << l.shift(); } assert!(total <= i64::MAX as u128); assert_eq!(p.limbs.len(), if xm <= 32767 { 1 } else { 2 }); -- 2.54.0 (Apple Git-157) From 986edfea3356f4001fe4f93303ca6faaa3fae206 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 25 Oct 2026 14:18:07 -0400 Subject: [PATCH 080/242] wai-rs: the narrow kernels' files are named by path, and the model checker runs the wide kernels wai-verify and byte-exact-conformance include int_kernels.rs by `path`, and a file included that way resolves its own `mod narrow;` beside it, so wai-verify's native build failed (file not found for module `narrow`). Both submodules now carry their `path`, relative to int_kernels.rs, which holds in either build. Under `cfg(kani)` the narrow sets are left out: no processor-feature probe, no SIMD, the wide kernels and the integer root, as the IGDN harness checked them before. Co-Authored-By: Claude Opus 5.5 --- wai/wai-rs/src/int_kernels.rs | 54 +++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/wai/wai-rs/src/int_kernels.rs b/wai/wai-rs/src/int_kernels.rs index 790198c3e..4999162aa 100644 --- a/wai/wai-rs/src/int_kernels.rs +++ b/wai/wai-rs/src/int_kernels.rs @@ -201,13 +201,18 @@ use std::cell::Cell; -#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] +// The narrow kernel sets (see *The narrow kernels* above). Their files are named by +// `path` so that a crate including this file by `path` (the model checker's, the goldens') +// finds them too. Under the model checker they are left out and the wide kernels run. +#[cfg(all(any(target_arch = "x86_64", target_arch = "aarch64"), not(kani)))] +#[path = "int_kernels/narrow.rs"] mod narrow; -#[cfg(target_arch = "x86_64")] +#[cfg(all(target_arch = "x86_64", not(kani)))] +#[path = "int_kernels/x86.rs"] mod x86; -/// No narrow kernels on other targets: the wide kernels run. -#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] +/// No narrow kernels on other targets, or under the model checker: the wide kernels run. +#[cfg(any(not(any(target_arch = "x86_64", target_arch = "aarch64")), kani))] mod narrow { #[allow(clippy::too_many_arguments)] #[inline(always)] @@ -312,20 +317,20 @@ impl Isa { pub fn supported(self) -> bool { match self { Isa::Auto | Isa::Wide => true, - Isa::Neon => cfg!(target_arch = "aarch64"), - #[cfg(target_arch = "x86_64")] + Isa::Neon => cfg!(all(target_arch = "aarch64", not(kani))), + #[cfg(all(target_arch = "x86_64", not(kani)))] Isa::Avx2 => std::arch::is_x86_feature_detected!("avx2"), - #[cfg(target_arch = "x86_64")] + #[cfg(all(target_arch = "x86_64", not(kani)))] Isa::Avx2Vnni => std::arch::is_x86_feature_detected!("avx2") && std::arch::is_x86_feature_detected!("avxvnni"), - #[cfg(target_arch = "x86_64")] + #[cfg(all(target_arch = "x86_64", not(kani)))] Isa::Avx512 => { std::arch::is_x86_feature_detected!("avx512f") && std::arch::is_x86_feature_detected!("avx512bw") && std::arch::is_x86_feature_detected!("avx512dq") } - #[cfg(target_arch = "x86_64")] + #[cfg(all(target_arch = "x86_64", not(kani)))] Isa::Avx512Vnni => Isa::Avx512.supported() && std::arch::is_x86_feature_detected!("avx512vnni"), - #[cfg(not(target_arch = "x86_64"))] + #[cfg(any(not(target_arch = "x86_64"), kani))] Isa::Avx2 | Isa::Avx2Vnni | Isa::Avx512 | Isa::Avx512Vnni => false, } } @@ -358,11 +363,18 @@ pub fn equivalence_policies() -> Vec { v } +/// [`Isa::Auto`]'s choice, made once per process (the wide set under the model checker). +#[cfg(kani)] +fn auto_isa() -> Isa { + Isa::Wide +} + /// [`Isa::Auto`]'s choice, made once per process. +#[cfg(not(kani))] fn auto_isa() -> Isa { static A: std::sync::OnceLock = std::sync::OnceLock::new(); *A.get_or_init(|| { - #[cfg(not(any(target_family = "wasm", kani)))] + #[cfg(not(target_family = "wasm"))] if let Ok(v) = std::env::var("WAI_INT_ISA") && let Some(s) = Isa::ALL.into_iter().find(|s| s.name() == v.trim()) && s.supported() @@ -1467,7 +1479,7 @@ where const IGDN_TILE: usize = 64; /// The x86 kernels an effective kernel set runs, if any. -#[cfg(target_arch = "x86_64")] +#[cfg(all(target_arch = "x86_64", not(kani)))] fn x86_kind(isa: Isa) -> Option { match isa { Isa::Avx2 => Some(x86::Kind::Avx2), @@ -1601,11 +1613,11 @@ fn igdn_tile_narrow( let seeded = isa != Isa::Wide; // On AVX-512 the roots and the products run in vector lanes ([`x86::igdn_emit_avx512`]) // where every |x| < 2^31; the rest of each channel's positions here. - #[cfg(target_arch = "x86_64")] + #[cfg(all(target_arch = "x86_64", not(kani)))] let lanes = narrow_out && matches!(isa, Isa::Avx512 | Isa::Avx512Vnni); let emit = |d: &mut [i64], i: usize, norm: &[i64]| { let xi = &x[i * hw + p0..i * hw + p0 + np]; - #[cfg(target_arch = "x86_64")] + #[cfg(all(target_arch = "x86_64", not(kani)))] let done = if lanes { // SAFETY: the set is one the processor runs (`Isa::effective`, which requires // F, BW and DQ for it); norm, xi and d hold np values; narrow_out is @@ -1614,7 +1626,7 @@ fn igdn_tile_narrow( } else { 0 }; - #[cfg(not(target_arch = "x86_64"))] + #[cfg(any(not(target_arch = "x86_64"), kani))] let done = 0; for ((dv, &nv), &xv) in d.iter_mut().zip(norm).zip(xi).skip(done) { let n = nv.max(0) as u64; @@ -1622,14 +1634,14 @@ fn igdn_tile_narrow( *dv = igdn_out(xv, s, frac, narrow_out); } }; - #[cfg_attr(not(any(target_arch = "aarch64", target_arch = "x86_64")), allow(unused_mut))] // advanced only by a SIMD block + #[cfg_attr(not(any(target_arch = "aarch64", all(target_arch = "x86_64", not(kani)))), allow(unused_mut))] // advanced only by a SIMD block let mut i = 0; - #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))] + #[cfg(not(any(target_arch = "aarch64", all(target_arch = "x86_64", not(kani)))))] let _ = (isa, nonneg); // Blocks of eight (AVX-512) or four (AVX2) output channels on x86_64: the norms of // the block's channels at 16 or 8 positions held in registers across every j. As on // NEON below, the bound above proves no partial sum can overflow. - #[cfg(target_arch = "x86_64")] + #[cfg(all(target_arch = "x86_64", not(kani)))] if let Some(kind) = x86_kind(isa) { let b = x86::igdn_block(kind); let mut norms = vec![0i64; b * np]; @@ -2445,7 +2457,7 @@ mod tests { } checked += 1; } - #[cfg(target_arch = "x86_64")] + #[cfg(all(target_arch = "x86_64", not(kani)))] for (isa, kind) in [(Isa::Avx2, x86::Kind::Avx2), (Isa::Avx512, x86::Kind::Avx512)] { if !isa.supported() { continue; @@ -2463,7 +2475,7 @@ mod tests { } #[cfg(target_arch = "aarch64")] assert!(checked >= 200, "only {checked} NEON runs checked"); - #[cfg(target_arch = "x86_64")] + #[cfg(all(target_arch = "x86_64", not(kani)))] assert!(!Isa::Avx2.supported() || checked >= 200, "only {checked} x86 runs checked"); let _ = checked; } @@ -2471,7 +2483,7 @@ mod tests { /// The AVX-512 roots and products of the inverse GDN against the scalar form, on norms /// of every magnitude to `i64::MAX` (negative ones clamp to 0), every perfect square /// and its neighbours near the powers of two, and inputs to ±(2^31 − 1). - #[cfg(target_arch = "x86_64")] + #[cfg(all(target_arch = "x86_64", not(kani)))] #[test] fn the_avx512_roots_and_products_are_the_scalar_ones() { if !Isa::Avx512.supported() { -- 2.54.0 (Apple Git-157) From 519c968e84327bc01fb2ff65537be70d9b873e68 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 25 Oct 2026 21:26:40 -0400 Subject: [PATCH 081/242] wai: stream audit, wai.delivery.audit (spec, reference, corpus) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A content owner's evidence from the players of a stream another service delivers over HTTP. settlement §14 acknowledges such objects by plain SHA-256 from an upstream named by its origin. stream-audit defines the session key and channel, the acknowledgement windows and chains, the WAS1 session record (spans presented, start time and stalls recomputed from the delivery-telemetry log, energy per role with its class, how the session ended), WPC1 claims for marked ads under the channel, the bundle a collector verifies, and the channel report it signs and any holder of the bundles recomputes. No price, tariff or amount. wai-rs module stream_audit (feature stream_audit), the generator wai_stream_audit_vectors, tests/stream_audit.rs, and the corpus live-conformance/audit/. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/settlement.md | 56 +- wai/extensions/stream-audit.md | 599 ++ wai/live-conformance/audit/README.md | 73 + wai/live-conformance/audit/vectors.json | 5949 +++++++++++++++++ wai/wai-rs/Cargo.toml | 18 + .../src/bin/wai_stream_audit_vectors.rs | 55 + wai/wai-rs/src/lib.rs | 7 + wai/wai-rs/src/live_metering.rs | 13 + wai/wai-rs/src/stream_audit.rs | 1377 ++++ wai/wai-rs/src/stream_audit/corpus.rs | 837 +++ wai/wai-rs/tests/stream_audit.rs | 113 + 11 files changed, 9095 insertions(+), 2 deletions(-) create mode 100644 wai/extensions/stream-audit.md create mode 100644 wai/live-conformance/audit/README.md create mode 100644 wai/live-conformance/audit/vectors.json create mode 100644 wai/wai-rs/src/bin/wai_stream_audit_vectors.rs create mode 100644 wai/wai-rs/src/stream_audit.rs create mode 100644 wai/wai-rs/src/stream_audit/corpus.rs create mode 100644 wai/wai-rs/tests/stream_audit.rs diff --git a/wai/extensions/settlement.md b/wai/extensions/settlement.md index 805d77fcf..064bc358b 100644 --- a/wai/extensions/settlement.md +++ b/wai/extensions/settlement.md @@ -41,7 +41,8 @@ rules and its chains (§3); the declaration of a delivery nobody acknowledged ( byte-range proof and the audit it answers (§5); who may trigger a retrieval that costs money, and how an acknowledgement records it (§6); the settlement statement, its lines, its tariff, its energy, its attribution lines and its recomputation (§7); verification (§8); the mapping -onto CDNI and open caching logs (§9); and a viewer that relays objects it cannot read (§10). +onto CDNI and open caching logs (§9); a viewer that relays objects it cannot read (§10); and +the acknowledgement of objects of an HTTP service that delivers no WAI envelopes (§14). Out of scope: a transport; money, a payment rail, an invoice and any contact with a party outside the records; how a party chooses its tariff; how a hop measures its energy, which @@ -64,7 +65,8 @@ dispatchable. - **Object**: one WAI envelope as it travels, sealed bytes for a sealed object ([confidentiality](confidentiality.md) §8.1). Its **content hash** is jwp-receipts §2.1's, `BLAKE3("moq-jwp:object\x01" ‖ envelope)`, and its **length** is the envelope's length in - bytes. + bytes. An object of an HTTP service that delivers no WAI envelopes is named by plain SHA-256 + instead (§14.1). - **Time**: microseconds since 1970-01-01T00:00:00Z, the unit of a room's `t0` (live-rooms §3.1). A **window** and a **period** are spans of time, each from its `start` (included) to its `end` (excluded). @@ -774,3 +776,53 @@ itself with each of its checks dropped. figures a payment would rest on. Its amount, what the party is owed, is an integer of millionths of a minor unit, rounded down once per line; its attribution lines are what it owes the owners of the works that conditioned its outputs, each rounded down once per bucket. + +## 14. Objects of an HTTP service that is not WAI's + +A sink that receives objects from an HTTP service that delivers no WAI envelopes, a segment of a +stream another service packages, can still sign acknowledgements of them, under §3 with three +changes: how an object is named (§14.1), how its upstream is named (§14.2), and what no longer +applies (§14.3). [stream-audit](stream-audit.md) is the extension that acknowledges such objects. + +### 14.1 The object and its content hash + +The **object** is the content of one response (RFC 9110 §6.4) with every content coding removed +(RFC 9110 §8.4): the bytes a player decodes, which are what a page's script receives, since a +user agent removes content codings before it hands a body over. A 206 response's content is the +range it carries (RFC 9110 §14), and that range is the object, as a byte-range segment +(`EXT-X-BYTERANGE` of `draft-pantos-hls-rfc8216bis-22`) is a segment of its own. Its **content +hash** is the plain SHA-256 (FIPS 180-4) of those bytes, in place of jwp-receipts §2.1's, and its +length is their length. Its item is the whole object: `start` 0 and `end` its length. + +For a response with no content coding, the content hash of a 200 response's object is the value +a `Repr-Digest` field of algorithm `sha-256` (RFC 9530 §3) states for it, and of any response's +object the value `Content-Digest` (RFC 9530 §2) states. A sink **MAY** compare them; it +acknowledges the bytes it received either way, since an acknowledgement states receipt, not +authenticity (§14.3). + +### 14.2 The upstream + +Such a service holds no key the sink knows. Its **upstream** is named by its origin instead: +SHA-256 over the 18 bytes `wai:settle-origin` (ASCII) and 0x01, followed by the ASCII +serialization (RFC 6454 §6.2) of the origin of the URL the response came from, after any +redirect. It is a name, not a key, and signs nothing: it is the `upstream` of acknowledgements +(mode 1) alone, never of a declaration (§4), whose upstream signs. A verifier given the origin +recomputes the name, and so tells such an acknowledgement apart: an `upstream` that is an +origin's name names its objects by plain SHA-256, and any other names WAI envelopes by +jwp-receipts §2.1's hash. A WAI envelope fetched over HTTP keeps jwp-receipts' hash whatever +serves it. + +### 14.3 What applies and what does not + +- **Unchanged:** the layout, the entries, the classes, the item lists, the tree root (§2), which + is over 32-byte content hashes whatever made them, the signature and the chains (§3). Classes + follow §3.3: a live presentation's media in class 1, an on-demand presentation's in class 2 with + zero `grant` and `proof` (a retrieval no grant was presented for, §6.2), and every other object + in class 4. +- **No receipt is checked.** §1 has a downstream party check an object's delivery receipts before + it acknowledges it; such an object has none, so its acknowledgement states what the sink + received and nothing of where the bytes were first published. +- **No range proof.** SHA-256 has no chunk tree, so §5 does not apply: an audit of such an item is + answered with the object's bytes, which recompute its content hash. +- **No statement.** An upstream with no key signs no statement (§7): its acknowledgements are read + by whoever relies on them, the content's owner among them, directly, as stream-audit §11 does. diff --git a/wai/extensions/stream-audit.md b/wai/extensions/stream-audit.md new file mode 100644 index 000000000..17ebc6d5a --- /dev/null +++ b/wai/extensions/stream-audit.md @@ -0,0 +1,599 @@ +# WAI Extension: Stream Audit + +> Status: Draft. Registers the record `wai.delivery.audit`: evidence a content owner gathers from +> the players of a stream it already distributes over HTTP, with no change to the service that +> packages and delivers it. For each playback session the player makes a fresh key, the +> **session key**, and signs with it: [settlement](settlement.md) acknowledgements, `WDA1`, of +> every object it fetched, each named by plain SHA-256 under settlement §14, one chain per +> origin; a presentation claim, `WPC1` of [ad-presentation](ad-presentation.md), for each ad the +> stream marks; and a **session record**, `WAS1`, which binds them: the channel, the owner's +> collector, the spans of time it presented and whether each was in view and audible, its +> start time and stalls (recomputed from its [delivery-telemetry](delivery-telemetry.md) log), +> its sink energy per role with each figure's acquisition class, how the session ended, and the +> digests of every record it names. The player sends the records to the owner's **collector**, +> which verifies them, stores them, and states a **channel report** per period: time presented, +> in view and audible; ads presented and the quartile each reached; start time and stalls once +> enough sessions hold them; acknowledged bytes per origin and object class; and joules per role +> and acquisition class, never summed across classes. A party holding the stored records +> recomputes the report byte for byte. No new transport, and no price, tariff or amount. The +> reference implementation is the `stream_audit` module of `wai-rs` (feature `stream_audit`); +> the collector is `wai/wai-audit`, whose `web/` directory holds the player integration; the +> corpus is [`live-conformance/audit/`](../live-conformance/audit/), and its second +> implementation is `live-conformance/stream_audit_verify.py`. +> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. +> +> Texts this revision relies on: the WAI texts it cites; RFC 9110 §8.4 (content codings) and +> §14 (range requests); RFC 9530 §2 and §3 (`Content-Digest`, `Repr-Digest`); RFC 6454 §6.2 +> (the ASCII serialization of an origin); RFC 8032; FIPS 180-4; the WHATWG Fetch Standard's +> CORS-safelisted response-header names and `Access-Control-Expose-Headers`; and +> `draft-pantos-hls-rfc8216bis-22` §4.4.5.1 (`EXT-X-DATERANGE`) as +> [live-ads](live-ads.md) reads it, with its `EXTINF`, `EXT-X-PROGRAM-DATE-TIME` and +> `EXT-X-BYTERANGE` tags. + +## 1. Scope + +In scope: the channel and the session key (§3); what a player acknowledges and how (§4); the +session record (§5); the ads it presented (§6); its telemetry (§7); its energy and how the +session ended (§8); the bundle a player sends (§9); verification (§10); the channel report and +its recomputation (§11); and what a collector does (§12). + +Out of scope: a transport; how a player reaches the collector, beyond §12; the service that +delivers the stream, which this extension asks for nothing; the authenticity of what was +delivered, since nothing the delivering service signs is read here (§15); the markers of a DASH +`EventStream` and of an in-band event, which this revision does not read; and any price, tariff, +amount or invoice: a report states what was presented and delivered, never what it is worth. + +Relationship to the core spec: `wai.delivery.audit` is a **record** ([SPEC](../SPEC.md) §5): a +signed record a WAI tool emits and verifies. It names no payload format and is not +dispatchable. + +## 2. Terms + +- **Owner**: the party a stream's content belongs to, which runs the collector. Its **key** is + the collector's Ed25519 key (RFC 8032). +- **Channel**: a stream the owner names, live or on demand, under one name (§3.1). +- **Player**: the software that fetches and presents the stream and signs this extension's + records, with an integration of §13 or one of its own. +- **Session**: one playback of one channel by one player, from the player's first request to + its end (§8.2). +- **Object**: one HTTP response body the player fetched for the presentation, as settlement §14 + defines it: a playlist, a manifest, an initialization segment, a media segment, a timed-text + segment. +- **Origin**: the origin (RFC 6454) of the URL an object came from, after redirects. +- **Time**: microseconds since 1970-01-01T00:00:00Z, as settlement §2's. A span from `start` + (included) to `end` (excluded). +- *u64*, *u32*, *u16*, *u8*: little-endian unsigned integers. A **digest** is SHA-256 (FIPS + 180-4), 32 bytes. **Zero** is 32 zero bytes. Ed25519 is verified strictly + ([SPEC](../SPEC.md) §7 *Signatures*): a key that fails step 1 or 2 is `bad_key`, a signature + that fails a later step `bad_signature`. + +## 3. The channel and the session key + +### 3.1 The channel + +A channel's **name** is 1 to 255 bytes of printable ASCII (0x21 to 0x7E), which the owner +chooses and configures its players with. Its **id** is SHA-256 over `wai:audit-channel` (ASCII), +0x01 and the name. A name is the owner's label, never a URL: a URL can carry a token, and a +channel outlives the URLs its stream is served from. + +### 3.2 The session key and the session + +A player **MUST** make a fresh Ed25519 key for each session, from its platform's random +generator, and **MUST NOT** use it for another session or derive it from an identifier of the +viewer, the device or an account: ad-presentation §3's rule, for a channel in place of a room. + +The **session** is the first 16 bytes of SHA-256 over `wai:ad-presentation-session` (ASCII), +0x01, the channel id and the session key: ad-presentation §3's derivation with the channel id in +the room id's place, so that the claims of §6 name it exactly as a room's claims name theirs. + +## 4. Acknowledgements + +### 4.1 What a player acknowledges + +A player acknowledges every object of the presentation it receives, under settlement §14: each +named by the SHA-256 of its bytes as received, from the upstream named by its origin. It does not +acknowledge a key or a licence response: their bytes are secrets, and their digest would let a +party holding a candidate key confirm it. + +Each acknowledgement (settlement §3) has `mode` 1, `role` 2 (a sink), `upstream` the origin's +upstream (settlement §14.2), `downstream` the session key, which signs it, and zero `grant` and +`proof` in every entry. Its entries are by object class (settlement §3.3): + +- class 1 (`live`): the initialization and media segments of a live presentation (a playlist + with no `EXT-X-ENDLIST`); +- class 2 (`fetch`): those of an on-demand presentation, a retrieval no grant was presented for; +- class 4 (`ancillary`): every other object: playlists, manifests (each refresh is an object), + timed text. + +Every item is a whole object: `start` 0 and `end` its length. A byte-range segment is an object +of its own (settlement §14.1). + +### 4.2 Windows and chains + +A player cuts its session into **windows** of *W* microseconds from the session's `start`: the +*k*-th from `start` + *kW* to `start` + (*k* + 1)*W*, the last ending at the session's `end`. +*W* is the player's choice, 10 s by default and at least 1 s. For each origin and each window in +which it received an object from that origin, it signs one acknowledgement of the objects +received in that window, in the order received; an object's time is when its body was complete. +An origin's acknowledgements form one chain (settlement §3.7), each naming in `prev` the digest +of the one before it, the first naming zero. + +A window's item list names an object once. An object the window already lists for that origin, +received again with the same bytes (a retried request, a playlist refreshed unchanged) is a +**repeat**: the session record counts repeats, their objects and bytes (§5.1), and lists none. + +### 4.3 What an acknowledgement states + +That the holder of the session key received those bytes from that origin in that window. Not +that the bytes are what the owner published, nor that they were presented: presentation is the +session record's spans (§5.2) and the claims (§6). + +## 5. The session record + +### 5.1 Layout + +When a session ends, the player signs one record: + +| field | size | meaning | +|---|---|---| +| `magic` | 4 | `WAS1` (ASCII) | +| `version` | 1 | 1 in this revision | +| `channel` | 32 | the channel id (§3.1) | +| `owner` | 32 | the owner's key: the collector the session reports to | +| `player` | 32 | the session key, which signs the record | +| `session` | 16 | the session (§3.2) | +| `presentation` | 1 | 1 live, 2 on demand | +| `start`, `end` | 8 each | u64: the session's span | +| `ended` | 1 | how the session ended (§8.2) | +| `origins` | 1 + … | a u8 count *n*, at least 1, then each origin: a u16 length, 1 to 2048, and its ASCII serialization (RFC 6454 §6.2) | +| `acks` | 2 + 32 × *n* | a u16 count, at least 1, then the digests of the session's acknowledgements | +| `claims` | 2 + 32 × *n* | a u16 count, then the digests of its presentation claims (§6) | +| `spans` | 4 + 11 × *n* | a u32 count, then each span (§5.2) | +| `startup_ms` | 4 | u32: the start time, delivery-telemetry's `vst_ms` (§7.1); 4 294 967 295 when nothing was presented | +| `stalls` | 4 | u32: the stalls begun after the first presentation | +| `stall_ms` | 8 | u64: their total duration | +| `repeat_objects` | 4 | u32: the repeats (§4.2) | +| `repeat_bytes` | 8 | u64: their bytes | +| `log` | 32 | the digest of the session's observation log (§7.1) | +| `notes` | 32 | the digest of its notes document (§7.3), or zero for none | +| `energy` | 1 + … | a u8 count, then each energy entry (§5.3) | +| `attachments` | 2 + 32 × *n* | a u16 count, then the digests of the records it attaches (§8.3) | +| `sig` | 64 | §5.4 | + +Each list of digests is in strictly ascending order, and so are the origins, bytewise. + +### 5.2 Spans + +A span is a stretch of the session's time during which the player presented the stream at normal +speed; 11 bytes: + +| field | size | meaning | +|---|---|---| +| `start_ms` | 4 | u32: where it starts, in milliseconds after the session's `start` | +| `length_ms` | 4 | u32: its length, at least 1 | +| `visible_bp` | 2 | u16: the least fraction of the player's pixels in view it observed over the span, in basis points (0 to 10 000) | +| `flags` | 1 | bit 0 **audible**: audible throughout; bit 1 **ad**: within a marked ad (§6) | + +A player observes at least every 200 ms and begins a new span whenever playback stops or +resumes, its quantised visible fraction or audible state changes, or a marked ad begins or ends, +by ad-presentation §10's rules: a fraction of 0 while the page or application is hidden, out of +focus or locked; audible only when unmuted at a volume above 0. The spans are in order and do not +overlap. + +### 5.3 Energy entries + +An entry states the energy of one **role** of the session: + +| field | size | meaning | +|---|---|---| +| `role` | 1 | 1 **origin**, 2 **delivery** (the network between origin and player), 3 **sink decode**, 4 **display** | +| `joules` | 8 | u64: microjoules over the session; 0 is the unmetered marker | +| `class` | 1 + … | its acquisition class: energy-measurement §2.7's tag byte, then that tag's evidence | + +A role with a figure states it with its class; a role the player knows took part and did not +measure states the unmetered marker (tag 0 and 0 J, live-metering §2); a role it knows nothing +of has no entry (§8.1). + +### 5.4 Digest and signature + +The **digest** of a record is SHA-256 over its bytes before `sig`. Its **signature** is Ed25519 +by `player` over the 18 bytes `wai:audit-session` (ASCII) and 0x01, followed by the bytes the +digest covers. + +### 5.5 Reading and rules + +A reader reads exactly the bytes it is given as one record, in the order of §5.1, and refuses at +the first of these that holds: fewer than 4 bytes, `truncated`; a magic other than `WAS1`, +`malformed`; no fifth byte, `truncated`; a version other than 1, `unknown_version`; the bytes end +before a field the layout requires, `truncated`; a class whose tag 5 reference is longer than 255 +bytes, `malformed`; bytes after `sig`, `malformed`. It then refuses as `audit_session_invalid` a +record that breaks any of these: + +- `presentation` is 1 or 2, `start` is below `end`, and `ended` is 1 to 5; +- there is at least one origin, each 1 to 2048 bytes of printable ASCII beginning `http://` or + `https://`, with no `/`, `?`, `#` or `@` after those, in strictly ascending order; +- there is at least one acknowledgement; `acks`, `claims` and `attachments` are each in strictly + ascending order; +- each span's `length_ms` is at least 1, its `visible_bp` at most 10 000 and its `flags` at most + 3; each starts at or after the end of the one before; the last ends at or before + ⌊(`end` − `start`) / 1000⌋; +- `startup_ms` is 4 294 967 295 exactly when there is no span; `stall_ms` is 0 when `stalls` is; + `repeat_bytes` is 0 when `repeat_objects` is, and at least it otherwise; +- `log` is not zero; +- the energy entries are in strictly ascending order of `role`, each 1 to 4; + +and as `energy_invalid` one whose energy entry breaks live-metering §3.6's rules for a figure +and its class (the tag is 0 exactly when `joules` is; no tag 1; the hardware classes' evidence +and resolution floor). Then the signature (§5.4): `bad_key`, `bad_signature`. + +## 6. Ads presented + +### 6.1 Marked ads and their markers + +An ad is **marked** when the media playlist the player presents carries an `EXT-X-DATERANGE` +tag (`draft-pantos-hls-rfc8216bis-22` §4.4.5.1) over it with a `SCTE35-OUT` or `SCTE35-CMD` +attribute, or with a `CLASS` the player is configured to treat as an ad. Its **marker** is the +tag's line as the playlist carries it, from `#` to the last byte before the line terminator, and +its **marker digest** is SHA-256 over `wai:audit-marker` (ASCII), 0x01 and the marker. The marked +span runs from the tag's `START-DATE` for its `DURATION` (or `PLANNED-DURATION`), placed in the +playlist by its `EXT-X-PROGRAM-DATE-TIME` tags. + +### 6.2 The creative + +The ad's **creative** (ad-presentation §4) is the media segments of the presented playlist +whose span meets the marked span, in order, each by the SHA-256 of its bytes as received +(settlement §14.1) and its `EXTINF` duration in milliseconds, rounded half up and at least 1. Its +media time starts at the first such segment's start. A creative's objects are among the +objects the session acknowledged. + +### 6.3 The claim + +For each marked ad it presented, the player signs a claim of ad-presentation §5, with `room` the +channel id, `cue` the marker digest, `session` the session, `slot` 0, `creative_hash` the +creative digest, `player` the session key, and spans over the creative's media time by +ad-presentation §10's rules. A claim states what ad-presentation §13 says one states, and here no +decider's receipt stands beside it: its quartile and viewability are computed (§10.2 step 9) and +reported, not compared with anyone's count. + +## 7. Telemetry + +### 7.1 The observation log + +The player keeps a delivery-telemetry log of the session (delivery-telemetry §2 and its §7.1), +whose `sid` is the session as 32 lowercase hexadecimal digits: fresh per session and derived +from no identifier, as delivery-telemetry §8 requires. Its `object` members report `url` as +`null`, since a third party's URL can carry a token in its path, and `content_hash` as the +object's SHA-256. The **observation log** is live-metering §6.1's logs document holding exactly +this one log, as UTF-8 bytes. The record's `startup_ms`, `stalls` and `stall_ms` are the log's +`vst_ms`, `stalls` and `stall_ms` (delivery-telemetry §7). + +### 7.2 CMCD + +The log's Event-mode reports, CMCD version 2 (CTA-5004-B) as delivery-telemetry §3 maps them, +go to the owner's collector as `application/cmcd` batches (live-metering §6.3) while the session +runs. Request-mode reports **MAY** ride on the player's requests to an origin where the owner +configures it; they are off by default, since a query added to a third party's URL can break the +signature that URL carries. A batch is advisory, as every observation is (delivery-telemetry +§1); what the report reads is the log the record binds. + +### 7.3 CMSD and the notes document + +Where a response exposes `CMSD-Static` or `CMSD-Dynamic` (CTA-5006) to the player (in a page, +only when the origin lists them in `Access-Control-Expose-Headers`; they are not CORS-safelisted +names), the player records them. The **notes document** is a JSON array, as UTF-8 bytes, of one +object per response that exposed either, in the order received, with exactly the members +`content_hash` (the object's SHA-256, 64 lowercase hexadecimal digits), `cmsd_static` and +`cmsd_dynamic` (each the field's value as received, a string, or `null`). The values are the +origin's statements, recorded, not parsed or checked. The record names the document by its +digest, zero when no response exposed either field. + +## 8. Energy, the end of a session and attachments + +### 8.1 Energy + +The entries of §5.3 follow one rule: a figure with its class, the unmetered marker where a role +took part unmeasured, no entry where the player knows nothing. A page has no meter: it states +roles 3 and 4 as the unmetered marker and has no entry for roles 1 and 2. A native host or a +sink with an energy profile states its figure with the class and evidence its counter or model +gives (energy-measurement §2.7). Nothing sums figures of two classes: the report totals each +role and class apart (§11.2), as live-metering §4.5 totals a session's hops. + +### 8.2 How the session ended + +| `ended` | name | when | +|---|---|---| +| 1 | `ended` | the presentation reached its end | +| 2 | `user` | the viewer stopped it, or left the page or application | +| 3 | `hidden` | the player ended the video because nobody could see it for its grace period | +| 4 | `idle` | the player ended it after its idle bound passed with no interaction | +| 5 | `error` | playback failed | + +### 8.3 Attachments + +A player may attach other signed records of the session (a termination record of +`wai.live.termination`, a sink energy receipt of `wai.sink.energy`, each where the player keeps +one), each named in `attachments` by the SHA-256 over all its bytes. This revision binds an +attachment to the session and does not read it; a collector stores it with the session. + +## 9. The bundle + +A player sends a session to the collector as one **bundle**: a JSON object (RFC 8259) with +exactly these members: + +| member | value | +|---|---| +| `v` | 1 | +| `session` | the record (§5), in lowercase hexadecimal | +| `acks` | an array, one per digest of `acks`: `{ "record": hex, "items": [hex, …] }`, the acknowledgement and each of its entries' item lists in entry order | +| `claims` | an array, one per digest of `claims`: `{ "claim": hex, "marker": string, "creative": [{ "content_hash": hex, "duration_ms": integer }, …] }` | +| `log` | the observation log (§7.1), a string whose UTF-8 bytes are the document | +| `notes` | the notes document (§7.3) as such a string, or `null` | +| `attachments` | an array: `{ "name": string, "record": hex }`, the record's registered name and its bytes | + +A bundle that is not such an object, holds a member not listed or lacks one, a value of another +type, hexadecimal that is not lowercase or not whole bytes, a creative object whose `duration_ms` +is not 1 to 4 294 967 295 or whose `content_hash` is not 32 bytes, or a creative with no object, +is refused `audit_bundle_invalid`. + +## 10. Verification + +### 10.1 Inputs + +A verifier is given a bundle, the owner's key, and the names of the channels the owner serves. + +### 10.2 Steps + +It runs these steps and stops at the first refusal, which it reports with the reason and where: + +1. **The bundle** (§9), at `bundle`: `audit_bundle_invalid`. +2. **The record** (§5.5), at `session`: `truncated`, `malformed`, `unknown_version`, + `audit_session_invalid`, `energy_invalid`, `bad_key`, `bad_signature`. +3. **Its binding**, at `session`: `owner` is the owner's key, `audit_wrong_owner`; `channel` is + the id of a channel the owner serves, `audit_unknown_channel`; `session` is the session of + `channel` and `player` (§3.2), `audit_session_unbound`. +4. **Each acknowledgement**, in the record's order, at `ack` and its index: + 1. one of the bundle's acknowledgements has the digest (settlement §8.2: SHA-256 over all but + the last 64 bytes): otherwise `record_missing`; + 2. it reads and verifies (settlement §3.6): `truncated`, `malformed`, `unknown_version`, + `ack_invalid`, `self_acknowledged`, `bad_key`, `bad_signature`; + 3. its `mode` is 1 and its `downstream` is `player`: otherwise `audit_ack_unbound`; + 4. its `upstream` is the upstream of one of the record's origins (settlement §14.2): + otherwise `audit_origin_unknown`; + 5. its window is within the session, the session's `start` ≤ its `start` and its `end` ≤ the + session's `end`: otherwise `ack_outside_period`; + 6. its `role` is 2, every entry's `grant` and `proof` are zero, and every entry's class is 4 or + the class of the record's `presentation` (§4.1): otherwise `audit_ack_invalid`; + 7. each entry, at `entry` and its index: its item list is given (`record_missing`), reads + (`items_invalid`) and recomputes (`items_mismatch`) as settlement §3.6 says, and every + item's `start` is 0 (`audit_ack_invalid`); an item list beyond its entries is + `audit_bundle_invalid`. + + A bundle acknowledgement whose digest the record does not name, or that repeats one, is + `audit_bundle_invalid`, at `ack` and its index in the bundle. +5. **The chains**, at `ack` and an index: each origin's acknowledgements ordered by (`start`, + index), as settlement §8.2 step 4 orders a chain: the first with a `prev` other than zero, + `ack_chain_broken`; a later one whose window overlaps the one before it, `ack_overlap`, or + whose `prev` is not that one's digest, `ack_chain_broken`. Of every such break, the one at the + acknowledgement first in the record's order is reported. +6. **The log**, at `log`: the SHA-256 of the bundle's `log` bytes is `log`, otherwise + `audit_document_mismatch`; it reads as a logs document of exactly one log (live-metering + §6.1), otherwise `qoe_invalid`; and its `sid` is the session's and its `vst_ms`, `stalls` and + `stall_ms` are the record's `startup_ms` (`null` for 4 294 967 295), `stalls` and `stall_ms`, + otherwise `audit_qoe_mismatch`. +7. **The notes**, at `notes`: with `notes` zero, the bundle's `notes` is `null`; otherwise its + SHA-256 is `notes`: otherwise `audit_document_mismatch`. A notes document that does not read + as §7.3 says, or that names a `content_hash` no item of the session's acknowledgements has, + is `audit_notes_invalid`. +8. **The attachments**, at `attachment` and its index: each digest the record names is an + attachment's (`record_missing`), and each attachment's digest is one the record names + (`audit_document_mismatch`). +9. **Each claim**, in the record's order, at `claim` and its index: a bundle claim with the digest + (SHA-256 over all but its last 64 bytes) is given, otherwise `record_missing`, and a bundle + claim whose digest the record does not name is `audit_bundle_invalid`. Then each claim gets a + verdict, **presented** or the reason of the first of these it fails: + 1. it reads (ad-presentation §5.4): `claim_malformed`; + 2. its signature verifies under its `player` (ad-presentation §5.3): `bad_key`, + `bad_signature`; + 3. its `room` is the channel id, its `session` the session and its `player` the session key: + `audit_claim_unbound`; + 4. its `cue` is the digest of the marker beside it (§6.1): `audit_marker_mismatch`; + 5. no claim before it that passed steps 1 to 4 has its `cue` and `slot`: `claim_duplicate`; + 6. its `creative_hash` is the digest of the creative beside it, every span ends within the + creative's duration, and its `objects` are exactly the content hashes of the creative's + objects its spans touch, in order (ad-presentation §9.2 step 6): `claim_asset_mismatch`; + 7. every one of its `objects` is the content hash of an item of one of the session's + acknowledgements: `audit_ad_not_acknowledged`. + + A claim's verdict does not refuse the session. A presented claim's facts are the quartile it + reaches (ad-presentation §6.3, with the creative's duration), and its run and viewable time + under the video rule (ad-presentation §6.2, §6.1 `viewability` 1), and whether that run is at + least 2 000 ms. + +### 10.3 Outcome + +- **refuse**: the reason and where. +- **accept**: the record's digest; the channel; `presentation`, `start`, `end` and `ended`; the + time presented (the sum of the spans' `length_ms`), of it in view (spans whose `visible_bp` is at + least 5 000), audible, and within ads; for each origin and object class, the acknowledged + objects and bytes, and the repeats; the start time, stalls and stall time; each energy entry + with its role, class label and figure, or the unmetered marker; each claim's verdict and, for a + presented claim, its facts; the notes' count of responses; and the attachments' count. + +## 11. The channel report + +### 11.1 Its sessions + +A report covers one channel of one owner over a **period** from `from` to `to`. Its sessions are +the accepted bundles of that channel and owner whose record's `start` lies in the period. Two +records with one `session` are refused `audit_session_duplicate`, at `report`: a session reports +once. + +### 11.2 The document + +The report is a JSON object with exactly these members: + +| member | value | +|---|---| +| `v` | 1 | +| `channel`, `owner` | the channel id and the owner's key, hexadecimal | +| `from`, `to` | the period | +| `sessions` | the records' digests, hexadecimal, in ascending order | +| `presented` | `{ "presented_ms", "viewable_ms", "audible_ms", "ad_ms" }`: §10.3's sums over the sessions | +| `ended` | `{ "ended", "user", "hidden", "idle", "error" }`: the sessions by `ended` | +| `ads` | `{ "claims", "presented", "quartiles", "viewable", "refused" }`: the claims, those presented, an array of five counts (the presented claims reaching quartiles 1 to 5), those whose run under the video rule is at least 2 000 ms, and an object of each refusing verdict's count by its code | +| `rebuffering` | `{ "sessions", "suppressed", "presented", "startup_ms", "stalls", "stall_ms" }`: the sessions; `true` when they are fewer than 50, and then every other member `null` (delivery-telemetry §8); otherwise `false`, the sessions with a span, the sum of their `startup_ms`, and the sums of `stalls` and `stall_ms` over all | +| `delivery` | an array of `{ "origin", "class", "objects", "bytes" }`, one per origin and object class with an acknowledged object (class by settlement §3.3's name), in ascending order of origin then class | +| `repeats` | `{ "objects", "bytes" }`: the sums of the records' repeats | +| `energy` | an array of `{ "role", "class", "figures", "joules_micro" }`, one per role and acquisition class among the sessions' metered entries (role `origin`, `delivery`, `sink_decode` or `display`; class live-metering §4.5's label), in ascending order of role number then tag | +| `unmetered` | an object with a member per role name: the sessions whose entry for the role is the unmetered marker | +| `absent` | an object with a member per role name: the sessions with no entry for the role | + +Joules are summed within one role and one class and never across: a role's figures of two classes +are two members of `energy`, and a session whose entry is the unmetered marker counts in +`unmetered`, never as zero joules. + +### 11.3 Its bytes and signature + +The report's **bytes** are its JSON with every object's members in bytewise order of their +names, no white space, integers in decimal, and strings with no escape but `\"` and `\\`: the +form RFC 8785 gives such a document. The collector signs them: Ed25519 by the owner's key over +the 17 bytes `wai:audit-report` (ASCII) and 0x01, followed by the report's bytes. A served report +is `{"report": , "sig": }`. + +### 11.4 Recomputation + +A party given the bundles, the channel's name, the owner's key and the served report recomputes +the report over the report's own period, in this order: each bundle verifies as §10 verifies it, +otherwise its refusal, at `bundle`, its index and its place; a served report that is not an object +of `report` and `sig`, or whose `from`, `to` or `sessions` do not read, is +`audit_report_mismatch`; two of the sessions it reads with one `session` are +`audit_session_duplicate`; a digest the report names that no given bundle has is `record_missing`, +at `report`, `session` and its index; a report whose bytes are not the recomputed report's bytes +is `audit_report_mismatch`; and one whose signature does not verify under the owner's key is +`bad_signature`, each at `report` where no other place is named. + +## 12. The collector + +A collector: + +- **MUST** verify a bundle (§10) before it stores it, and refuses with its reason one that does + not verify; +- stores an accepted bundle under its record's digest, and accepts the same bundle again with no + change; a record naming the `session` of a stored record that differs from it is refused + `audit_session_duplicate`; +- serves a channel's report for a period (§11), signed with its key, and its key; +- **MAY** accept Event-mode CMCD batches (§7.2), which it stores apart and never reads into a + report; +- names nothing a player has not sent: no cookie, no account and no identifier of a viewer. + +The reference collector, `wai-audit serve`, keeps one directory per channel, a file per session, +and answers on loopback; its README states its routes and limits. + +## 13. The player integration + +The reference integration is `wai/wai-audit/web/audit.js`, a module with no dependency, and its +adapters: one that wraps the page's `fetch` and `XMLHttpRequest` for any player that fetches over +them, and one for a player whose loader interface a page can wrap. The module hashes each object +with WebCrypto's SHA-256, writes and signs the acknowledgements, the claims and the record with +WebCrypto's Ed25519, computes jwp-receipts §2.2's tree root with a BLAKE3 of its own for inputs of +one chunk, observes the media element every 200 ms as ad-presentation §10 says, writes the +observation log and its Event-mode CMCD, and sends the bundle when the session ends. Its tests +drive it with the corpus's inputs and require the corpus's records, byte for byte. + +## 14. Reasons + +The codes this extension adds, each with where it is raised: + +| reason | raised at | +|---|---| +| `audit_bundle_invalid` | §9, §10.2 steps 1, 4 and 9 | +| `audit_session_invalid` | §5.5, §10.2 step 2 | +| `audit_wrong_owner` | §10.2 step 3 | +| `audit_unknown_channel` | §10.2 step 3 | +| `audit_session_unbound` | §10.2 step 3 | +| `audit_ack_unbound` | §10.2 step 4 | +| `audit_origin_unknown` | §10.2 step 4 | +| `audit_ack_invalid` | §10.2 step 4 | +| `audit_document_mismatch` | §10.2 steps 6 to 8 | +| `audit_qoe_mismatch` | §10.2 step 6 | +| `audit_notes_invalid` | §10.2 step 7 | +| `audit_claim_unbound` | §10.2 step 9 | +| `audit_marker_mismatch` | §10.2 step 9 | +| `audit_ad_not_acknowledged` | §10.2 step 9 | +| `audit_session_duplicate` | §11.1, §12 | +| `audit_report_mismatch` | §11.4 | + +A verifier of this extension also raises `truncated`, `malformed` and `unknown_version` for a +record that does not read; `bad_key` and `bad_signature`; `record_missing` (live-metering §9); +`energy_invalid` and `qoe_invalid` (live-metering §9); settlement §11's `ack_invalid`, +`self_acknowledged`, `items_invalid`, `items_mismatch`, `ack_outside_period`, `ack_overlap` and +`ack_chain_broken`; and ad-presentation §11's `claim_malformed`, `claim_duplicate` and +`claim_asset_mismatch`, as claim verdicts. + +## 15. Conformance + +[`live-conformance/audit/`](../live-conformance/audit/) holds the corpus, written from fixed +seeds by `wai_stream_audit_vectors gen` as `vectors.json`. It holds the owner's key, the channel +names, the objects' bytes, and: + +- **sessions**, each a bundle with the outcome §10.3 gives it. **Accepted**: a live session from + two origins with a marked ad presented in view throughout and one presented half out of view, + repeats, CMSD notes and a sink-decode figure of the `Estimator` class beside the unmetered + display; an on-demand session ended `hidden` with an `OnChipCounter` figure and an attachment; + a session that presented nothing and ended `error`; and a session whose claims get every claim + verdict of §14 and of ad-presentation's that §10.2 step 9 names. **Refused**: each reason of §14 + that a session raises, and each code it borrows, each vector a control of one defect. +- **reports**, each a channel, a period, the sessions it reads and the outcome §11.4 gives it: + a report over the accepted sessions, whose rebuffering is suppressed; a report over a channel + of 52 sessions, whose rebuffering is stated; and refused, a report whose bytes do not + recompute, one whose signature fails, one naming a session not given, and one over two records + of one session. + +A conforming verifier gives every session and every report the outcome, the reason, the place and +every figure the corpus states. `wai_stream_audit_vectors verify` and the test +`tests/stream_audit.rs` check that the file regenerates byte for byte, that `wai-rs` gives every +vector its outcome, that every reason of §14 and every borrowed code refuses a vector, and that §14 +lists exactly the reference's codes. `live-conformance/stream_audit_verify.py` is the second +implementation, written from this text: it shares no code with `wai-rs`; it reads acknowledgements +and item lists with the settlement corpus's second implementation (`settlement_verify.py`), claims +with the ad-presentation corpus's (`ad_presentation_verify.py`), and logs and energy classes with +the live-metering corpus's (`live_metering_verify.py`), verifies every signature with WAI's strict +rule (`claim-conformance/_ed25519.py`), recomputes every report itself, gives every vector the same +outcome, requires every mutated copy of the corpus to fail, and requires the corpus to fail a copy +of itself with each of its checks dropped. + +## 16. Security and privacy considerations + +- **What the evidence shows, and to whom.** The relying party is the owner. A session's records + show what the holder of a fresh key received, from which origins, what it presented and how + that went; they show nothing of the delivering service's own logs, and they need nothing from + it. A report's figures are recomputable from the stored records, so the owner can show a party + it deals with (an ad buyer, a rights holder) a figure that party recomputes. +- **What the evidence does not show.** That the bytes were what the owner published: nothing the + delivering service signs is read, so a stream altered on the way is acknowledged as received. + An owner that also signs what it publishes (jwp-receipts) can compare its own content hashes with + the acknowledged ones. That a person watched (ad-presentation §13). That the player is honest: a + modified player signs what it is told to, and a party can make keys and sign sessions it never + played. The records make each session attributable to one key and each figure recomputable; they + do not make fabrication impossible, and an owner weighs sessions from keys it cannot place as + ad-presentation §13 weighs claims without attestation. +- **Collusion between the session key's holder and an origin** can produce acknowledgements of + bytes that never moved, as settlement §13 says of any downstream party; repeats and the log's + timing make a replayed session's figures stand out, and nothing here detects one. +- **What a session reveals.** To the collector: the session's objects by digest, their origins, + its timing, what it presented and its energy entries. The session key is fresh and the session + derived from it (§3.2), so sessions do not link to each other or to a viewer, device or account; + `sid` is the session (§7.1); the log reports no URL; the record names the channel by the owner's + label, never by a URL. A digest of a public segment names a position in the stream, which is + what an acknowledgement is for; a key response is never acknowledged (§4.1). The collector sends + records to no one; a report states sums over sessions and suppresses rebuffering below 50 + sessions (delivery-telemetry §8). This extension sets no gate of its own: sending the records is + part of playing the owner's stream, as acknowledging is part of a delivery (settlement §13). + Where a regime asks the operator to state the lawful basis on which it processes these records, + the operator states it as a `lawful-basis` declaration (jwp-receipts §2.4.6), which this + extension does not restate or check. +- **Replay.** A record names its owner and channel, and its session derives from its key: a record + sent to another owner's collector is `audit_wrong_owner`, and a record for one session counts + once in a report (§11.1). +- **Size.** A collector bounds a bundle's bytes and a client's rate; the reference's limits are in + its README. diff --git a/wai/live-conformance/audit/README.md b/wai/live-conformance/audit/README.md new file mode 100644 index 000000000..9ebf52b13 --- /dev/null +++ b/wai/live-conformance/audit/README.md @@ -0,0 +1,73 @@ +# live-conformance/audit — stream audit + +The corpus of [`extensions/stream-audit.md`](../../extensions/stream-audit.md) §15: an owner's key, +the channels it serves, the objects its players fetched, and two kinds of vector: sessions, each a +bundle (§9) with the outcome a collector's verification gives it (§10), and channel reports (§11), +each with the outcome its recomputation gives it. + +It sits beside [`settlement/`](../settlement/) and [`presentation/`](../presentation/) because a +session's acknowledgements are settlement's `WDA1` under settlement §14 and its claims are +ad-presentation's `WPC1`, read by those corpora's second implementations, `settlement_verify.py` +and `ad_presentation_verify.py`; its log is read by `live_metering_verify.py`. + +**Audit equivalence.** For each session, a conforming verifier given the bundle's bytes (`bundle` +serialized as JSON, or `bundle_text` exactly), the `owner` key and the `channels` it serves reaches +the outcome the vector states: + +- `refuse`: the `reason` and where, `at`: `["bundle"]`, `["session"]`, `["ack", i]` or `["ack", i, + "entry", j]` for the record's `i`-th acknowledgement, `["log"]`, `["notes"]`, `["attachment", + i]`, or `["claim", i]`; +- `accept`: the record's `digest`, its `channel`, `presentation`, `start`, `end` and `ended`; the + time `presented_ms`, `viewable_ms`, `audible_ms` and `ad_ms`; the acknowledged `delivery` by + origin and class and the `repeats`; `startup_ms`, `stalls` and `stall_ms`; each `energy` entry + with its role, class label and figure; each claim's verdict, and for a presented claim its + `quartile`, `run_ms`, `viewable_ms` and whether it is `viewable`; the `notes` count; and the + `attachments` count. + +For each report, a conforming verifier given the bundles of the sessions the vector names, the +channel's name, the owner key and the served report (`served`: the report and its signature) +reaches the outcome it states: `accept` with the digest of the report's bytes (§11.3), or `refuse` +with its `reason` and place. A verifier that reaches another outcome, reason, place or figure does +not conform. + +**Provenance.** Generated here: `wai_stream_audit_vectors gen` writes `vectors.json` from figures +written in code, from fixed seeds: each key is SHA-256 of the corpus's fixed label +(`wai:stream-audit/corpus/key`, then a zero byte) and an index byte (0 the owner, 1 a stranger, 10 +to 13 the four sessions' players, 100 to 151 those of channel `fifty`); each object's bytes are a +SHA-256 stream of a second label (`wai:stream-audit/corpus/object`, a zero byte, the object's name +and a little-endian u32 counter), truncated to its length; and every time is a constant after +2026-10-25T00:00:00Z. Ed25519 signing is deterministic (RFC 8032), so the file is the same bytes on +every machine. The objects are opaque test bytes standing for playlists and segments, which +nothing parses or decodes. The durations, stalls and joule figures are synthetic constants chosen +to exercise the rules, and were measured by nothing: the corpus states what the rules compute and +claims nothing about any stream or device. The origins are `.example` names (RFC 2606). The +generator asserts that each vector reaches the outcome it was written to have before it writes it. + +## Layout + +One file, `vectors.json`: + +- `owner`: the collector's key; `channels`: the channel names it serves (`other-channel`, which a + control names, is not among them). +- `objects`: each object's bytes, hexadecimal, by name (for the reference producer's tests; a + verifier never reads them). +- `sessions`: each with its `name`, its `bundle` (or `bundle_text`), and its `outcome`; accepted + sessions also carry `cmcd`, the Event-mode CMCD batch (live-metering §6.3) of their log, and + `live_two_origins` carries `script`, the inputs its player had (its key's index, its window, + each object received with its time and origin, and its log's control events), from which the + reference producer's tests rebuild every record of its bundle. + - **Accepted**: `live_two_origins` (two origins, two marked ads, one in view and one half out of + it, a repeat, CMSD notes, a stall, an `Estimator` figure beside an unmetered display); + `on_demand_hidden` (ended `hidden`, an `OnChipCounter` figure, an attachment); + `nothing_presented` (ended `error`); `claim_verdicts` (one claim of each verdict); + `nothing_presented_again` (the same session as `nothing_presented`, a record of its own); and + `fifty_00` to `fifty_51`, the sessions of channel `fifty`. + - **Refused**: one control per defect, named for it. +- `reports`: each with its `name`, its `channel`, the `sessions` whose bundles it is given, the + `served` report, and its `outcome`: `news_east` (its rebuffering suppressed, fewer than 50 + sessions), `film_library`, `fifty` (its rebuffering stated), and the refused controls. + +Rebuilding: `cd wai/wai-rs && cargo run --no-default-features --features stream_audit --bin +wai_stream_audit_vectors gen ../live-conformance/audit`; `… verify ../live-conformance/audit` +checks it; `python3 -I ../live-conformance/stream_audit_verify.py ../live-conformance/audit` is the +second implementation. diff --git a/wai/live-conformance/audit/vectors.json b/wai/live-conformance/audit/vectors.json new file mode 100644 index 000000000..7c1a52ec3 --- /dev/null +++ b/wai/live-conformance/audit/vectors.json @@ -0,0 +1,5949 @@ +{ + "corpus": "wai.delivery.audit", + "extension": "extensions/stream-audit.md", + "owner": "775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2", + "channels": [ + "film-library", + "fifty", + "news-east" + ], + "objects": { + "p0": "7321a47c86970791d2f57e3384138abbb6122f936e7ebe20c34d09c6ea888f684a463c6938658b2483f82945c0dee5d39eb730d6de5e2b336ac967a3706653b6d71f730f276ca71ad303229285c7e8e958ad56b8b9e9d853bd798f4fa30f24c92dc5fd6baf4d7134183d6bf252e00da064ae49f96a8f1146b0df65bbf98c4043da66381efe8c44c96c3f51361139b26ac1bd44449a1314c6451139486b0bfda541572425e9cadbc5ba327389bd24b9a3a4a294a9c7c95077b77f16fe61edae1d3c91a404c258503dd2f4af6a43dabb58ecca2bf7f59409551d6c4a11cada27e05c6ba27ecf556b88338063c566151a4896c45f9fa3c30ccfeace9349bc6afdb43208b846e52cfc0d2a8a258e78593ce5e2647bec715e0e6652096a8c1c6e5c921a2da367af09f6921c9c1e5c", + "p1": "e52a337a1c5651f89dff8567106d1bbf777121f0aa36b140b92ece8cc1296469042831dad7bb586a0d597a2c531c500aa7f75828d5fd88cf572afe4b324cd7c40c1c60f1ce291f40171d4bbaef5a1f4844386a26f1c82ba617add3e0988778d34e458e5565840b5036a0e5b62ce4a18a5ac69ed24358b2c0ecd39153e54298ec92629e9c7a3f85c463c68d94d79aa3e965b34f686b108d25cabc13fb88a33f97b194d10d09f74bbbe6601df940862e1927ac4435d5d241d5dd9c6d91a8980bbe71e02ad5f2c20caff3ac8d612326eb08cb7ae0f4d5eb5379f8c7c9b4ba0ff594182760085b45abbb9ce18fb971f23fffc2a6b272e0b3a1f72135f9624c58757c7452911becf3a0f456bb3bbe390f1e00cb48fbfd97db84e8dc585bf5dfe28e2d9106b98d698978568734e8299175dcebaefd18b69906", + "p2": "a7cf6ae249aa6540808c8ee355dd3b3f1bbcb73b4d5d80a15ddf818917bb78c45ed09bf286e5b36dd0f06156425d0d89be42cfa2f08b1cf56d4d12239afb4a1f16396f3a1130cf8cdc79d74c0b848ad3d37233f9c4cf54332a686eba593e3f5ed003bfb066491016f22820a10697b9750c1a5c824e1cf55dc44c86dcc3fc2524ffdccb07664845f12064eac7fdad673fa3bb1400e039464c3e5e26bb1ec9083b0c28a520206a70ee779815a8b47a9a6879330fff19cf02032e87c53057d4074ed086e85738fd848688bc47da3bf5c07a6259050150e28c0e1f3775fec948b4fce8d1c06a5872e300af84f0837a01752fab98aebdc8e4dc3d50818d0b48ef3503e020ca0cb98b62d45c539dc2ad6278929645900709a5763919250926e5352210edec542fcd1468a8aecb58e10ad61c5057a700b1e5205badccd7aaaaf205bf06", + "init": "29971fdcf3b6101e405174326e818f8f31372fe947396a0089c67833df8d4e9bc4d532c9f19dec8f360f56c84f8e1ba86f15f69d41b1d52b5d57ece3ff02e2a922ace1a1eddec3b176d73b6bbe35b10f61c7712f2a10e0355646b108469514e984817c5f7124c2fcdc9d501363ee352964dd26f543bfb0e3e9a3ed44d23f37086720fc846bce3610ddbd409900c4231c9fa141ea678dd1b97e39ec247be4dc88a7babd3dfd64daf4014be6944815e19a85a8128cbc78283426c3d9a05e39cf7098d62d1f8dcedd4b", + "s0": "a5badba6e6f6b20568eb50bcca43d213068b9a339d2b3ee771fdeb9573e8c056bc378c8462567eb6745ecdd752f1333e08860fd6fc5ff495376d075264c965ee18536d5424b0a24b81e3a2eddcdad0cdb1b009f22e97e5d014ae638b28f1796e5ccfb9996ab00d37d48ff6e5f99da16d16a4fdb04d4419b7eb485e56175cc9086b8a2c4396145c006da5dfc3ff4526e66d1f16c9b1d66642320c493ce533a6de274e1c75c2d3e14ec7d47d11eea51f20ecff67314e242d176865ebffbe7989c589a7890b7060091908457ae4d9453104038b4c93c64da2b01c0b1d14e1a480b9314d283bc42806f6919a5ccf1e78cf3d07198a884e6af9affe0500da9b666a76e368b5accec63b1983734ab454fcde3556649771287161872a0e5f726e7f34d59d61fdd9f694340a32ff7b644306b43b4aacad672ed4c6c505eb0c688dc41ed27f502c4ea28ecd3052b66b78f56005bec564056043895c4fe1792accf5e30485012585a96f0fad3fbcbe42ea628775ef0de03787cbd23d21cba5cbb0b71c3964b789b8af3fa0311d74ce86ea5a9aae5c70bd37fb7b4de908eae6d6e64e356bea8abdafe5de677d6f690e11fe2e7bde94e88cae7a2beec64fc9ac568f7aa4563a7705614db480ed148e12bbef716fea4696b39b6697a4576e213fb3751b1fcb5b2d58508efaccc4edfaf0f91998114438d9698dba858f44086f5b6988389be10af532ad41c23b4891ee3707b2e39a98b19970bb6339d986a8585006d26f7878e0f05b1a7f45cb04585b30c1bb6b06ef7d4b6875685831ca47290a8e02207e7bd7fb6ac1759b3a49709fc72e813c4cb13fd9fd6f1128c710b6b000a3b810b81875ab1f9f1f2ba56fb389e5fb932e3e94bb8f8cea3aeb003399c7b2cc4f56e892b22ed5370aba1ae013d9b9f1b9f199396ada23af828f300d30d2fe3f10c7255e71d1ec1bb038635606ef4ea2f5fbba92e28ff55f13a3fbcd6d595ea797e2ebf6aab203b50dd5e3dfb9008c6b3e02bae9094ff803f69ad07c1355acd93ded700db308cbe67bfe182e4c2ca33cf1aab6b59ffb9e8941d86d75592bd7939ece8c26fcf49e495ca48d78de9ffa14db5de1474a1d0d8a54ce3469c3ff322f92c33a9bf782df2a55b44f78b46e03c063334ce2c877a76539b1f4f06dc1e150bbbc6aad6055ca51158d9d06e7efea1292103c9788236afec2a70b68b7a6a228958c8188d059b3e4a6112ee2d1dfccbd37f64d9736c51ee3a16ebafef45176d974cb8c693505ba7285", + "s1": "3294f50b3d1705dfcceb778ec66bbda2198429118b56de3e0d30b6db981cd77e9b306f236728818d66079403fe61f98e0594b42d9092fd0c26cad36fa24df05c75ba5db56c558242acd966f5be9a5bd962e532003105de3b75875a8efd4c88c73c901e101d6c6a8e3b8d3a87e5a82b3a5df101d2bdcaa4c641c9b884f65e0df318ebbc9b37d74dd9df29fc54c257fd2d3590bfbc6751b6d73a256b6dbfbd5301bde2f0ae40247c7f4c268101a301bacda9bc7a876bb455a585ffa01c6bda7871e9181905ade655963ddec05cdea8e03dfbd40145b95faf0af7b953d220d7cb1d3e194a23c638fcd1c9954ca3cecf052594aa85aefc9feede368a0dbb92ba60f380672a02aa0fdf4bcb26fd49c7bb41e41c441f81f5d81cb0bf8cc78faca34da66fe19efd796fb703e72a33c2337b7a7e4147b85909168bb077daa651f1b8e7eac800befd04b597ab8463f02a9a18eb03f2113fa0191fe9e8cbfcf8108ed12789b4dcb8ba3a82f50d8211178208b28b1300f4a3367b9d0cb061160b3aee91ebdf4425232463b97d12e7d9f9693198eaa48d2ff1ff061c0903e35d3824e71c6d4b556481af27af05c8f3b5283bc54875bb86c07fa74c86fecc122eba3315bedc93762a8308035e6218bbc930689dfcc7860b3150c0e01922bfd731ff5cbba53017bc621019071095f5077ea4ff2fd086ab61f2ea38155ebb50e2f752b74e1c6f8cf418b07569da4e9b0ac5fcce5d8f92dbdb1efb0f9e70a34e23351b8e0a4f33c866e5f09784e9602bfac90b90af3c1bdf35de976556d17fe9dbc8ad663042aae3005a4516dfc927ea863f4aa7fa63b46a79d4f5f746be59be2609d06289b16615a36e7927814e76bafe61f438432dcf2577977dd2ca550200936594937aab4a80fcdf1dfafc8e0e7f82a1c89656551fd532c2ae03dd132e31132b9f84d4db56df3f216540ac94c7b5c4de668054fadd7f1ab6c082356b0425684bd2b48c419b1a81fd2419768b295cfe54c65b4723a9ec6bf03942b0db592300220267efb7618a90797dcb0376be8108dcc53512ac3cb729e566f5f48f25f97af8279440613a0076127db260bd4709a1f25bac737b2a95c092d9ec492864256e264bbba591f993b193b1ab2232a5aa202ae81d9d31354f6d161c822dd00e59260ca6f07375bc2837bafd4c1023bb974fbc4216de6e02d171a2cfc67f6fdf207ef337521d17b99ad7449e657039c8d2166d626becd5b31e5e74c9e9965c115f3968165817896ee1ff64311f", + "s2": "2909dead08b7fa015b8abd0b973e9aebdd7de3bd51bc6a1059bef8a1573917723086dd45816cb465b9dcd5d890a53dbfbe63be12797d3b9bbc19805e1b588f4a4244c53e1bb50586480046b0534670e773812f15941bd438dcc32b58ba8af21a07d9b578a497d4470b99e34af450267e6b7a1f541a0c45a0f36635a6205aed74a94fc9ffcc71f24c101e8cf6eb654335d7ad59160872426d61230f71ae09274d00c67d8cdd728898cbede5fe350f61059e947ecc1e2d04db59ca96ff360eabef5277942083ae327049a37b284398ef689c79530c959b318e824977605cc79718134c250b04d3477ad92cd30b7b2c194606d5a225d91894a61ce63f476bc6c3d2c9da3f21f28fa601d52f49cb83a76dcf1f51e93d26646fd740fa9f456975b1f73a38d859af2d2cc9dde54454e09609cee95d6f84a66335f3f7851af7a1ef53d6915def9e10351634664067b2cd79521cfea625272bafacef54014ef43af14a7d0816d731307fce69463c9e4ea6c0be78b14e48c0b1ef6b4ddfe86300308b2233487e16596bf11f8fdab15dc2a01da8f5db950d9523ff9c7e501d09775697bb725759a4a268fed979752b80b501a5a4e3d9c14656d681dad2c63c2ff39d70522bbdde29781b42c16d6b6d1628f8d1684f63a5dc1ee3a599646bc00227c1dcb747eb258406ae674780467d9968b9d0e45661b3928714392ccd6090aa5d03a07ba6a52069cbde42cfddd2752da9da94f8d3b6a04081a79946648dad104f023fd7c6bad8fe24fd20ecbd023885f3d81563b7b53268297e442d035f3a4e9d5fe0a58298f1c06ee9da124641753cd8db814865d9dcfad68346791747c3ec2f54707abd005a0917fb17c6bfc24d842135b463a78bfc9d30ffcbb6343d60fe52d35a70c62827076b003a0e536a945a4583ed25a1a213a144946af726f226ff65c66c1ebc99de584e17df2306db722fff66382a180fdc6d8e795c3a1b2e0aecdfafa598f279105426e48ba4c13a746ca7ac402ccbf309a42855e005a1f59dfe26117920fbca9c569abf48873c04dfda7dfe8b8aabfb89706c54fb9bef6c84eb11313aa3560b8fc82880ec04bd67a7446020125c1e4e256861899e79954921505b64bbe3dfe6503598e1fbb5c5b28e40f318f875ccd514a841a0529d8d50098a37ec3ea9bd4c68c91859188aae5868ee8cccb886d8182cb9cb65b2c39cd28f09f3be6697c08ebed296071bf1d07002cad03717b4bb761e623c42c80e1869339fcd6419aaf4aa5423b7", + "s3": "efcf02ae74e926fe0baff7863e2f5b714327b7374689dc55f4eeba4859c0bdc587eb31ea3a382f243e22ac13fedb234e7446c9f7c4393090697c28b91fad14383c9d0ac9a269e3afd7ea477443cc383e615c88f81e2557228f04bf66f305b9ef7b7ebbb2ee5bc6678c92ad1def16fa2e7de7da4bbef507f154c50bf58ab0ef163e6b21ebb8b3617605bb61dda99d7779c12fd6860606a3c2716c0c00b8f32be721547c80707692a9d2061c0e6d69e9aecd67cf2e394bfab6585443041b5172099e69385bee8a50fc800fbe68a04e6ea0f93d21a683663e91a0e15e558da8d76c6e27d60364cdbaa30c8e8de6a57a5faa42e84bea71c9183224986861c4b33efa3997f57572302d1e556ed67f510c19d79a3f4513a4ff98f55567f50bfb548bb49d4f4c4b77fa22f8646195b6171ff0fb6a97d22042322355c548e5286617d6611fa6488dd80d1049623fcd28b3f6932a813540aa455a3af692151f49915d10037719ab30bcde967a69c01586e2998f50fcbd28584dca925e9054d131cbc45099a4cb7564968207d3ac1d253e1614cf03cfc21286d527a50b12ad7c02059f87a1490fcc89daa6a0e13eca96a5e55b546d47bee1937649cdb2e37d61e2253c769a1462c336a7e8e241297a96fe71ac359a95017f6a6d712ca890c41364240196e53424ec5ea27a521855c1b24070e94f45d16b2393435660a133177262cf3557e3fe77d834283a0dde5b3edd16e13ef1bed280b50d6e44bb0d796d3d6881927680b472e46e3f38ad54383a1ac5824d32eced2c5e6f2376cf49a258c5f3ad29e5cac2c21da4a41f16afe3e1512a32ffa8d956340a384f1b6d0670a70e264d1bfbb4282f30e9a0fb25c1f0cb756786b345739ac9d4a2b2e6061577d292ad88a9ecd67d9416efbeac0078abdf687892dd180e69030b633d428cd5043b358099000d6313ecb671079e959f98bc3dc478dfe2295a4441301551f2747aa3e7f106fadc9c0de0361f7ff151b98300bdca2917d59848806464770e55bd6d891c0491515123a1badf2d58388580fa512b1d3edf214825c6d56e827dd6efc28b4ca73aae4261cb044bd362cff9d99cf4956425aa7aa9e126ea4e70f7259823b0870e64a4fb12459767791d86e0672ba9bff3d035ca7fe5e6eb166d54ceac4a94e42916bab1942a13a403e2a0f44f308c4e47a1b558bd78c0c96da759c105ca4eb2f68a0df2c5f0493838d781f4909625490524688989468ac45bb410c73de6ec94656a4a7976c24690dc", + "s4": "88d60d608a7851a3c7a6ebe7f7ffc5b0509932d28fde0f68dec5412d99b7f753c8d4249d4cee3787c29b3b81b41a3e3cbcf1e4c37ca0654df8dd4a7a353ceca09f81a083868b31e0324632fa0328adce1fdd27f60fe163d5584b78105d21777cf859b19a7e846f9f2469b880a8d8dfec9bad4e59e257bebddf62db3c278041e813cae948742400678856933fe82e5dfd6cbc35a84ead85a60b1fa05ac09cd8d5b629e217c1b460abbeb35c1ea875afb39c7efa154511ed64a4a506fcae55c5790c88e2bee8b6a6005da25d1c6f2fbcab57df8e3ea9e39d8dd25e186990a34cb81157aea58b17d35b0a241270bcc48401a6d42be99c29f235b3a625fb0725007f0c265a33a911c7eeeb51038d1e8683b4dbf0c83ff25fa4ed1934a418f4f82159273a7448196b6de299fb06623ea6f121d6eae36f48b1c6696ec9d24846a620591515120ed36d2b2c08a9400db3921528cb3e592d98b65bb64a676672312d6d56b79295ce91988436e20438001dfd33fa191281d5c8b6fd96b842e40683bb3ac856b27930513334db11581d8580bf4f4a23e062af4615b0086b956bbc805a7c1293b3e05161d7fe13b07c8b14b199b3b39503b2c3fddbd087396b326875461574df6dc08c2fe2ae406355c2b9ba289e90fa6729b5508f710e26825af7a03333bf5427b55d50680b8d8e305261d2ca1f22db5ab35a1a5e50ae4fbf637aac54b85b23c956c2e6719ab5cfd4bc1f66319e46056c04491ea31bfd028915dac078717d85b653230f3ca4601599998ca563e25cc27136e123b0c476aaf90d178e0210e89d4a6c28d1bf35a432908d718c44b7de091edbcacf7660e1c2330780f1985b2db9a895034057fdf433da5e750499785c93d567eff33d1c544d1c8c1d3211cd35f0aada93d2ccaab3c96b5f1d436e88f9ea1e59e5db524e10062e79969d4c0d562dc51bd9bd6061343b31b16d40719373bdfae897bc7b9e3753b9c80f8050946de311b648961f772f322c5a23403ae49ba9676eeeb80a9938d1b14ce2e1b4a0630bf891d3a406d1cccbf0045cad724227117f3b9920ac3e32afd662004c7e9ded5e03bba9b55d1e79d40360971e8ab8555d7a240f9c0a1692b17ecfdd677e28d9b739ea256a5f99f7371af2e9ba8a877591231205cbfa3c787c37933a0fd690501445e3163ae23e74f586bb112ec379e13b80d69ad38ed0f68362f2b5fe772f1d8b5945cd87849b643aef12f672b9e0dd75a46bb23bc62feaf184abb86d359aaeccb43acb", + "s5": "ca2e1cc08c4121092057e5d6ce943c82f3a3e4fcacd0c4326299c73446e51bbb6493789703671408ce29704e9939dc528004b74d7fdc978fb5e0db56fbaca6ed3f68b2ff863c949181a9f0f91c26f9418d011a4c4b7d2992e172d865b6d6bf0ec7a5c78412b9b165424dd9a31b5ec6038cc12624a533673e6253aeb4db520610a79c39e7e84a41e4c9dbc044ea687fb2d3401aee7c67ea8a1020e0cc3d843bbc3ea8137ea02841116666663e950cd29bcbba29278893d4988b3baabad041b89d2bce58de55b9248e19483ac7b2e8bedc5d347d99ab16fdacd2a6df5f803bc70292389efdee715508db59feb3294ebaed681f010ffafde6621b11f91076a5bb91a621b0bcdbb702592d3af0c246354e1c95021fada91a3cd7974f8d6bd39b2ffffa5a54229e4653f752b97869d3a29536b6029bfaaf367047035f6791cac71f9470acc264c0cda6d5320e8d3b6de69f440a552711234c4db9b76b6c2c8f46d2447cb6dc621928f8f9384382b000d2b01425eb41e85349626dff7f971b417089ee9752dfaf75af36df078396b9da7dee35f93092c33d8e3381e049d91fca4c9adf4b1cd1c172b747d0dc04f723e9861e7e5734881bd5b5bdd59d30879628d39ec0d5869e56933d2154d56233c922ed9b15a048acc0fb9fce1dc4cff7396510cd7b3d218071fb9ad079d3af6dfee0c9b949dddd77739a1ba8abfaf474f1912db0deb4df785be6ed71bd2b1b08782491f242ee6307d40d3c795d298306c4dbcacfa1ab8c465325b617edfbf428ce5de196d4e82d1832351734ffc6512a07754f5fd2e692d3e0c03ffde988d215d8058d90d485a62c276ccf88838186ad1c0df8005dff4aaedb4e8155c4dd393e930e280512baaf92c856b17e4e48d241506e0c77053d31ba39a6a9840a7e7b03a50311d6c3d4f8df4ff7836ccbeb075e2944f416f24b957540630ef7d7aa3134213695623c9e38dd4ba4cd708ce8c139c3dc08b7262ec6df9cffa29cfbb50294f5dff7e44f4e2d208e39ea75c467305dee0c1254e85b28c398b4cf3e91c615ffba01c767fd91fddac51a60953b413820a516f022d6777b3837fdeb9cf8cbfc0b909a171b754a2ceb505beae2a10c67e6fda88262541db2780f5438137a8f3571aab99942efa459c3095160112450562581088476d28cabf092b7dc24b28c5584945557b3dd1bba7e05eb05b022742cc783350ea4769c052f3e745402947983deda813870a4e26777d6a86a15149cbebc2463fc5cdc55cf4942", + "a0": "7dfd158ed42294115a0954644437a5666c06d7e3c73913457e7b10ac9a6d5aec5b4a1e53155f873e8294515971f79391c4931f849a9812dd9cb196b8dabf943ec613b6a7f815a398e38e2ec2e0b01383533668bf9bd104bc1686c940b73c998e7432a4cca184ce71be868df9629bdf7c3373c254693cc868ca1da70283f9ad0f44b0e915b8c76d5f38eefdcc5b3d29a83a31f994ddf5fe1b21c8599ee4632d38e03fe4070bd78665b6a4f70c4aa390da659fe2e12b301835a3c944140c081c5f580a2579b1295c843a74590f9016ce407ef68c88526f0de78de321188d740e69b6f830598a36917775f306d291ae53ed6efe36f2731176ba00fb6a9b5c1e7e26f00778076a6697d83acd5de65ed1741a47ff389da018a9543158555ea07671172ae758dd5d008322ca72e822324d841eb4a3ebff786ee681f32b12b2ef64088dfa671a5e689a8487f04424918857d1ef66e051b370a81f8054674d7001179f44e1c16d5305e19553864bf07495039b7cb48f9ffe0dac2b5ca7e09cfb9c31fd8cf101ddd933735fe6d21cf8cfc2abba6f855527bf8e6d6275019a282bce2526b32d3a69f8469531894f6713f282d309029593c6c5bb5787c2694dd783c74e8c512793fd0770141ab98e5b6b1293b299b03a26f6014590ce11a90df769bc41197c95deaf0f829ba6c694fda356184376c587c8e7bde4b6f5c7ed64e18f473cbe17b0726707db808103b57329debbdecd6315f3643ce2d42ec847861a036957a3184e5629158c924030831d546713346db6cd1749e7945d5e4f237f2b67a2783ab9d81869efab0b8482195778513e3df3b623efee143b10869bf058cc6e5849bca8bfb5757435023aa578177728258c662abd14da22dd8b52e7ec52ebb9fdb12aea6597654a8b35250e194c972b8d8d03e52c87f65944e50889ebb778d1869da04beb74fdc737a470acfdc346ec9f8ecc98ec33aa789a3db28f3510801e", + "a1": "fff84baab84db6f1a9d23ecf4fa6e6b00db58aa408a15c4157918a2c74af6f18fd4152c7fd908f0e303ad9c5f3967b5730a7c4fd2c2427f9edcd9c9dd87fa5fbb41d8b0484e0cf755233dae19ca2977dde62a36b8d3bb5ca57c87e178460e1a80f399a2b89c460f0b9d4a36d0eaddf239d3249724b4236284134425f6e9e37cd6897d969dce56981ea15a0613b312801e9c0a27b438993ac1aa2da154fbe1886c082993bccb4664f03c7c163d473f520e41af0ec03aa32b87b3877819ed9234e54ff7150e89e1243afaa995fc818d334fae28a1c04d52d701be25adaeeeac24db2d866fdf0a591f081f8533275d7b019fbc8fee629a109a54b5e0cfb2f4963b788a5b79ef39d521de358d9f4bec513ad76a052360f1dfd3a580dc10c5a09739415fa497146d5b37e4e64775e76b5bccdd673dc3af0f9b0238ab766e665766a45a1ad42c7f3a658520bcf35b43baecf1d5d54235381ac25f060240759c06faa0afe272dcea98643ae22d17b470f8ed9cedf100b64ee287558f317dc7cf8677a1fd2e4ee6eb649a9f6ad40d1f781ad4c332d23ccce514ca9c0aff7459ded42e78cee7358ce1857483a1af46bde49b1d902db6fbd475d56c75457b0448eee2214ee0e593370c47a203e68ddb2a01df1c9dad4e6d38e987b4b4002a6a812f480f6b6aad383d605fd9af05ae44fd2c6de0039c5de942e3742f08ef621dcb509daed9bbde262b9c32aa2f7d7d9ca31daddde4d3633ac19a8b5de0284882133a325238184c158ba613c531080241b36cbe6f0380111397f772478721d03a578b92d9c60b1cca08b78154f6595335a177e11ee97c172c34d93837c3230e6721077329fc4eeac6546a5f0c2a7536d2a48163e113239233158f6998f920e1b9a7141024f4861871470a7dff7e031c693379aa534e2109429772532e115e736ab1130d99d2b3df695710b64f0a0fff2aead3813c6738cbbdb176ee51addf620bbca", + "a2": "1d8a668396ef6bc785df2ff65bc082d91be3511b7501ea1156ba9caea90a38095360dd9b3d2b355a261c1e9860b85423d3ce18344e5e1782e1319de709f87785ad83da7dd99c841e17304012deee799ab6e0cb67fd556e618c59c995ca7783a8c0c0a7571bd8b296afa8bd89d811604b309b9d4f13287e9b102999d3b4b7f0c6378a4d1c935f4bd42a81b12bc1f1d91ef7d8d0db33c7ef3aca570f4141f7ed643994bd85e60ad6b5a8d04974d1f1ea52ee5c6c09f33808894b28e6bac44dad2ffe8f92b478077751b27f4fa2aaf74438e2e539a0f84076763f30e8dfa904543ac8cde3930eb5a582c958cbaa4ee6e56812fb0f16ba5a3b8c3e7b53c2cfce03923c93d79d7cc80799872a264a97cae42a9324e0ee93efa0f603433f47d74440ab896951dd3636197b1185e1ba168bf5815e313d5e555c0bca51e861ab699d42d59edd43cc9bc2aacb436707ce93232c44f81f7e1fc8c8dc801baa93b534e7fdf452e9c6bf901c42deff64c2897938147db3623d24d0594771ab5b5022512e6c7747888587d05088152bdf713e2c1de405869d3e225ecad5592b7ea2706556eb03fe4d383c4a2029b3dcc67997d3a420f1f2e7e78088f1bfc94ece981448d6fc0e3044b33c11e5ac1654d9504b2e91e81555e69e6b04a97163cb6a9857960b99ed2429974fdb4dfc8549a4179e9bfae9e1bae1cd33614ae600f26650d5f4ce98511103d981b5108b57d59e8cf8752a613d7c8d2bd4513453e8f79ef8f367d6d8f11b6776861b5246fdd3387b641e51c67e67b55282ff029cbdfbaba6eee674526b359be73905aa4a49d1eaf2223f5e05ac4c6fd70dd845307256619e96e53df1a4c206cb4b32d74a0f86a06469879a326d61434d9b311bc51a367b222cd1216910602c6a5a763551117e619f5384fa9d1b91c0cf917a70290a99d6400b7b30e8d22afb3265e89919773e14049b03c23f7dbef21b37119999284d3988f6", + "a3": "70278d60a8b88d87232858f3bae744ec5dc5ed0c004c1e529573abcef0c8395c550342b978dcf6fd0d46cdc6f6d6c8bb0a139bfa2f0d58eb33c3d6db69b710a8ae520740a6ddca36929e4c014a899171d76f2d6c85d68758d90c793b57d0e52e1c1ae3de4f2406da9e3da903248fe5d7772fac20a7d10151f7905373d7eea39dbe56365903d550ace94a250fd44d990fa8fc9016f097885fb1830bda75cdaa109313154e414aae9eb003b06db2afdc0779eef027bae186f4e08ef8999c90cef9bdf7d88b70c3b08db3dd3f50189ecc7862e1f2241f1bf99628179d3b3accd8b1fb52a5e41f1cbc06b801eeb4ea736a8cb3711ac9d33e3c7da81ef1f0d849848d56ddfcf4d6e9a77df7879860d62332f21f8e0236b917851ec814241342b24eace62b252c77b6a14e1a431303d51f7c489d015b6f947088d0d6b4b4f4dc24c8a615aa3895eee879f4ebce412fff7488f9c7695f769473271cb2d07c50a27dc498a1b90a4ea213fe1d353546d723a43d128c0abf9b7ab0595c5f4648342d585d02f760574fdfc02cca561b508ef2d0333fa5c6edc3166f2852a3400bd1f54a08b5ddc12bf5a766a3089b899bd234845435158f227d77bfd483c635b2ed05df2140aecad55625258818ee45e7f41d66100ab99280773cf4579908fecb7160dba237a65ae01523dc215198cbbd130f901613038c6833e8d8db3ff7df907ccdf19f2adbdb02b27e9eb7ea427531cb46f88dd5099e5737b867ca8d16aa1d9ce0a0b42a00458a36c321cb81a22e144ec455bc19b389a2df1b890ab6e3923dbed1fd6dc1ff5aa18015ac783006f831deb1de42135c929569677d8ae4ca80ea2145b6098fd904c21d0726464787fe961e03206df13992f36b28c244fb4c879eb0e2a4952776c1dfc7a728201218b31d42aa3425727abbf090c914b97de5e22fb9ab421aa862bd751a1ff649ef67b9975b13f73e6a1041f2fefafa631798232ee1", + "m0": "2726133fc3061b92166964f22aaa61a93dd89fc95c24282f711d6654815ccbc055e496788424f83597fea9aaa2b7bafa1d4613dc843d361f1e7a0a53fd75661be4d4449d6435c3644a8a1a818f2614cab5afcbf22262f7678114edcc0f69a94cd3aab6bfb59c1efb8f58893b83f5c283e85bd7bb77c105b624148307f300ce7768279b2fef865cb3651dd0d6d1ff34835f4a5a5f89af0a8e5a1da5abdd63fbd139216beae00703955ac8c9222aff6392e56d102afa3a9bb3250e5b4c3010461e51b32247cb41826750360317d36d14f0f17d31b5f5203858b20f156f7ad5849804d86a1231e4254163bf11ab63ca9f7188e37348fd88d9f4279519f71a26be7f9aef034bba3ccb52da422cc624c975e6127b7ab0aa8c814a", + "i0": "64fed8b38461629a78fe4822e190c41e6b16d691ba057b2b09aff2a1e7b8d7a37bf5c27e8153e18ebaebb682a2ee698ff7be7b280e4a22e0f995b6f46efbb1328e8b89abe48caf9679764e048b4660e0390b7b91bbad81e799de9e45a479e96970b4742db43d823ff3f77403fd39d54dd149f22f1567459c558e09d0c7a01b8a147feb331d271e4972a53a1e0aedd4954a3da2a6843dab510029cf91939c0c29e331a9741fd3c505002d4749d074ba2034a364ca", + "v0": "063e95d2dec2bb3a452ee3d5ea45a260e489e9f69bedf0f658b003923807b5202ba201bbdbd24e54f4ac211df79637f9522c9e5af1f9354ccec14330c039191630404064d8c437e4ddd5a3e5f8956088a55fcda02aadd4a06dc0dbaa53dfcd7b35cd03be9e28c54f068dfb8225dae80caeadc5cc4766e6e5de5e215c0394ec845ac57297cfb972dea1f546597cc21dc71e03862f33f9563b5b628e41e30bff7024f61e8f20403aa3bccd8d6146113521b70c854501fb26426829e08dd4094d45117015965cd157a6333fc7c4ca89fc5e2a696ae78a277911cbd3a8f9d0883b2336c4904920a6598ea9bcd6b4a5be6c1d34b0995b65ed6e6a5891d1543b71e2bdc533689c25f9b32815b3761fcdcb615d678ac275822cd5101e0e5e78645f68eb3be40e489fd9e94ec119fe0eeb6992753f14674373c192c3d2239056d3c765ae0db1ec65da4004c0afca981671faf97c07669d01e1348006290367f0b6fb5e811e88f6893d385474bde1c82dbed193f15d36692f36b6e4416e48807e603ecb2bfd842ca1b5948f57b1f18cff938bfbe39a6adad5de057ffc2dc049a3a7e6dd9fe30d4373c16707822ce886e04ea303ef0159fee6ef42e194c77ecd70ba2e359e7bd7c9b80c25a97f0e40fffab281bdaedb16eae22d8671755c4d3703710791b9269296ab039771f803ed5eab14fdee46510cfe516c868c756971e06f635a3a99674e67c2928180884725c069386689e4cd3bdbec18ab7a5dbaa7585c0d8abf88f460df17513a2a42a41ef2144c2e0ef7f6647401e382161dead310efca9502264be85f1f58a606ddc8b9e3dd37321f41c88f95239c05615d38baf0a6d2abbbdecb654f4f51b3f900e10a20c1ad4ff4671ff3c782ec6d0e68fd5f5437c36496ae1c574eab6c230547ae23bcaedfa186966a8805b85f2f4e45e3866e2bf89e6a96dae0d58833e8efbcc890e92b402648833ae5ac8b25b7e97e9e08ec01b1fe2754d47c0844f4ac41ba79a6d2a4415acad7e89dd156a345fbdf99c5bac57d7f7bd01d437fad3859c8e45b940a10a26966b6b263755d92369efb9c37371e6c9c6d23d1e99c83c60b41681fd23ee0bf99b8706f4cbe62a8572f341acfb0677d73bc245164cb60d1f1c69c2a2572862bd49f7c3428fb288e1d82ec87aaa8cd238dc3103ba0edaff86b02ce0fe51d2b40b4291510c589650de17d73745761179419e65d9f9f743cf7175619d58daaaf4018d314288e3f5252e248f94a824a4597abd89be24e6d851b601d1d21ddc4b468ff472d081c80538c453ded663642d0f2257ee3ad47c27a01e0cae47674cd88f3c8f0916eea268816a17c1e37f5d3c1da999abc66d31a3a7179b56602515fa1645ce2e056706f0242b465ef72d4a05c178791a6ec5dace9fd44e8521268af5f1df24f8c3f1687ff92d8c23e03a5531db343dc42e7e9225aa248c0345cbae6c8b6adb9b4851a031fb89a8654ea84bb023d70db80733c7339b2984a4c68d3248353376943687952c0f3c4b04b869f244863d855615ba7ed17daf03fbdbcb2c987ad7aea2c737167f9121d0e8951415a56ddb34dbe97bde17f581ed8f5e5f61f527616cbe143318938324efcdaf7e7f8781bcaa72a2bab15ba937da268cb120aa3f3b63f6992328a35e9a69e7d9f36675e1d26fc7777522dcdb4c52dfb32e66b6a449bb596", + "v1": "30e6c4c7b2db447eac9c95da8f6f3204dacf78fff138e3cb47b305e8d54eda7491f21d9d4448c969c40ff661698f95b3c8fb556abab7e7e4affbc7717f30cb9ef261fa1f64a22e1ff299df79cf5cbb3e1853f374958d628c8a272ca2370b5a011339867b176d6e58c791571ba599fce072573be74b2d5ccda3bdf9e632f73dcaf7f611737d765c904c38fb48b3dbdf3289b05460f97108fc713fa26900eed65fa537fd68e5c6430ea0e02e5f1ff89878918e86b28c0d414b9d69f64533afc92558dfd43e61ac16bc0014837d65c230039e4a6acf9512919a3574ad2a90bd450d9a7a70d607ff9ac812eba029a09182d32b15291147fe6f3d8a89002f579e6408177dae2e7e4e8eb13a60559785418df008025dd6a1ee231605e208a01817a7e12005a51019bd1889da026a59375206152dad458c7c5cfe24c6e622cb2c11b31de7393e70817a1a08cb34478c1a763cfff0df3fe9d328e327829be000d108453472f83d160c4d5ed0d5e3e38429c7e50d30d19fcfcb26cd5ec9c94659c85ef1f101896da0758333b35cacb1bb415714fc3c658e1d9bc10351ec632220aa16a89daf6fcc54044346950bfbb5f95ba3d9ae4832b68b5d3c05ad343becdec5ebf02eaf9f14b82d9e5e67db50ba903f2a5e90116c0e8239466e518aa48c82d97ebf03ec83e7de164554e7af894046a1352830699768a664b9e554c52943304e96a61116e6dd354a3f02d4d64bba49070bfc43074f8a657b0dc1c1f2f2cf27563be0495d1ee7becb2095e6a7707a560cee9320d4510ee61183449c6da539906b51177004810b28b39ff2335fdae2272e312b84baf61f850e7b242861308fd090826d0ffe2a839182939b74a048efdb09707ef5d81b230057fae1f6acbef61b3c279b9ef09dd0c7cd2099d93934ce7871475cdd376a194d5449db192fb7c29587653c858a7064f6aba75e4385e4cab01c50bea6f355007b766f757e9a0ed9749b1037c0ffa7cb5c6ec70c6fffa37a76c3ef1f0a8e851556d7b46b761ab822103d6dcee7b49c3fbabb43c1ba8221a7120b43bba993a13e693d3be9674ad6c7790600cf43d9dce4a9a7750c45d6bb128c0adce4404e7604c5fc3af4872f0c0b010572df6b6aae4d82624347569e128f29eadd65c1858244616f78fb3df07814b252688083a96a4964ebe15bf199970fe0b4ee73f9dc147aac9a520e0dc32ebef07bb871281a8ce57d61eecaa574fa58be9a07a318b41a1cb886a6ab990b7613e8eba4ab38ccf8c7b76a8700b6a32bf5011591596fa14171d21a7ceb0660d64b36990e139213e206a7a1fe3846f2d8ac8ec4a799d63e1d088e39be49fb2a8455c56b4e5d207414400c651085b2d201ae84d9817533f89ab294cd15b6c9eb17a344a18ef42c6b8471b9e4f82377de5e5956c875a938e233fdaddb9f45fe0e7896391cd1008c4478665469b88df22926b0a5a1635762aba67ba2484ed4fe4f7f0f74dd7e569724ce9ed6059d8922e1bdf9186e9b6bc4f125372ca920769fc1c26b1a4e02bd03fb207c02e6954d004049e5e0e87d8887bcbd343c3ff1345e5620ed9d30d3accef0a3cfe9fbb7a4f101dc852008597fb50edd112275a517a6ae27d51590eec226426124e0f7d6231cbaa2669bc2ef2d5023692fc12ab207ee19844aee1144839901655a199d261e6064c659bb5b5ce1fe", + "v2": "2416f508850e0047e37ae7ef28456faf195c73c7cd2c36b3f2da3410ef2eaaf4b68c61b599b063d1a5feef8a022eacefccda647f1af76e35401157c49d15ad41737ffe19d92782d80160009ed9f600808259bbcdcc3d29e2df24b2ca7a6c5a7715f17f4502734d886f990dd91df68389854811b6efde93af3d78af36134bd0bd868db8ce62c23cd9cc4f3bbaf75e7565f5168ad9f75a2dd1084e6cf9f5f582d21dbcbca2d6ea0258c217b8d7809bd732c7856d9ed9f6ce1f8cb95ee9554d63a20ff27df464e8c74a5c54fc917ee347603da143c3e0d1528f649ed0996b4df0e8240bc359558317da5a44de4ea98267c23f5d32c78fe674a8f03e55cee3bbff9a4c4040a2c077da000f0cc8f21fa46763d8aef58e58c8e95a233720300ec6d9d2d1f6428f3499a1b86076ef23003a48672629d92f9277a7da30e747946768e8c91ac2364838aa05f71e2e31f5899dad0b6cb35b0ad899b3c5705da8490bba5675a38d1c58987472aba58ba334968f941fa901a60b3c25f8233d3a4876f97be441b937d0eaa656d2d0af7a4d1814054568165f2fa896511d115383484ed9dc240cc21f3ef6e313a32e0357a57e1bc175549f20a66cc6d3f912c8e9511354314848e7416a26b8993e58e6e0542f6c245b8086a225853f49a5d7e2873dfa2f747128306f3860b4b476ff9c29a4ab44130981483fafb47cc0154dd51309c8cf5deb723eedec80c443f1e3cc98a714a0807ee1f7100493375e38330379887e205ff31e4bcec17e97636d0922c668d107193b3d76e5b1298609fe89a66f1737b4cfdbde51cfcff848ce4addb34cd19967323507d3d0b76edd1eedef95bb8fd752e279b3c91feedaa74fd21c6b0d0340292ccd7e621e7f2099b759b72ea48ac3726a0fb2efd590b1a4f900e9b7b7a6b683f96c4c1a14879ccf66ee78fc2fd268962e72c25c953d8b2eaf0fe351a595cd15c4a05eb6e05fb1417a52cdb20d0ca058f2cabb9bbe595397ad0f0745731f9c88cfc11a45cc3901ec3b2661041d0f179f18c501267c40882fec4f2ca8804978ebb04ff7fbde23bd256fb73ebc93793819c02cc29fdbae6cf2374bcf5edcae99de87820ee71c15b8880ed2000051115e2acc77043566b1a849f99f387cc87ec48ede13ef2266860e177182ad118db19899d3b51751b4a11e2f946dc5f1d278dc4048502459587abb3588e740152b014794ee3d4e34e2ece9c3292910789a8638101c873852fc3df9b91223afa54da8f2c327e3845f6618c1a137d1795126b2fefe7e444e46a2aad0ab9e5a6c5cac1073dd4f110dc88270de762ea239a657baec40779b8fb78e2031be9a1a2e3e556cbf7653b0edd7745299252da78eaab8bf6857f740f28428ee434f685204940fa5018f45ab9c0a616648e4cdb699b510d5a7f76a7276b376739897c7b8f4f998ae40c8677aa3e7137abd65ea1b0565fc58186a057a20980043be9d2ef5028b7fd7ec07ecce7f64050d6fcceb33a259bbd96f379c983fbc9637d0a8737d16390b0a4101ffde7be99012ac21497f329c6f0bd70153e09d00aa87e66a0fb2f2cf0bee32ee513ed9f03cf7e719ddee97b6d563fc8ead199b684d4b065e3ec4b8b655ad4f18d0bd41da00ef51ee202ac1e2694845f07e7a1741ecd8f2356fd66a21e65737d24b8581effb5a319f841435746362ae48bee3d9", + "v3": "0b412bd80638bac6fa166c554a6c296a40a3386a17aaf1da4bc94170ca1cf233e608e8ccd6909fecfd9a43ed149dd52dd892e6d3cdb63a8f6d5efe1a0f1ef846775996e6f874a2dcae3c1aa98a8dea79aab9742cfa0c1224b76c41eccb4bf4b62ff1be4c07104de629211be45439c36174d4d56535babb860572586f8678ab877998d1c155f247fc8de6e7e1e59709c70260bdd06f4606fa4776c00e27d76367ac84204d12221ca57b1245c7bf8b635a93059061b767b18ddf4fef8bdbbfca22066716036082567f60d10fa3b81f39458509de781449f73121c132fbe4470b1ca3b89317e7bcef7aea039b58cf77f946a441f990d9766b59ea9942abb779a531abcccfa856cac186fd249de3b176419a40a138f6a1fe5ad56b01439b533a2d02db4a0c3f9345f92b9dd8698adf5a1b911231abb86a8e034d958c6605d54716367d581de3994030d550c71070c75531ce1c8cbe2bf5ce4604b7e470a1d6ef48e3137b919f8794532bf087c371f28f7d4af4438288fd605fe1eae0e910b13930c54f6edbf6733aca37bda8ad43f9de84d95f2e9dc324b4beb388afcf7714a0c53121d51553587474e0112789de917e583b7227b4755d99c26636c76edc8deedce0d7b0d3be58ac7a79d419fe846a071c044ab12cf694992ffe2f2865e75121f50a83a1e5fbe1bfc60bf66d30a074f7edf0c2e0ffbacd7d692fe6ecbe78b50e5cbaa1e4a32037bfd7c44475ac686ea62448ee4f31ad5d1be43c7412b5946a94b455ab3abc8ee01187ace46161ae6196a0b12a02b76865f4c9234b08713b881a61e4a52f7eac4342a5e52c655786e699b6b3f6ce003e8230e90ec760a1bcb50831c4effb1338e0d1a87cfe52b35c7cb676f1ea4a6de2a500899040e82ce2fedb54718c8e9af187e114d45a6174ce2bb70e91b668143e8b79a27c5de502b756e77e658aca4f1bb1be226cc15042e4cdeb894623d6d06de44959685865330915a76599290e2e9949516f5f156a9c1964fcbc838cc9d62aca407aa80bb649722e0c4092b80011c904f4b3eebc401698677e6f90450d2d37f36b55bc12bcf3e4c14a60de037945a9b687a61b486511069b64d929ebc2abb330ffeae41744b9e91608d1308ff890910dd98de2786b930bb52d38964180bd081d19e8e68cbc59a2666411e98ae73a4d8c96c9a6cf677e8441a12aeea1ef030032e7e4512b6b627f3a30238183e470219b966a602aab5c6ae9321d5d73791ed1cedad744fd7fedeb5891152d5f737c98c32bfff751e6db34f82fd7673d26ee8fd619271cf7c3518db4035d355c1982eca2b5df59eec09b8597313e69a7b5c9b22aefdc3216de31a01423da80fe8da0e4c8332be23429fe8689bb49c3295fc458e27f34089e7277e054f39ec285ec76e2f7227daeefb24cc6dc9bf2a9f1536b0e732cb1d74056f9911d1c2c588830d5e01ec3558e1a2902117dedece67a1fd81f71ee3c2acaa028e762381bf18a242961aa264ab5776f27ac809c4b319142beefafb3165c4458a41af650e97168f36163ccfdbb8b079f5833e593b21813cf2dae9f9cdb5d85c53759be3fb9d91ab57abd94313b06236e28be55824548e6ccc0557b84462b8af93ce291e5dfd191ba4d10dfc9af91fe61f249fe624a3e50d2031ab3c5b94e1412a262df0b8039bc19a83325d359c54175084904ea08a6", + "d0": "94a1f64a06f773e39966f7122bb77bf4264b106674209a96ca95e3b3aa25f947398a30214ce13dd74fa649a703e13ad1f17acfa7c690ce77d844a33f4b372d29b4900da7e0b2d2189e7153e1dd17ff45590f16429120085e681fdeef77a93e929ec0e27fb730ded0e421256b9675e7f771f96701e79f868836771b9db6d10754bd5881ab8503a973eead34ba28844b799806def5e15e8ed73b0fbdd0b884a8b5316548b44742cc9a5d6f70f9bf6a5e60e71e5acf301c8bda86bc84cb73747ce5e9b91abe12134e7f91eecc50f914f724f0011c1fa11412e105846ac38a159b752919ec22cf5c54d2feff7b0192c70a160ba467b38b8d45c6f5d58b7774407a83ddaae17a34f98a193bb57a1f26d501934cdf2a599d0ce8cc5306ff630e9b70b0c4ca89a47fbf96a00ec9c4be866ddc46d5e9cc3420ead20248a26d63fc87288d98a9e91e85734db0811b5b6b15c19d25dcb2cd58981ab3e3923e9cf9e1338978e607c56b9db1a0aa9f6ab8904655c1ddd8ff3d7d2487d59342668175136dda8b4090c998b2e90cd39c52510921e095c41d3c2dc06eb4a34d8409fe0bd8ec6f4d1b6fc06c46ce3764d4320482ed399f9b91645608c75a41e02c55b0bb11dae93574c5a262e93f43b1e289ab9a56628af083ad78de2f9513bb6e33653d437ec091028b8135acd892f809e74617f278bbaa2c3998c79e0a3c0405bedb15328dd17838c14799027637819895ffceec437a4fd43fd785b72de3e850432e01ffd95e0a8c35881cf0541836a1611b77c4ba82a6fd1855f027ee02b20e02b0e332c523c010377bb6e63635e3616b315bf5a966f21ea5775c3d5228532728e32be4a6492d4fe5872d0973dbd6a9c4453503e0c004eacb9eed653c7f8cbf7fce72fd96d0608e54f47b2d471c59fe6d148e94d7e8e23566b72949c7efd8258adc09388f653e95837c7972fbaa190f2a667b69970e509284f1dd62c12fd2a56cf80eb442cd2c77579c1ac1f9f780fa9312c4222ea9386d81be9b571a097ecb61295098f9ac3c2495e5e294a720535c46abf8a88c806665ca749e6e798b0ff385af0a219245f40b29ee94373deb81631e1f5f5165b89bc6a21860604016b8dd68842369d8badaa829fd1031408b227a41e50c422623e83368b0749642cb326ec66aa8e013c7490de8deb26bee0d1caac2fedda369cd387dc052216c92d955cc7c480171cfa45b47ca99e93c152681fd4bc01a61d244edf9891ea7afd2525d87c9d5ccb3aab06543308d79", + "e0": "c117d6d1286051f9cc11229aa63e6d5900dbd4b5873985407581a7e7a80639eed6258e1d07b42fb68f7733e88a9ff6dd48763f5483057def96ed730aa73bcf7bfec13c4139fe9d089bbba8072184d0a0b116f21a02e1adf11bd51770913805400e085b9c990e5dc1dd5c5d602ffabe9498e5e199f51826292677bfeb7c7f737215ef0e9c0d68deb3aeaf04efb5274133e47e974c7534150adb67c07c22d000caaad751d5988bf084b14049e0aae1abaa16deab092eaec5e02f35860630ff1013b9ee5ab37b1e7bec7163d84610dbdefba3e0cc928b1e96afb5998421a972c4a2a9715adf0f0d1517ffe1a2bbea34186f4c28ea3d359d5d5dc647eace304dd23ecc0bc97433f8abe459a3f87333ddf478a2c6f87641fb13d2bf5b601c3d692b7f5c661a927d352e1c3d0dcaddf4275b467cb38ece4f33496bf2933081eec40bd1a8330dd79399e890c91d4e2b469a01b7c1b0bb78da0be9255c8f1a4dbf822c0f21424e87c63c5a290e88e41bea08c705a560c861aa2e5a3c66675a9dab7c8c7eb304617f89877abe5d92f1c25e509c9633fdc36afa1c64e4ca377578b56a88b93883a9703f50a556930c46ec9dd0351e8ff22231a0c89a30576eec823dd5afba35e3985b854d5652381461d89c53bba1cd0ef9e09cb3d03383409edf4e8fdf9492f3b5e32c4ce3134910f788f59aeef05258aa93f61fd0264a2b0cabb8ff4db690e02bf0fac4d2ed4bcbc87295b7d97151d6a1fd3e8e80c810c17c4862f5d10f65cded057d14c9acaa068027b4de422ca1163ee1807152144491171a6e0344852ffb99edcf4ccf63f0a093bf3d3e0c926e0c2d40da526ee471e550dd88938be8d16e8b12959abcca6e6234e603abb21c1e3990ea41aa27fd8132ca42b358febe", + "e1": "c819ff0f6d10dd980862d51aa7ac63397cc2fdb0f607b1ef43b222f0a6effaf69758a41d05f65fb5d766df30783955261e573a22d6dc6c714c782aa8555f71db647489d9f0e12e1a52db62d28c3968ea77d9b44d8e8b6ea44c10e75dbb758470b15a28d96530fee6f5191116af5e4b9ab138bfd9a9b1782f53a784eb83c3eaff9098cb1f4434a271e3a4c7ccc52d946f9418f03025cd337430b4fd47dc616b51c2e47278a6a2293f2b0bfb68b779f9064bfa56d9534fee3db51132647d961652220cdd93735a62b29804a64bdab8ba2556284937e918266e6572553a200ce7b5a22acbb46ba8343ae01eba45d60ca0866d32f8d4462fd791635b3e0cb0f07b3e1f28046c6cb703bc2fac46201aba0270b41cac07a904ff0a4b99406956c2b974570fcd36a84ad5d7a6d7cb8ef58587a9a7c1407d9b50ca457a64df1a5a962b379685c8fa7e6dcf50eb1a2240a7945337ee847124b3ef6c94d4218404d0289da3f1d54c95ab4fe500a96d710b3cf5353312f69aa36cc0ed2df302464c8426ab718fb1075706680dc6fa0f062fad5dbf1bc048c888ff3c9548ad7d4ce827cdbf9ac0b800dfa73bf4437c46a2453574852aefefad3c45eb3c94f3e76df981d20e67156b77294eb3fed505e5fda8548b227a90ddf81d22a177ef49d038c0e690d8bf0658d7430671e3c50f3bda9bf25f6e3d02d5c7c06a29a05e539df1e07f8595173d3ff291ec6a27e35e3e8673aa64644c33232df3a7d4b4cd335072fbdc306b2593ae59266689f5bb1e6169e2300a217dd9d3688173ea56321caf58fc86dad0d7a65b561a047d37a150214e48b8f95b713f2fc7cf44b96c315e1765af06f1b630bccdb5111b8b2691d17c59ee48b7d9d196d372c40642f4ed18d03bb14c89ab67", + "e2": "85544340728a47df6a39da19ee13124112f96a34fe00dbf2f68755f784a5efac0460503e732d0a7717f1b006c96351bc0e310f80c0507f7f53d46911a321b74f343b9fb6f900070e4c125aecc46dce548241b371f9d5dc6ed72a4f7e7e4709e513b3837bc652293c9fd6097628c00d12a423d97e1429bbc3eff7ce9b64c02a12a7cb2563f96cad34b9e56566be27eab81310f5e9918d9432f0a1f77598eb1daa38e31687ca93782cb5ba8c2228e6ec69d34900bc3cb1164715fd2a8ed13c7d041aa5b596cf1adc27114535dee30e0ada0bb8b27c1498c1c4000a57a30b80013535716c9ea54e7ba10e07642a046a8b03a6766eb6fa86f70a5769d17aa45f64927176d997df809867302da09f7a4981d5bff2676c0cf6e5a11bd5f7844684eb9bbe1f40780230ccf1e1875aae371cbb4cba89d5b7021e3259c5238b8b5ad318efdbe915f152384d152f2bfd854080c594d8d412d4ef798c7959e58714533ee801602d6afb3bc0130c1d2535185258e8b180c97023e887d90d3e0ad2e4d535ab0d7d8cd3b7f1b4ce6d571f76e6db753adcfe752fcfe1a27d3e462436dc1bbf126b2fd5c36b562ff2b9e497670b393806b685332bde9d8d02727a760f07185dce2a022aecc66183f55c30fc16e1d7e596b1bc58624d562743a771a81f885b3889ab216265981453af7cadb53a2840a3e4c695073e8911eea566ecf8a0de51faacaf154d8327767928bd726ab6f9cd5008f6d45afe2fe0f7eb1cd88306f02137c4987b56de73a1b376ee44c35deb7b3e0eee758bb5e6dfbdbf17f9c39585db374754d58055655a36ecec8fc17b314077189762682ef92f390a1ae2a542bd66b9d9f58410a42d24cb6ad12886f8fedd6928c70c3ac28a2303e4576058958fbb3186d9", + "e3": "6942615184aa4acda68c77a9b25dc1aafce365e86dc77bfc2bf86e2947b58f792a19ca07dbfd3925df32949edf492174e927ebfafa11adf52c2cb1b6e4c18f01a68014e1ce87809b24091dc00f50ef1e93cf9225a69cb2a47b0339bad2a944a9256b576bc3e7731bcc9fa09140f9fd1f135f082df8595f449e41596bef0c1cad5897a4ef8ed67f57a14160f8bb219ac3d0371c51a8516dc2db1090100f6f2a914a06c6ea41b402b3118d7e4684d203b616c9410f6228f2d1b5cd4caf4b3568e426528b74a700cae0940a806a85f47c8ec3993017f446629bebd7aa50c239f3dc81eb271ee4c9cd1e550f92b774076b2b4cf4414c2f547b62bd7150f8cefdbc055ab3f8e9031e85776a0a9aeab3fedbbabc2ab45d3b6bd20e035ffa76ae387eb2ca39e9ec96f2601035cc8dd8dc84215330888898be4b58dd8241046599db277b15912c3ea08f74a02f345da47de060b88d79621c176547c3c5e360ad1865483c4404ba0b2e98ada1eabf502300e2965c07385f7b59014407f08e83f8c5d284f53a4bd38f32065239851b5977e82e9c1d2cc79428254aa381cef03264493134f53a996509ceed950524e14dd269ac0deca5180d2d1bb47adb91d698ecebf6f6fdc2f3b90df952864ce365fb96d65ead4dc8dfcf73ed3d7450f9f0a81c3d1d9d7685460b0d89f0fa39401e3b53da97e130f4d6837840c934d7c2693ff8151eecdde74f323a42d74eb5f505fd81e5f1cf2f7696209deb7d24b72793c56e94bf14dd8aee7ea5446467ede0888206e41ed2f10fa405851ce890f103cb606b06a05274836c6b3b77cd340f95affa4256433286bf0f200af87c5783d96a5a89f923ded76a81b1ed3b42659f1ce139c1f5dc23fbc0802212d7e35d957765b3c46b597ad7", + "x9": "3354d2c97c7a2e9c69346bb4b62d2f728df9c25d2fb4723a6887427295e649e93ec45f8fe63ffcc623d650a9a77e37296660f904e61b0a088c9a39d3352a4c8684576489b9cbed37bfc994c5e35a166234576b5dc380090797bf4b6dc5d4210a3d0898de832b6d79ada60d2d70565123be0d138caf08652ea96934e2de5e698e8befc7a7d8f2318fafca587d7f6bf7d1ddec52cd905e2607aacb7fb5c21e875a51284131a935dec2316acdbdc98105d968a56207dafd177c9b0702af550e1a32dca2badb8e8bca1a0c2c450df9b0fec962e76e1efecfff272cbe58c7a6c7731892f3ac668e2f4201e20d4fce41aa8f3dcf21127ee116339b4e74b34f37e7ee40cdc9921af3ddbfc80e5e9a9ecb047031e0d4fa6ee9de2e61a06e285fbfee7b0ed9173b3893bbf93f7f9bac35078fbd0f44ad0f1a41b9af63078caac8a62bc282463120a8424363caecd6ad3b1863903683287b341c1365dbfea432153a2fec844a52f728eee75571c6b0d80f38d763b7d97b0a16d01924d2384104e77eefb2717995d4b0d7165c6b619e664a61da0fe5c736d02429a2ddaa394d71048e9bdad125dc8a5daaf465562950a8987ee57122b7356f00cfd3fa9fd2593cf3609e74b20c451ac457d9d5abd492a8545f71f53d33f9465204d12ea73d84ef4076fae59e74fc1a329bf181d21192e509896444c0f920fd304203f1c942fe0628a26e3255b33d153d6407d78f8023b4ecd32c95be73113caeba012eceb66705ec791c6347fa51a86b0d863a69d5d3affd01818a11882d9933c111ba220804306b7c6f37faa820c9adb78c0a37e58a96ca4cec36cc1ac05115e5702d60a20896c0612175480e939fe1641195f3164cb6931656a7aeded10f0e25ede5c5f6c55ae8f35c1e6c", + "fp": "2065b1a5ce5d86671a5258542a34f6b2560b3559fc8f4ff111d309381c41148d528ad93b88fbe6dd154955b11cd2046d428c4762f779e7e589e01d553e1909ced2c0c2aa8d25a51a06191d4856919f66776727693012b59f75b0dce02bf8d9416b1131af750ae78811050ea4969f93612391bfa75c83478c3164aa7a0907e14c25327d58271bcee79ba46a4f1bea729fc2e96666b2825e0017a1c23b39793f89c74b4a771edd07bfb886c65c32d9ef137552893caf88b8f38fdcfa7eb5ebe95b17a4dce1b588a734ab5bd0628cba3831ac4854cc0fcb7451ad3fd86d0c0fd4faf4a75ed7000b7a85d52b6cccb7d118b7a04fce1c8e6090bb20eb83e8107b4f0958d644e0", + "f0": "a5bdf25b8ecefbb5908c20ce456197f74c98706669f711b9192f29d0a233e39af9705bab1336051cd5f76b55085c345383d6fc0b16757b199adf1ebb209ff07cb5f8eada5fe1d91afd54840445042ce5555c78421d1f6c5a0fba8cdc49ae0526476fd9bf19fd34718c81ec83d3b123fda3eac7f1f777edb86c3712c064452c820ecd6bad7e67073b0e1674412ee353d6644bc7f853aff1c8fbebf38ffd46e73477a305c15f8092b4eb1b3ea3aa6d9718dbdb9b2a9e4bd5afed3168bbda7b2ebc7aad9946f7e51debd9124ffcc84951931ed25cf65e5ecfc9a529d56b613d0c34bfaa23c4879a6eedfc84deb84f089eb781f5dafebdf30af33d9bf84f479c033d2959acfd98a9aedeba54f75c7aed6cbb4bb5fb47878e6c572661b348df4437369dbfe5846e23471847d1cc72c3f30dc9afcbf159a344c5576275bb6c6688ecf06b0d9e24bf6aefd7d7022a98085710344eef0ed6a02f20f6d5a5f52a19841d82f59e8b79ee83c6eada7dc2155bd8cdf3dcfd6657e9b9e34281d460d8ff64aa96088a3c1d777a6dd29bce7d6c82d31b65049a16c41e37f26b69073c6ae96588814c3f030056e2bb761fa8cd66947e95649f2f633ee67c9ee4489227686a715a4f33555f65c150c58486cdb682d58859ea16511902f31c4ea8c6380d649a52c8e7044844438126d26c21b9444db4bb85b9419b7d11773ab1157b9c121bb7c090754ff8f37ba4d65d497f022567ce2d7377ad7e16ce971934f140e202cb165d1c4dcbcc4fa9bd28b71f287c7fe00c0689e87f22ab6b243ea3cda9724589169d61c8cd797e49a50bd0e880a5b9bc882ca90dedeca83b66e994fd5b261e697781fc3281b55508da5ffca0c4add4f360d23d83032ba3bc92430bd89485e0bb34ffd45383116ff5ed473f8b5a12a054d8c54a6ba470a139a0d7cf9807e0a15d60995eb6608ce0d9750de16bb7eb0e77593d54e53af8220ad3aad540c99459a51291b5d4fc954b3c164c8edbc1ef3a031c2e01f4644fdb35b41ac4a6e5f038f9a69ed4c0f3f5e5a5a8a83796c3336cd3a9144c68cef962e96523d7e4d79abfbb686a22c1717c0e57312af8ff9de17f909d116332bad74159c961ad99e018f0ea766fe049", + "f1": "0b7f916b5b6f966a83b10fdcbbf428106327bbe24e8e68161174ee24d033d826ccacea900d535bb37a5399969461b66ac8e281412ec6cd787cbd81534968d4fdd6138d8aa10a59507fa9efe1b2ae72510269a146652cce78d1d063a5d399c9c0e814a48bccf6f9c61e727e319e469eef15a2d14256df258766bbc42db2b2eb61f2903236c733b33d62a3b673e874a2c1cbc529c4cb5189f48a6b5fde4782435632fcf72d92d57932e50cd5d9f11ca1ff50ea1ac1a89ce68969304361a12a14075ff4ce730c8ff6aaa26f6071e9e6814cc7f74fa423c2a2ed717d4672890056a0f675508c0690768e6fdf3c83b4ced89e04b946ada55be0701e0456b911b7def6c135139384c5085ba8f1960b598865acb9350f085d4912ef70d018a678b8b85f04762fc84f68c880ca73afd76c52b93f4717ab79f3e1159555b9e233548f4b4ce555497aacc49e2da2adc0ff5632aa7349b69435a47d41f0dbd6080d4e1d8509010c58149a77f4eca86ce004e667f5bb2e830666b02d874992d11f3f8785ed133da03c399428114d60369d58b8d7023d768938516539bb31f8eff50b9ff4a96bd0df8740579bff1361d509bb669fe71149f3293186e4ed77bb5dfc45dd911120b5e35048c3605fa69b45c69586d50f2cea528331ed5a50a046f59c52873cab008a4aff69e3745d8fd60b5d6a5ee62a11a6cf509d4e19a1d82bc7920eef67992b4d8a659a4ffe7205600b69d0808616e2eff0e5f4373c4525acbe0940e99b49e8f60af1769d5f444d62c76bfa1d05d0561d4b1270d1ad46c48f2d153b24ca5682d3318862849b68e615a7f61f05c708cad4582420c2ca6def2a3346b1cd080c4fff343c2f741caa36cdde7e4820a618322b4977fb833d320003db648868cea7fccf1d99f65b1b0dea67271730fe1bb7137bed75f1763b7f4f46698fefc8817bf8c040165ea6468eaae6fe850c62db6c4ff4bdca4f64421bebc57e362abbf13e14a7e3e4f92b49289c6545aad28c4cb6148429c313b0bc93da171abf2192ed44d9eac96f48b913537ba2ac132fa2f43894f89875c3ef7ca2ddaafbf05b783cd5e30b7f3cb7b2856178f2d77e070da76c3617412b5e11e025aae875da960e15850d", + "f2": "0a3e936cef080b2567d40de1599a57922442bb127117ef9204886e5d531de2f844d7fb3af13ebe22cf8b9edae03ba10d8d485a6979ba71d8a69f21a088f4c2ba4ee5e0a1fde5ab8c6749b3a279bf45c6f598d6b35bb0bbfd5c31860e241fb5bb3075ec1a4b0c7ba8463179ee37f342957da73a68cade881a566fd07e41f8b991561369c8870e01536a72cd92e4d49b4524978e1cec03cc29c8f6fb9e848e45024112392bc52fa239607fe36c3355b13670a7b4ecf5fbda1ded3d60c11f9fed45828ce7baa09d6f514e4999c46e1fbf24ff2336d7ce81765ca534053355d9085cda92459a2e6cea8475cee7afc6e523bf5776c79a7bb62fadc47cf284ddb309cd0dbd601d916d77b7f6d46b21d55ff5563d870f54ff455b799be803f88c594d1930037518b79b863a25fa47da876004af5674935a83e1a3e98d05254301c271b5845048acb35523ca4fa36959f4e1da6ae63a8c89f9fb4d4a0f7a05b03220822688368ac39fc0fb77c93a63e700964f2691f6d1754f82141788c1b7a62d1567f9f6bc715ed908761f1a81d7b917d124aec8f9c800a96c47f58f5e88ca4f8ce79ee2caa5371938e5dfe743735d43121f663ae862989632e8aa3ca8b0b03973a830c3d325ba6e1aea26c2a665263dae963a6c5c80f858194577e5b5dc7447db1112114f57c671f287c0f7557488f95b3657c1be224f7906a0a995cdfbb59de31b248089d8d9534cb816e6e0ff657e3be71f652630133aab1c5e734f9e1fa9ff882e540ce4e0833395fbb6386d254bf72883b607f357cdbd1ae71a4e2f2260bf2d93b6420ef5a55637aafc525239d40f46ac74b2b921fdc79ce30bc499bd51ab7ec22cdc94f24c65edeb843342a9d039c995d3431843e4ef1fce58072aff46cb5d8b2c93cd12818b1496efb8e033554f33931ac6e3d5da5003c1203eb639ef6a4a4e1604b97638b1c1ec7917b214974a5d0734cbe00e2ee636b2c12fcf679c9c3ffa620bfe845cf87a5a9cdcf0fdaadedf61082a3028bacefb6ce75cdcb2f4acc1d8c87f94e3847175ce7bf334faa488f8481d1a9ab7aa2640ad3650caf0e3ed025ac9be584b1473454e36d615a887484fc6dd40309a9d856e345636b6402eb7b814", + "f3": "96aa27278fdcd70731e67530e17d594cb3295b3870e7be92d8b482de7c3d27798b149b2520c37053ec7497a810043add5678f94e030f686c657ae401fb0dc2fb0a502d8983acc4eb87e02d774d70246fe7109fe00697ae63ec94e682c566236c95b761436c16fd3135b42654c3d866ed2193f0264991141308bd2daffafa9b25cff83750e1388ebb1dfdfd69e8b1e69ddc9042469e77b20b260b7a4b7b4b03f2653694e998e64feebdc2b9156eb67e4587d0e090b11bd7fb6557ab9df761fbe049e814cfa476dde2058417e5386f050104ffe52062db1d354ad171038dea25ac88f57b83118430fb19f36e1ca5512c25e118a1513a07cce52fea2e5b2dfc29e1bb0725377be79ffbe9b150591497ba68a13967283831671ef0ef6134bb9f7f4bdde44b3bb593fa51bf55a9f1b798bdde0a78f8b0667c9bb7759f8fbdff88bf7ebd022f1b6d14ef618efc088f034bde7048bb9e4d3dc37091a2232b1d86a5d344b833ffadc1f720ed43e3c42dc033e81097aac3898d3b47afd31219c65bf7f8ae2f81bad6046fb795bd7cb2951cee29c58861092616be91d8c51a802227bdde445f6014fe74ae2f601af8f6d09195e4ded77e7401434170de2cb25480a9a65ee63ae25f15d078eb04bd275239983bfec9b3d93f829129c394e1717783169fc7ec2af59e1bd3af6dfb532c0907179d77badded8be96362cda2b919aaec38a126dc2820466670b73d9806e22937ff339e7cdd245c13945ce57adbd352070d0dc5a2169d92c697db0d37c82fcf3e8cd2609d523d7e3a35fbca1fcbb2291c5a88456b6cd08eec16c05828fb2fcf13d55dad6745b3d7fe510a7b92a55e16e390a178a6c40133f9c7ef4a69fbbb5f5e5a55e019d58c4052e66caaa5f54e64ef9eeb3ca38a011f103390f356208ce7955a5986f5f2617b3c0a61569dcff4752bec26935561ae81807b1932dbea3ae81730556bcbb75515b4426f734eddba3571dd51e9f9c50b7b730f6148aca7f3f8d22af7c2d73518b1a92c00688bf086c548c0754e81f58d8296bebf2986f7c716742c9b91d2675240564756ed61d5f2da4cb9c8cc0e97b063b7d181f5517ee0f7ba353d28413df1768b5d6ba30ac81c9a4c71c61a91" + }, + "sessions": [ + { + "name": "live_two_origins", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e47a628e02ebd980ba5e0fb790cc0161560100a0fee49e5e06008063c8e69e5e06000202150068747470733a2f2f63646e2d612e6578616d706c65150068747470733a2f2f63646e2d622e6578616d706c6505003b1d27d3cccc8c522bd589f7657da1b113200a6573d8ef6d85bd8f05457c18513da4be0a28e087720382c4ea8b0fc399bc59ca6956a7478192c9ecb627df0fa47f3d4d03c5427528551f68f449df2dd3cc76ffd127dc4109a6f6a5adfd3266e5e69cd4f55b49b97be5787cff28bae1989b53f22ef9a0d4a15a56cb1cf6748305fdaf2e91106cc9f6b73e327d1dacda59456eefa80240879213746b940f6e20e302006488bdc79885c458643cc4699f4bf7730c4d7ce9ba3bf335e23f4e49099b0176ca8c47fca83b70baad1c63f9f2f1bbc9c00deac9eebaa3e843beeef683cd327006000000e80300008813000010270170170000a00f000010270310270000a00f0000102701b0360000a00f0000b80b0350460000d0070000102701fc530000401f00001027018403000001000000dc05000000000000010000008403000000000000a7b48adfdc740a41c820db3bc8d7f265da8db0fdc4f997cb3c3ce431c62a40d3ebdbad7c5b33ede9054d283e7e4067186c394988d7d0a332aee09e1272a58f950203d012130000000000030400000000000000000000004fcc5e1422174e83bdcd5a8dde54ad92025cf577f3d10fbd43868ecd0917837eccf240106edbe4f9561aea4281f70dfb6ea5d5712e985d425e73dabca04d2000", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e400a0fee49e5e0600803697e59e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000006202000000000000a4dcb70d985fd047b2ad36239f5fdcb85e1c414f01eb1ed03fbf59bb2b27efd42d2a5a96d82b5511203846c5676b3989652bcdf003f8e8c138d804e71513de20cd3dc6696ec92bbb2caff4d96c6460940f84ec2069f168b6eba073ac98007527f420acef2656fb9956756c7e524fd63e5a10613b4f3ad499df570c3439a4bd0e", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000668ab7156c0cdafd3c825e6a0509e61906d7963db2730b433a01ec0836597c6700000000000000003601000000000000" + ] + }, + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e4803697e59e5e060000cd2fe69e5e0600fdaf2e91106cc9f6b73e327d1dacda59456eefa80240879213746b940f6e20e301000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000004001000000000000acc4721b823695931be9ac381900d99e3654fb960d5c2dec49199d562c02f248986296bbe1bc46edf918a1489014010c5b4ea6a086ad82da076a735e8924da90a75c5dd65ecd72f25b6bf7334eaf6fa1ad18f2a0ac8588fbb094502ccae81dff5434971cc7217670ff93281372bb7cc3944886694b09e9203b4045778075240e", + "items": [ + "45bc67b48205766a0dcb1529580caa95566229ab2b75ab76e306149f187b0e8f00000000000000004001000000000000" + ] + }, + { + "record": "57444131010102aa9823a88a3500d91e003a07aa44da6e732282285eb660753f886af8317618179c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e400a0fee49e5e0600803697e59e5e060000000000000000000000000000000000000000000000000000000000000000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000cc100000000000005b946688e15c0dc062fff6403ca57dfeff256c28bf851a1c6cb0a276892be1e671a13a49b84a40bfb078a0674eb84317572a927c8c72819a4c9271d985970095b11a7462786029d1f1eaf541c3fbb29de5185097b42897287d1eeff8e91c23069f2ea26fee1b198ec93c94b0c6a71ce9f6a269d1052454659e797664856dd709", + "items": [ + "1cc1bee91e1678d1d0cc88e35feb520a88814da36bf757e76929e41ab22b81e70000000000000000c80000000000000058c5a889717aa9f19a18257a79a4a5e1fe5ffcd3bd764110c48caa99bb25f32600000000000000008403000000000000b46f33eda8230cc1d862f234b52230f4f28a2811bfb0551b2b7180382051051a00000000000000008403000000000000ea144235442623eae241fb82d66d987db9a712af44764a88ebe72a39b6d875be00000000000000008403000000000000e60f1e58f1280583b829596ae6b30238dc1d4fe1b746968ed4760a7afe7a79810000000000000000bc02000000000000a337ff2ce9c5dd88f28e86626a6949ebb451c7b834d6efe245fde68f9ebaf8f60000000000000000bc02000000000000" + ] + }, + { + "record": "57444131010102aa9823a88a3500d91e003a07aa44da6e732282285eb660753f886af8317618179c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e4803697e59e5e060000cd2fe69e5e06007f3d4d03c5427528551f68f449df2dd3cc76ffd127dc4109a6f6a5adfd3266e50100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000800c0000000000009449879d18725f52b9da1fad72a4e0a43295e12928f6071d3ade475bd8bd4273283707c4176716cf235199da2437611ea72b40b9ded8a8abfb3430608c5a29b950263fe9aa70b5b3075c20a08ed884b3c153ef3ccdfcde934d224e621657f30ecff7750b6194ccf0750420d0f6d55c6dccdba4b5456a0f8dbf8fc8581a613a0d", + "items": [ + "4f92db4b2f1126bbea570457a2bb4af5d4539ac78dadd09c1a0b7be8423907f6000000000000000084030000000000005a3329c2a4bbba26ba88806b2e6241497a8e5c327227698ef4ea28ca698248ca0000000000000000bc02000000000000e44b87886191603d7af3d1d7ac0cf8b1e919347c9adb95e108159ec7aa64f8350000000000000000bc020000000000000433693c3a9563f9d91ed5834b3603a57fcffeabd7c5515d75d7436fd649ed3300000000000000008403000000000000" + ] + }, + { + "record": "57444131010102aa9823a88a3500d91e003a07aa44da6e732282285eb660753f886af8317618179c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e400cd2fe69e5e06008063c8e69e5e06003da4be0a28e087720382c4ea8b0fc399bc59ca6956a7478192c9ecb627df0fa401000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000008403000000000000c4219ed8ccd91ccb9e86fc1493094ef7580b706227fe6e38b6c68483773817cf73f823ed1659878991de0529d3c0b0f8df6fd11910793dc46583016e46339ea6a6b1814742c79a6c3f0aca0378778a80152218d5b1a25fc5b270e2b95c49b1b979a589ff49136c29ac05a91b9de4871c1c6a3336a04793fd3c20fc76b9bd1b05", + "items": [ + "17a19fb27a17ab820ca3c1a28f780eff76e0aee401a9525db42f2e63109a23df00000000000000008403000000000000" + ] + } + ], + "claims": [ + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3a744df636e2eb68b252d21d9efdb3e1a48746c61612d3bf5729910c0420dbbe97a628e02ebd980ba5e0fb790cc01615600e0b5a429b349be39beb139fc798d6372dcd7acf84b54db1f1bb38deba0136ed89c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e403ffffffff0200e60f1e58f1280583b829596ae6b30238dc1d4fe1b746968ed4760a7afe7a7981a337ff2ce9c5dd88f28e86626a6949ebb451c7b834d6efe245fde68f9ebaf8f6010000000000a00f000010270100001f5b94cc0e845508d2728c57a1f00fee43d393e1cec1bf732016a5558c7e989e651b294dcd2882935bb50d61e858b77132f65f2d7e14934dd596bb2f22723e05", + "marker": "#EXT-X-DATERANGE:ID=\"ad-1\",START-DATE=\"2026-10-25T00:00:06.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "e60f1e58f1280583b829596ae6b30238dc1d4fe1b746968ed4760a7afe7a7981", + "duration_ms": 2000 + }, + { + "content_hash": "a337ff2ce9c5dd88f28e86626a6949ebb451c7b834d6efe245fde68f9ebaf8f6", + "duration_ms": 2000 + } + ] + }, + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa363323e5827fe2af15ba4e106c527f002329c0ad3149835c43792c8f5672ebddd7a628e02ebd980ba5e0fb790cc0161560063e9f2c46288ffe99caf952f59dc5900efe0b3d7d1de5e9acf92380f7793fa079c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e403ffffffff02005a3329c2a4bbba26ba88806b2e6241497a8e5c327227698ef4ea28ca698248cae44b87886191603d7af3d1d7ac0cf8b1e919347c9adb95e108159ec7aa64f835010000000000a00f0000b80b01000012d6cafdf3ce998330ebf16e02a9fc17bce91151ffd23e9dbe07d0d41b2034a0e920062ec1ab7e210d459e54c8537d04bbaad31549cb4bd63f28c9e17d6c7401", + "marker": "#EXT-X-DATERANGE:ID=\"ad-2\",START-DATE=\"2026-10-25T00:00:14.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "5a3329c2a4bbba26ba88806b2e6241497a8e5c327227698ef4ea28ca698248ca", + "duration_ms": 2000 + }, + { + "content_hash": "e44b87886191603d7af3d1d7ac0cf8b1e919347c9adb95e108159ec7aa64f835", + "duration_ms": 2000 + } + ] + } + ], + "log": "[[{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":0,\"ts_ms\":1792886400100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":1,\"ts_ms\":1792886400200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":2,\"ts_ms\":1792886400400,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"1cc1bee91e1678d1d0cc88e35feb520a88814da36bf757e76929e41ab22b81e7\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":3,\"ts_ms\":1792886400600,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"58c5a889717aa9f19a18257a79a4a5e1fe5ffcd3bd764110c48caa99bb25f326\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":4,\"ts_ms\":1792886401000,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":5,\"ts_ms\":1792886402400,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"b46f33eda8230cc1d862f234b52230f4f28a2811bfb0551b2b7180382051051a\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":6,\"ts_ms\":1792886403000,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"b46f33eda8230cc1d862f234b52230f4f28a2811bfb0551b2b7180382051051a\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":7,\"ts_ms\":1792886404400,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"668ab7156c0cdafd3c825e6a0509e61906d7963db2730b433a01ec0836597c67\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":8,\"ts_ms\":1792886404500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"ea144235442623eae241fb82d66d987db9a712af44764a88ebe72a39b6d875be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":9,\"ts_ms\":1792886406500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"e60f1e58f1280583b829596ae6b30238dc1d4fe1b746968ed4760a7afe7a7981\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":10,\"ts_ms\":1792886408500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"a337ff2ce9c5dd88f28e86626a6949ebb451c7b834d6efe245fde68f9ebaf8f6\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":11,\"ts_ms\":1792886410500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"4f92db4b2f1126bbea570457a2bb4af5d4539ac78dadd09c1a0b7be8423907f6\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":12,\"ts_ms\":1792886410600,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"45bc67b48205766a0dcb1529580caa95566229ab2b75ab76e306149f187b0e8f\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":13,\"ts_ms\":1792886412500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"5a3329c2a4bbba26ba88806b2e6241497a8e5c327227698ef4ea28ca698248ca\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":14,\"ts_ms\":1792886414500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"e44b87886191603d7af3d1d7ac0cf8b1e919347c9adb95e108159ec7aa64f835\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":15,\"ts_ms\":1792886416500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"0433693c3a9563f9d91ed5834b3603a57fcffeabd7c5515d75d7436fd649ed33\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":16,\"ts_ms\":1792886420000,\"event\":\"stall-start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":17,\"ts_ms\":1792886421500,\"event\":\"stall-end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":18,\"ts_ms\":1792886422000,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"17a19fb27a17ab820ca3c1a28f780eff76e0aee401a9525db42f2e63109a23df\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":19,\"ts_ms\":1792886429500,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": "[{\"content_hash\":\"58c5a889717aa9f19a18257a79a4a5e1fe5ffcd3bd764110c48caa99bb25f326\",\"cmsd_static\":\"ot=v,sf=h,st=l\",\"cmsd_dynamic\":null},{\"content_hash\":\"b46f33eda8230cc1d862f234b52230f4f28a2811bfb0551b2b7180382051051a\",\"cmsd_static\":null,\"cmsd_dynamic\":\"\\\"cdn-b\\\";etp=1200;rtt=40\"}]", + "attachments": [] + }, + "script": { + "key": 10, + "window_us": 10000000, + "received": [ + { + "at": 1792886400200000, + "origin": "https://cdn-a.example", + "object": "p0", + "media": false + }, + { + "at": 1792886400400000, + "origin": "https://cdn-b.example", + "object": "init", + "media": true + }, + { + "at": 1792886400600000, + "origin": "https://cdn-b.example", + "object": "s0", + "media": true + }, + { + "at": 1792886402400000, + "origin": "https://cdn-b.example", + "object": "s1", + "media": true + }, + { + "at": 1792886403000000, + "origin": "https://cdn-b.example", + "object": "s1", + "media": true + }, + { + "at": 1792886404400000, + "origin": "https://cdn-a.example", + "object": "p1", + "media": false + }, + { + "at": 1792886404500000, + "origin": "https://cdn-b.example", + "object": "s2", + "media": true + }, + { + "at": 1792886406500000, + "origin": "https://cdn-b.example", + "object": "a0", + "media": true + }, + { + "at": 1792886408500000, + "origin": "https://cdn-b.example", + "object": "a1", + "media": true + }, + { + "at": 1792886410500000, + "origin": "https://cdn-b.example", + "object": "s3", + "media": true + }, + { + "at": 1792886410600000, + "origin": "https://cdn-a.example", + "object": "p2", + "media": false + }, + { + "at": 1792886412500000, + "origin": "https://cdn-b.example", + "object": "a2", + "media": true + }, + { + "at": 1792886414500000, + "origin": "https://cdn-b.example", + "object": "a3", + "media": true + }, + { + "at": 1792886416500000, + "origin": "https://cdn-b.example", + "object": "s4", + "media": true + }, + { + "at": 1792886422000000, + "origin": "https://cdn-b.example", + "object": "s5", + "media": true + } + ], + "events": [ + { + "at": 1792886400100000, + "event": "start" + }, + { + "at": 1792886401000000, + "event": "present" + }, + { + "at": 1792886420000000, + "event": "stall-start" + }, + { + "at": 1792886421500000, + "event": "stall-end" + }, + { + "at": 1792886429500000, + "event": "end" + } + ] + }, + "cmcd": "e=ps,sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=0,st=l,sta=s,ts=1792886400100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=1,st=l,ts=1792886400200,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"1cc1bee91e1678d1d0cc88e35feb520a88814da36bf757e76929e41ab22b81e7\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=2,st=l,ts=1792886400400,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"58c5a889717aa9f19a18257a79a4a5e1fe5ffcd3bd764110c48caa99bb25f326\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=3,st=l,ts=1792886400600,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=4,st=l,sta=p,ts=1792886401000,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"b46f33eda8230cc1d862f234b52230f4f28a2811bfb0551b2b7180382051051a\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=5,st=l,ts=1792886402400,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"b46f33eda8230cc1d862f234b52230f4f28a2811bfb0551b2b7180382051051a\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=6,st=l,ts=1792886403000,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"668ab7156c0cdafd3c825e6a0509e61906d7963db2730b433a01ec0836597c67\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=7,st=l,ts=1792886404400,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"ea144235442623eae241fb82d66d987db9a712af44764a88ebe72a39b6d875be\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=8,st=l,ts=1792886404500,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"e60f1e58f1280583b829596ae6b30238dc1d4fe1b746968ed4760a7afe7a7981\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=9,st=l,ts=1792886406500,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"a337ff2ce9c5dd88f28e86626a6949ebb451c7b834d6efe245fde68f9ebaf8f6\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=10,st=l,ts=1792886408500,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"4f92db4b2f1126bbea570457a2bb4af5d4539ac78dadd09c1a0b7be8423907f6\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=11,st=l,ts=1792886410500,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"45bc67b48205766a0dcb1529580caa95566229ab2b75ab76e306149f187b0e8f\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=12,st=l,ts=1792886410600,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"5a3329c2a4bbba26ba88806b2e6241497a8e5c327227698ef4ea28ca698248ca\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=13,st=l,ts=1792886412500,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"e44b87886191603d7af3d1d7ac0cf8b1e919347c9adb95e108159ec7aa64f835\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=14,st=l,ts=1792886414500,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"0433693c3a9563f9d91ed5834b3603a57fcffeabd7c5515d75d7436fd649ed33\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=15,st=l,ts=1792886416500,v=2\ne=ps,sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=16,st=l,sta=r,ts=1792886420000,v=2\ne=ps,sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=17,st=l,sta=p,ts=1792886421500,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"17a19fb27a17ab820ca3c1a28f780eff76e0aee401a9525db42f2e63109a23df\",sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=18,st=l,ts=1792886422000,v=2\ne=ps,sf=h,sid=\"7a628e02ebd980ba5e0fb790cc016156\",sn=19,st=l,sta=e,ts=1792886429500,v=2", + "outcome": { + "outcome": "accept", + "digest": "bcf64a2740b66513c4759f7771da54b3c15a009fc5f9f68aff68ce55caadf78a", + "channel": "7fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3", + "presentation": "live", + "start": 1792886400000000, + "end": 1792886430000000, + "ended": "user", + "presented_ms": 27000, + "viewable_ms": 23000, + "audible_ms": 27000, + "ad_ms": 8000, + "delivery": [ + { + "origin": "https://cdn-a.example", + "class": "ancillary", + "objects": 3, + "bytes": 930 + }, + { + "origin": "https://cdn-b.example", + "class": "live", + "objects": 11, + "bytes": 8400 + } + ], + "repeats": { + "objects": 1, + "bytes": 900 + }, + "startup_ms": 900, + "stalls": 1, + "stall_ms": 1500, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 1250000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [ + { + "verdict": "presented", + "quartile": 5, + "run_ms": 0, + "viewable_ms": 0, + "viewable": false + }, + { + "verdict": "presented", + "quartile": 5, + "run_ms": 4000, + "viewable_ms": 4000, + "viewable": true + } + ], + "notes": 2, + "attachments": 0 + } + }, + { + "name": "on_demand_hidden", + "bundle": { + "v": 1, + "session": "5741533101c54a2ce987d8b6f5085ed3aaedc11cf5ac94033b937abbebbdafab9bcd8c7540775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2b08a1b307aef49533c055b3bfa324fbc6bc9aa0280a4d594265b334b7ad2f090f17ebef6b76617e3688826da8f464311020081f4ea9e5e060000ae25ec9e5e06000301180068747470733a2f2f766f642e6578616d706c653a383434330100ee574b6a92f59eb783428c83ab04d5bf579eb3a59663647d164c2a6d8bc43cc900000200000020030000501400001027017017000010270000000001ee0200000000000000000000000000000000000000000000000000001ed7881d211c44fda3d8265bd27f076c0ac78708ab94853f59a73308c882025500000000000000000000000000000000000000000000000000000000000000000203c0c62d0000000000040000c350000000000112a88002040000000000000000000100881aeb7444210b24241784f2b56ceea5d9ccc2901e88bc8e9649aa93ccf10398d856cd7664563fadbc2a02798383d4aeab0bc2853437041f577b5c6ced0c5b2af26c47a8c7bacb5cacdbb10cd464cf52f031da0b525577bb9e8fd6fe992e2a09", + "acks": [ + { + "record": "5744413101010298eaf8c19146061bc02557e72dc6ab1083eb1f492a709eb444f521dcb6834238b08a1b307aef49533c055b3bfa324fbc6bc9aa0280a4d594265b334b7ad2f0900081f4ea9e5e060080178deb9e5e06000000000000000000000000000000000000000000000000000000000000000000020002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000074130000000000009388ee7e596d2d0033e10adeb1eb096534325ab0e4bcfdae8b5834adb37e995d98d3eed000dbc6044418de7b614810ff89fd9cc9c410142d9652bd30c9348f910400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000001801000000000000c505ea4e585ea68728d666bd48c871666a0afb5cd4d5a485f629eb594275d3291aa83e857c61a656350a405d48bbc78803ba2f829d48ebea7833483051dff578ad30795f813b4e02346ee4de04dc060b9f2ce8ebac7f90fada5c7e9bdcf2c2325d3c90a4a7aeb858daf7f49f0e66d5762f65d43c80d466aae540342290085201", + "items": [ + "0cccfb45ade1862370a2a3a7e731c0e38a9794e74094bfa8e8a739b657f5ba470000000000000000b4000000000000007813ffce6e9e9639ce372c18857f3b4595b3752c4a65c11b1f240e27dfe94cba0000000000000000b004000000000000476ef077c8bb7f11ecb1fcb7775c3955685bd66be22e765c0e248c0dba399d480000000000000000b0040000000000009ed88395feeff2c3efd8ba135cd4624ff651b21d8adf951879bc1e3c70ff4f750000000000000000b004000000000000e4b3fb4ae8b7ae981e715bb1a2dc56b8a426f6209c4cb192b8ae5883c389a7c00000000000000000b004000000000000", + "c5b1c642e6e8632fcd90d8879206fb155f406cc718adbfa0fdcbbe494d9d174e00000000000000001801000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"f17ebef6b76617e3688826da8f464311\",\"sn\":0,\"ts_ms\":1792886500050,\"event\":\"start\",\"cid\":null,\"st\":\"v\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"f17ebef6b76617e3688826da8f464311\",\"sn\":1,\"ts_ms\":1792886500100,\"event\":\"response\",\"cid\":null,\"st\":\"v\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5b1c642e6e8632fcd90d8879206fb155f406cc718adbfa0fdcbbe494d9d174e\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"f17ebef6b76617e3688826da8f464311\",\"sn\":2,\"ts_ms\":1792886500300,\"event\":\"response\",\"cid\":null,\"st\":\"v\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"0cccfb45ade1862370a2a3a7e731c0e38a9794e74094bfa8e8a739b657f5ba47\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"f17ebef6b76617e3688826da8f464311\",\"sn\":3,\"ts_ms\":1792886500500,\"event\":\"response\",\"cid\":null,\"st\":\"v\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"7813ffce6e9e9639ce372c18857f3b4595b3752c4a65c11b1f240e27dfe94cba\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"f17ebef6b76617e3688826da8f464311\",\"sn\":4,\"ts_ms\":1792886500800,\"event\":\"present\",\"cid\":null,\"st\":\"v\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"f17ebef6b76617e3688826da8f464311\",\"sn\":5,\"ts_ms\":1792886502500,\"event\":\"response\",\"cid\":null,\"st\":\"v\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"476ef077c8bb7f11ecb1fcb7775c3955685bd66be22e765c0e248c0dba399d48\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"f17ebef6b76617e3688826da8f464311\",\"sn\":6,\"ts_ms\":1792886504500,\"event\":\"response\",\"cid\":null,\"st\":\"v\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"9ed88395feeff2c3efd8ba135cd4624ff651b21d8adf951879bc1e3c70ff4f75\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"f17ebef6b76617e3688826da8f464311\",\"sn\":7,\"ts_ms\":1792886506500,\"event\":\"response\",\"cid\":null,\"st\":\"v\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"e4b3fb4ae8b7ae981e715bb1a2dc56b8a426f6209c4cb192b8ae5883c389a7c0\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"f17ebef6b76617e3688826da8f464311\",\"sn\":8,\"ts_ms\":1792886518000,\"event\":\"end\",\"cid\":null,\"st\":\"v\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [ + { + "name": "wai.live.termination", + "record": "3354d2c97c7a2e9c69346bb4b62d2f728df9c25d2fb4723a6887427295e649e93ec45f8fe63ffcc623d650a9a77e37296660f904e61b0a088c9a39d3352a4c8684576489b9cbed37bfc994c5e35a166234576b5dc380090797bf4b6dc5d4210a3d0898de832b6d79ada60d2d70565123be0d138caf08652ea96934e2de5e698e8befc7a7d8f2318fafca587d7f6bf7d1ddec52cd905e2607aacb7fb5c21e875a51284131a935dec2316acdbdc98105d968a56207dafd177c9b0702af550e1a32dca2badb8e8bca1a0c2c450df9b0fec962e76e1efecfff272cbe58c7a6c7731892f3ac668e2f4201e20d4fce41aa8f3dcf21127ee116339b4e74b34f37e7ee40cdc9921af3ddbfc80e5e9a9ecb047031e0d4fa6ee9de2e61a06e285fbfee7b0ed9173b3893bbf93f7f9bac35078fbd0f44ad0f1a41b9af63078caac8a62bc282463120a8424363caecd6ad3b1863903683287b341c1365dbfea432153a2fec844a52f728eee75571c6b0d80f38d763b7d97b0a16d01924d2384104e77eefb2717995d4b0d7165c6b619e664a61da0fe5c736d02429a2ddaa394d71048e9bdad125dc8a5daaf465562950a8987ee57122b7356f00cfd3fa9fd2593cf3609e74b20c451ac457d9d5abd492a8545f71f53d33f9465204d12ea73d84ef4076fae59e74fc1a329bf181d21192e509896444c0f920fd304203f1c942fe0628a26e3255b33d153d6407d78f8023b4ecd32c95be73113caeba012eceb66705ec791c6347fa51a86b0d863a69d5d3affd01818a11882d9933c111ba220804306b7c6f37faa820c9adb78c0a37e58a96ca4cec36cc1ac05115e5702d60a20896c0612175480e939fe1641195f3164cb6931656a7aeded10f0e25ede5c5f6c55ae8f35c1e6c" + } + ] + }, + "cmcd": "e=ps,sf=h,sid=\"f17ebef6b76617e3688826da8f464311\",sn=0,st=v,sta=s,ts=1792886500050,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5b1c642e6e8632fcd90d8879206fb155f406cc718adbfa0fdcbbe494d9d174e\",sf=h,sid=\"f17ebef6b76617e3688826da8f464311\",sn=1,st=v,ts=1792886500100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"0cccfb45ade1862370a2a3a7e731c0e38a9794e74094bfa8e8a739b657f5ba47\",sf=h,sid=\"f17ebef6b76617e3688826da8f464311\",sn=2,st=v,ts=1792886500300,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"7813ffce6e9e9639ce372c18857f3b4595b3752c4a65c11b1f240e27dfe94cba\",sf=h,sid=\"f17ebef6b76617e3688826da8f464311\",sn=3,st=v,ts=1792886500500,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"f17ebef6b76617e3688826da8f464311\",sn=4,st=v,sta=p,ts=1792886500800,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"476ef077c8bb7f11ecb1fcb7775c3955685bd66be22e765c0e248c0dba399d48\",sf=h,sid=\"f17ebef6b76617e3688826da8f464311\",sn=5,st=v,ts=1792886502500,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"9ed88395feeff2c3efd8ba135cd4624ff651b21d8adf951879bc1e3c70ff4f75\",sf=h,sid=\"f17ebef6b76617e3688826da8f464311\",sn=6,st=v,ts=1792886504500,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"e4b3fb4ae8b7ae981e715bb1a2dc56b8a426f6209c4cb192b8ae5883c389a7c0\",sf=h,sid=\"f17ebef6b76617e3688826da8f464311\",sn=7,st=v,ts=1792886506500,v=2\ne=ps,sf=h,sid=\"f17ebef6b76617e3688826da8f464311\",sn=8,st=v,sta=e,ts=1792886518000,v=2", + "outcome": { + "outcome": "accept", + "digest": "8a4397318745bce492ced97880f9eafe1ee4dd5ae1be9fc8dd8cf1a97309f408", + "channel": "c54a2ce987d8b6f5085ed3aaedc11cf5ac94033b937abbebbdafab9bcd8c7540", + "presentation": "on_demand", + "start": 1792886500000000, + "end": 1792886520000000, + "ended": "hidden", + "presented_ms": 15200, + "viewable_ms": 5200, + "audible_ms": 15200, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://vod.example:8443", + "class": "fetch", + "objects": 5, + "bytes": 4980 + }, + { + "origin": "https://vod.example:8443", + "class": "ancillary", + "objects": 1, + "bytes": 280 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 750, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "OnChipCounter", + "joules_micro": 3000000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 1 + } + }, + { + "name": "nothing_presented", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000008712ce0afd2b0ea1c2d2c36a40722b3423da9440a86d0c308012ca2b30dbc2ec5f429cd418f24c72bf00cbb4a92dabdcbab447675a7b77294bf83eb7e3b21c0a", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"ac3053630eb9973bb5d0544e657dc170\",sn=0,st=l,sta=s,ts=1792886600100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",sf=h,sid=\"ac3053630eb9973bb5d0544e657dc170\",sn=1,st=l,ts=1792886600200,v=2\ne=ps,sf=h,sid=\"ac3053630eb9973bb5d0544e657dc170\",sn=2,st=l,sta=e,ts=1792886604900,v=2", + "outcome": { + "outcome": "accept", + "digest": "1ceafda75b6b806fcbc71a8b54d44fd772565b110cb68c3c67b178ecff6852ac", + "channel": "7fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3", + "presentation": "live", + "start": 1792886600000000, + "end": 1792886605000000, + "ended": "error", + "presented_ms": 0, + "viewable_ms": 0, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://cdn-a.example", + "class": "ancillary", + "objects": 1, + "bytes": 300 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": null, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "claim_verdicts", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e268a36c7d469ba51b471330c77fa1e7c4b8d6c08ebe3370eaca222cf0ab94866000b4d5a1d39d4e0a02d4a74428101c15010043e0f69e5e06008006aaf89e5e06000101150068747470733a2f2f63646e2d622e6578616d706c6502009742d9b43cedc1645cdc41a59d2fdd0e161d2f4c62fc55b2c95e6dba07cce3a2dc9ce445e0668ed6213551323270e4adf4846ff2c4daf668292dc39fa6ec7ea909000b731a4c0d6d59855817f624b980e403627e99aa0595cf0438ad4d8e08def95e286abb9342b594499c44a13263dd5c1a95945891cc4e26455ca4d14409553b11718525307ed43d2dc16251752ac33fbc3be2794912d62f8a110e8fc2e51bd950725727d06df6d7de7dce24ee39d5e8693d5588ca869b16560b0daf5e52aa689f7deb00ebef3d0556f1b19ce45677871ce508354a0c7476b2f5042c018009f78b8aaf384a7da286e2406ce9d3c4d46363c1d8dd0b771e6253a36e4dfc56cb9440a25c204a8c20c78063c38de974806c16da54697dc059afb4e9dcb1659011919ebb8e5c43fe617e4ffa61095feda630bf6820c84db58a46df3fb2181763b15bb8eaed7e45dad839f216e27d151ac4d69752d0d65b7d23f4812fb143238031e37f03000000e803000010270000102701f82a0000a00f0000102703983a0000b0360000102701f4010000000000000000000000000000000000000000000000000000a3040c9e890b5d4037b98991d592fa3173606fcbf559b8a2de31dfeda223ef3b000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000005d8da02b218e7ca4d4f2dd341ed7d710ddb1ac3eac6c0f428df96fdfa802e4f049c29a2d2a25e79ded4f923fef0fecf92b9e0e0694ff2972a98c7c3ae157fc0c", + "acks": [ + { + "record": "57444131010102aa9823a88a3500d91e003a07aa44da6e732282285eb660753f886af83176181768a36c7d469ba51b471330c77fa1e7c4b8d6c08ebe3370eaca222cf0ab9486600043e0f69e5e060080d978f79e5e0600000000000000000000000000000000000000000000000000000000000000000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000008408000000000000af0a268bf69ec0fff8516689509b52a130774b1d5905bd76f1a180beb7b5dbfadbdd2a5877ed0f163010af6a91dbe71fbb707c40e4cf61f4334730221d7092122b9328f5329e7fcda542d810c6288ea62a8591ac88b0de2df2ac1d60d09609531405d5dedd65a51beb75eb0e8d1fcee16e59414791e51527e3e069147cfadf09", + "items": [ + "ebd52a56dc1cf49e6ffa803b30d3379911e371099043d74f8fa15da4ae4c22040000000000000000840300000000000005c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650c00000000000000008002000000000000c346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe200000000000000008002000000000000" + ] + }, + { + "record": "57444131010102aa9823a88a3500d91e003a07aa44da6e732282285eb660753f886af83176181768a36c7d469ba51b471330c77fa1e7c4b8d6c08ebe3370eaca222cf0ab94866080d978f79e5e0600007011f89e5e0600dc9ce445e0668ed6213551323270e4adf4846ff2c4daf668292dc39fa6ec7ea901000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000005000000000000b40f43913343c674d95df25f61285c47634665869e22267964d6ab3fd974138c1b69943522f9a0ed54d400b5b8cf4e321a765890d0f70dd97631306cd179a149e3120d333397ad5da3c9de031229b348e42733e7fd8da254975e34640439796066653ce0c05fa6e21dfba4c5a7b5bd289d16fe0584fe7100b4fae8d0a5da3602", + "items": [ + "58ea245162376bd39eda1f446844b74c7ffa5c1ffff50719f77aaf96e8405b5c00000000000000008002000000000000833a45ae63041e51f91cd32121ea566a5dedb04db771ddd082da46905ba71ef100000000000000008002000000000000" + ] + } + ], + "claims": [ + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3e1a7319dfdd8051a8ab213ea73f5a190565dfe4c34d013b0948eebb0283ec54200b4d5a1d39d4e0a02d4a74428101c15008a6d2c683403130cef7e2f8246a0d187f041ab3d97b4dd8b4b98aa157def0fdf68a36c7d469ba51b471330c77fa1e7c4b8d6c08ebe3370eaca222cf0ab94866003ffffffff020005c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650cc346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2010000000000a00f0000102701000003c72648a9d566cea6e99629b205829536ad650aca752c84b1eee41dbdcf9a6a1d8f6ee1022e396f731acb82c4e7be767b2ca65ddcf694c6cb201c606339f009", + "marker": "#EXT-X-DATERANGE:ID=\"ad-x\",START-DATE=\"2026-10-25T00:05:11.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "05c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650c", + "duration_ms": 2000 + }, + { + "content_hash": "c346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2", + "duration_ms": 2000 + } + ] + }, + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3e1a7319dfdd8051a8ab213ea73f5a190565dfe4c34d013b0948eebb0283ec54200b4d5a1d39d4e0a02d4a74428101c15008a6d2c683403130cef7e2f8246a0d187f041ab3d97b4dd8b4b98aa157def0fdf68a36c7d469ba51b471330c77fa1e7c4b8d6c08ebe3370eaca222cf0ab94866003ffffffff010005c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650c010000000000d00700001027010000f4b1c2b96df8e033c6e7ec03921fadf51716e87eb7f8351a1244671c4b875aacc8133cd01cf66ba9382ff23d200579062c6b4b52e4636b3d892f5c80fccd1b03", + "marker": "#EXT-X-DATERANGE:ID=\"ad-x\",START-DATE=\"2026-10-25T00:05:11.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "05c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650c", + "duration_ms": 2000 + }, + { + "content_hash": "c346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2", + "duration_ms": 2000 + } + ] + }, + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa30a00b0be977d9a61b43c3a85e9cfbba987029b469aa9cee661c44f812834f6b400b4d5a1d39d4e0a02d4a74428101c15008a6d2c683403130cef7e2f8246a0d187f041ab3d97b4dd8b4b98aa157def0fdf68a36c7d469ba51b471330c77fa1e7c4b8d6c08ebe3370eaca222cf0ab94866009ffffffff020005c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650cc346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2010000000000a00f0000102701000013dccb212d57992a16b8d9d98f8e5c2cacbacb35158ecfb942bcaad670d49141eda5186b51259144394e393c27586d54ca5bf18fa1f908d3f0eb6cea4a59f209", + "marker": "#EXT-X-DATERANGE:ID=\"ad-m\",START-DATE=\"2026-10-25T00:05:12.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "05c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650c", + "duration_ms": 2000 + }, + { + "content_hash": "c346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2", + "duration_ms": 2000 + } + ] + }, + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa39cbb07e3dc0fd0c3153ab0072b1d2e93caca9307b12d284098902a645cd3ef1500b4d5a1d39d4e0a02d4a74428101c15008a6d2c683403130cef7e2f8246a0d187f041ab3d97b4dd8b4b98aa157def0fdf68a36c7d469ba51b471330c77fa1e7c4b8d6c08ebe3370eaca222cf0ab94866003ffffffff020005c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650cc346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2010000000000a00f00001027010000c8f5e8afdfd23b40201642ef3062da254094ef70c6be7f5d9567427c7be37e8273cdcb26cd3c670c89d6c875d23e3800bf11df1bdc9be361d97cb423d50d2b0b", + "marker": "#EXT-X-DATERANGE:ID=\"ad-s\",START-DATE=\"2026-10-25T00:05:13.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "05c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650c", + "duration_ms": 2000 + }, + { + "content_hash": "c346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2", + "duration_ms": 2000 + } + ] + }, + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa315b73fec2a0c06c82b383164a7027b0504b60fc6250bad91635a713a0e00b2a300b4d5a1d39d4e0a02d4a74428101c15008a6d2c683403130cef7e2f8246a0d187f041ab3d97b4dd8b4b98aa157def0fdf010000000000000000000000000000000000000000000000000000000000000003ffffffff020005c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650cc346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2010000000000a00f00001027010000bba06e6e963975badf0f4a72536650b7385ee24f209bf88152680b2b3da1bef22cc8218d59751103ad741b0b02374bf7b22678b158639d8c618af8ce533db00e", + "marker": "#EXT-X-DATERANGE:ID=\"ad-k\",START-DATE=\"2026-10-25T00:05:14.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "05c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650c", + "duration_ms": 2000 + }, + { + "content_hash": "c346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2", + "duration_ms": 2000 + } + ] + }, + { + "claim": "5750433101c54a2ce987d8b6f5085ed3aaedc11cf5ac94033b937abbebbdafab9bcd8c7540161d09b3468bb0e6a743d530e1c2dba91631b5bc9fe18e284464c97b9230294a87501e1c3cd6be34d29c82b4f227d11b008a6d2c683403130cef7e2f8246a0d187f041ab3d97b4dd8b4b98aa157def0fdf68a36c7d469ba51b471330c77fa1e7c4b8d6c08ebe3370eaca222cf0ab94866003ffffffff020005c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650cc346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2010000000000a00f00001027010000b53f7d4df5fcac1ede0f43d44ad677657305538e8403f89aff6daa61f61c6b15282fed17e34d0fcc4700cbc887c619ec1bd62113729207b5e4aa9694b7939200", + "marker": "#EXT-X-DATERANGE:ID=\"ad-u\",START-DATE=\"2026-10-25T00:05:15.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "05c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650c", + "duration_ms": 2000 + }, + { + "content_hash": "c346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2", + "duration_ms": 2000 + } + ] + }, + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3cf0f1b52e9f287eab7418444cb05553d5cb63be448abd020dc8d373552d5b09700b4d5a1d39d4e0a02d4a74428101c15008a6d2c683403130cef7e2f8246a0d187f041ab3d97b4dd8b4b98aa157def0fdf68a36c7d469ba51b471330c77fa1e7c4b8d6c08ebe3370eaca222cf0ab94866003ffffffff020005c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650cc346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2010000000000a00f00001027010000ab98eb9456e8db0586b3068d5d1770cf4a3404fd20e5aee3658ec48832553cf46a5a50e7f4b6e21c7847c1ce16a52ad703e3706ee6e8e3bd0f14751b1423b507", + "marker": "#EXT-X-DATERANGE:ID=\"ad-w\",START-DATE=\"2026-10-25T00:05:16.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "05c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650c", + "duration_ms": 2000 + }, + { + "content_hash": "c346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2", + "duration_ms": 2000 + } + ] + }, + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa33a11d187db2f300ed410f35100b2ca799277c2d29e34c2bfca355644fa5763f100b4d5a1d39d4e0a02d4a74428101c15008a6d2c683403130cef7e2f8246a0d187f041ab3d97b4dd8b4b98aa157def0fdf68a36c7d469ba51b471330c77fa1e7c4b8d6c08ebe3370eaca222cf0ab94866003ffffffff010005c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650c010000000000a00f000010270100001b26eaf2f0abb75cda8f7be1ddeb077fe8f3949bf879189bf1921c24ea08b5d919d95a95c0c3f59939ff6b46f14c3ed791bcd19d5e79e8222a982d0f86ef0a0b", + "marker": "#EXT-X-DATERANGE:ID=\"ad-a\",START-DATE=\"2026-10-25T00:05:18.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "05c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650c", + "duration_ms": 2000 + }, + { + "content_hash": "c346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2", + "duration_ms": 2000 + } + ] + }, + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3b1fb00f6c1dd8b0ec07e4abd5cdd022b6b57fbe904ac0aa66f0e5aa519d2cb6c00b4d5a1d39d4e0a02d4a74428101c1500d9eb7dd0153e1acecfb8d591ccd3d507ffffeb4eecc990ff94d6f3b8bc6447db68a36c7d469ba51b471330c77fa1e7c4b8d6c08ebe3370eaca222cf0ab94866003ffffffff020058ea245162376bd39eda1f446844b74c7ffa5c1ffff50719f77aaf96e8405b5c881aeb7444210b24241784f2b56ceea5d9ccc2901e88bc8e9649aa93ccf10398010000000000a00f00001027010000a32405fc76d0526886ccd0c90c5fcfc8acb65932d657d533d85ea1b83b8ff16a80289034c4fa81142ade3ca05f15971093a30e6cc1635088d902488f94189604", + "marker": "#EXT-X-DATERANGE:ID=\"ad-n\",START-DATE=\"2026-10-25T00:05:19.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "58ea245162376bd39eda1f446844b74c7ffa5c1ffff50719f77aaf96e8405b5c", + "duration_ms": 2000 + }, + { + "content_hash": "881aeb7444210b24241784f2b56ceea5d9ccc2901e88bc8e9649aa93ccf10398", + "duration_ms": 2000 + } + ] + } + ], + "log": "[[{\"v\":1,\"sid\":\"00b4d5a1d39d4e0a02d4a74428101c15\",\"sn\":0,\"ts_ms\":1792886700300,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"ebd52a56dc1cf49e6ffa803b30d3379911e371099043d74f8fa15da4ae4c2204\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"00b4d5a1d39d4e0a02d4a74428101c15\",\"sn\":1,\"ts_ms\":1792886700500,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"00b4d5a1d39d4e0a02d4a74428101c15\",\"sn\":2,\"ts_ms\":1792886701000,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"00b4d5a1d39d4e0a02d4a74428101c15\",\"sn\":3,\"ts_ms\":1792886708000,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"05c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"00b4d5a1d39d4e0a02d4a74428101c15\",\"sn\":4,\"ts_ms\":1792886709000,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"00b4d5a1d39d4e0a02d4a74428101c15\",\"sn\":5,\"ts_ms\":1792886712000,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"58ea245162376bd39eda1f446844b74c7ffa5c1ffff50719f77aaf96e8405b5c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"00b4d5a1d39d4e0a02d4a74428101c15\",\"sn\":6,\"ts_ms\":1792886714000,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"833a45ae63041e51f91cd32121ea566a5dedb04db771ddd082da46905ba71ef1\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"00b4d5a1d39d4e0a02d4a74428101c15\",\"sn\":7,\"ts_ms\":1792886729500,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=rr,rc=200,science.transaction.wai-ch=\"ebd52a56dc1cf49e6ffa803b30d3379911e371099043d74f8fa15da4ae4c2204\",sf=h,sid=\"00b4d5a1d39d4e0a02d4a74428101c15\",sn=0,st=l,ts=1792886700300,v=2\ne=ps,sf=h,sid=\"00b4d5a1d39d4e0a02d4a74428101c15\",sn=1,st=l,sta=s,ts=1792886700500,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"00b4d5a1d39d4e0a02d4a74428101c15\",sn=2,st=l,sta=p,ts=1792886701000,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"05c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650c\",sf=h,sid=\"00b4d5a1d39d4e0a02d4a74428101c15\",sn=3,st=l,ts=1792886708000,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2\",sf=h,sid=\"00b4d5a1d39d4e0a02d4a74428101c15\",sn=4,st=l,ts=1792886709000,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"58ea245162376bd39eda1f446844b74c7ffa5c1ffff50719f77aaf96e8405b5c\",sf=h,sid=\"00b4d5a1d39d4e0a02d4a74428101c15\",sn=5,st=l,ts=1792886712000,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"833a45ae63041e51f91cd32121ea566a5dedb04db771ddd082da46905ba71ef1\",sf=h,sid=\"00b4d5a1d39d4e0a02d4a74428101c15\",sn=6,st=l,ts=1792886714000,v=2\ne=ps,sf=h,sid=\"00b4d5a1d39d4e0a02d4a74428101c15\",sn=7,st=l,sta=e,ts=1792886729500,v=2", + "outcome": { + "outcome": "accept", + "digest": "eb718c8ac12701100ba880402bca56304026283ce1f9b80f53126d72e8bbcab1", + "channel": "7fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3", + "presentation": "live", + "start": 1792886700000000, + "end": 1792886730000000, + "ended": "ended", + "presented_ms": 28000, + "viewable_ms": 28000, + "audible_ms": 28000, + "ad_ms": 4000, + "delivery": [ + { + "origin": "https://cdn-b.example", + "class": "live", + "objects": 5, + "bytes": 3460 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 500, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [ + { + "verdict": "presented", + "quartile": 3, + "run_ms": 2000, + "viewable_ms": 2000, + "viewable": true + }, + { + "verdict": "claim_malformed" + }, + { + "verdict": "claim_duplicate" + }, + { + "verdict": "audit_marker_mismatch" + }, + { + "verdict": "claim_asset_mismatch" + }, + { + "verdict": "bad_signature" + }, + { + "verdict": "audit_claim_unbound" + }, + { + "verdict": "bad_key" + }, + { + "verdict": "audit_ad_not_acknowledged" + } + ], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "nothing_presented_again", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060080ef45f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c6501002be57be9a101ea6c65bd4e74d7d3b37c23d3b79087b1092a242b4c6421ab3a5c000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a6757630000000000000000000000000000000000000000000000000000000000000000020300000000000000000004000000000000000000000027e29cae955d52d2ebd338d41ad65453adb2c1ba814bc952fa190c1a5422809820280eb04ab6ec9fef0cc7ead416b93300a0de36cdef439956608e5ff5bfe305", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060080ef45f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c0330912f85d1253fb3b344f27dd32217442503b65b525d0b34fb68cde7c89fd421c285470f175fbe84acfddeba543d6a57a2dd068c097250d4b634cae3499f60b", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"ac3053630eb9973bb5d0544e657dc170\",sn=0,st=l,sta=s,ts=1792886600100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",sf=h,sid=\"ac3053630eb9973bb5d0544e657dc170\",sn=1,st=l,ts=1792886600200,v=2\ne=ps,sf=h,sid=\"ac3053630eb9973bb5d0544e657dc170\",sn=2,st=l,sta=e,ts=1792886604900,v=2", + "outcome": { + "outcome": "accept", + "digest": "a319cd285471078b5f2e63079a60a2ce069a2f7541ff48e6eadb311afcf1f858", + "channel": "7fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3", + "presentation": "live", + "start": 1792886600000000, + "end": 1792886606000000, + "ended": "error", + "presented_ms": 0, + "viewable_ms": 0, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://cdn-a.example", + "class": "ancillary", + "objects": 1, + "bytes": 300 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": null, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_00", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e238c22ed0c121659a31c7fdfa195a6b21bdf8dc703f0cd064736dd1eeaa33df42131ca7dbd17b7c3c021ee9ef6f847ba801006a99209f5e060000c4fb229f5e06000101140068747470733a2f2f656467652e6578616d706c6501008fb2179a878e79d4a6b8155943e63a5354e23fd8dfb0dbb1f0d8596c7e2a94740000020000009001000080250000102701c02b0000986c00001027012c01000001000000b0040000000000000000000000000000000000006174e8abcecb589de4837c36fa243c36d8c68591fdc03ae1b6110033830498cb000000000000000000000000000000000000000000000000000000000000000001031027000000000000030000cd62d30974f27e293af6cee07df4cf06bf994741d99a0692572c1dfa2a27d5e251db0ffedd3ba056b071c7506d11e5388e3d0d4e35cb5b94c935a75f210c2c08", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e38c22ed0c121659a31c7fdfa195a6b21bdf8dc703f0cd064736dd1eeaa33df42006a99209f5e0600800032219f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000337a39bd3f6b7e9baed668d597aa6dae34bcfb6ea4cdcf7dd36cd1b67a9584116aa57f38b82fee8df91021bc1708d8347b84262ada79b95d6aea5f9ace0b8c36040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0403fdcbfeb63657feeb17781982613dd67238fb69f52372057b76ef33641a1f05d3ff5361fee0ae3744dcf4fa8afe538eb90696e2b57b5c3599287332b3f7e090b", + "items": [ + "3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee200000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"131ca7dbd17b7c3c021ee9ef6f847ba8\",\"sn\":0,\"ts_ms\":1792887400100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"131ca7dbd17b7c3c021ee9ef6f847ba8\",\"sn\":1,\"ts_ms\":1792887400150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"131ca7dbd17b7c3c021ee9ef6f847ba8\",\"sn\":2,\"ts_ms\":1792887400250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"131ca7dbd17b7c3c021ee9ef6f847ba8\",\"sn\":3,\"ts_ms\":1792887400400,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"131ca7dbd17b7c3c021ee9ef6f847ba8\",\"sn\":4,\"ts_ms\":1792887410000,\"event\":\"stall-start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"131ca7dbd17b7c3c021ee9ef6f847ba8\",\"sn\":5,\"ts_ms\":1792887411200,\"event\":\"stall-end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"131ca7dbd17b7c3c021ee9ef6f847ba8\",\"sn\":6,\"ts_ms\":1792887439000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"131ca7dbd17b7c3c021ee9ef6f847ba8\",sn=0,st=l,sta=s,ts=1792887400100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"131ca7dbd17b7c3c021ee9ef6f847ba8\",sn=1,st=l,ts=1792887400150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",sf=h,sid=\"131ca7dbd17b7c3c021ee9ef6f847ba8\",sn=2,st=l,ts=1792887400250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"131ca7dbd17b7c3c021ee9ef6f847ba8\",sn=3,st=l,sta=p,ts=1792887400400,v=2\ne=ps,sf=h,sid=\"131ca7dbd17b7c3c021ee9ef6f847ba8\",sn=4,st=l,sta=r,ts=1792887410000,v=2\ne=ps,sf=h,sid=\"131ca7dbd17b7c3c021ee9ef6f847ba8\",sn=5,st=l,sta=p,ts=1792887411200,v=2\ne=ps,sf=h,sid=\"131ca7dbd17b7c3c021ee9ef6f847ba8\",sn=6,st=l,sta=e,ts=1792887439000,v=2", + "outcome": { + "outcome": "accept", + "digest": "7666bfc8367a02f7eeea5fa6344bd2d43992d37b7f676be66ccaa9ca27dd9b18", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792887400000000, + "end": 1792887440000000, + "ended": "ended", + "presented_ms": 37400, + "viewable_ms": 37400, + "audible_ms": 37400, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 300, + "stalls": 1, + "stall_ms": 1200, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 10000 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_01", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e229d476114cee1abf9e933337750ceaddd8f96c8be146ac6f21c47394dbc21cfaa49d955804a2e717388297c6a9b631af0100f12c249f5e0600004b8f269f5e06000201140068747470733a2f2f656467652e6578616d706c65010050aa8e1784cc25a9050250c7afd0a7bd19b8a4dffcebeb66a77f213199d0eeda0000010000009a010000be9600001027013601000000000000000000000000000000000000000000000000000054ccd564004807610475bff7411a6c1dbda46cb0ed72e564b50498e433034af100000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000accde268a839d5e4e611b9673b41650f7112aa713d3fc8502a8c4b4a0edfc7e0bc077183f75fc5b2eaa01fcc8ed26d7252bee1a6d89cb7c87ab138da80602a0d", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e29d476114cee1abf9e933337750ceaddd8f96c8be146ac6f21c47394dbc21cfa00f12c249f5e06008087c5249f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000a512307eb525ef68137406bb53edc1cfd963fe40ad9e3b0b1d80c578766f02648514c44dc900d081093ddf8e6883676f5c9d06e065ed016d6254c2d0a80639f5040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040c2332c5bc247acc9c3db5538003eb533e8874d8ee3b3c463be5f58717b162f2e0d90015f5a2d52d65049a637a87949d655f1c21c793eb39c1d2f03dcfcc9f80f", + "items": [ + "90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d8100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"a49d955804a2e717388297c6a9b631af\",\"sn\":0,\"ts_ms\":1792887460100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"a49d955804a2e717388297c6a9b631af\",\"sn\":1,\"ts_ms\":1792887460150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"a49d955804a2e717388297c6a9b631af\",\"sn\":2,\"ts_ms\":1792887460250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"a49d955804a2e717388297c6a9b631af\",\"sn\":3,\"ts_ms\":1792887460410,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"a49d955804a2e717388297c6a9b631af\",\"sn\":4,\"ts_ms\":1792887499000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"a49d955804a2e717388297c6a9b631af\",sn=0,st=l,sta=s,ts=1792887460100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"a49d955804a2e717388297c6a9b631af\",sn=1,st=l,ts=1792887460150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",sf=h,sid=\"a49d955804a2e717388297c6a9b631af\",sn=2,st=l,ts=1792887460250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"a49d955804a2e717388297c6a9b631af\",sn=3,st=l,sta=p,ts=1792887460410,v=2\ne=ps,sf=h,sid=\"a49d955804a2e717388297c6a9b631af\",sn=4,st=l,sta=e,ts=1792887499000,v=2", + "outcome": { + "outcome": "accept", + "digest": "a5a68880f53a4ddcab408032aefaf8e6eae5cac931707cc3aa9e2239627a2ebe", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792887460000000, + "end": 1792887500000000, + "ended": "user", + "presented_ms": 38590, + "viewable_ms": 38590, + "audible_ms": 38590, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 310, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_02", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2245f0471e5bf500ea9af9c385d3d4497b690102f569378aef15a9177c08929eb6c7fcdcb688717cc38f781d9a1c7feb6010078c0279f5e060000d2222a9f5e06000201140068747470733a2f2f656467652e6578616d706c650100e29448543fd44a9d66b0c1f33de1949c01ab2d772b5961bc7499ccb8ed351320000001000000a4010000b496000010270140010000000000000000000000000000000000000000000000000000b639fef02cc5211c7e0e3b9ab026daa35d67bdbcea6408d85ce39c84cd94a2a300000000000000000000000000000000000000000000000000000000000000000203307500000000000003040000000000000000000000ab5e8c8417840c31a722c207ae2bcf9e45e3c23c87cd656b3f1fdc3bd63b63464e5a14a85b5d539947ef11b297655b2d527028436fb91f1ae5d877e5dbca4d0b", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e245f0471e5bf500ea9af9c385d3d4497b690102f569378aef15a9177c08929eb0078c0279f5e0600800e59289f5e060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000200300000000000038d05149bc5379282245bcf7547fc45a42705115272c20886ddaf10872c388cb9b636335eafac219654e59520ed4ab116ce4e164055dac0d8927a1fceb81ca03040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040eea72e4c281b66c425550397f770385f3b87f2f5b7831130433b62f9778b9dbb228dee2736f68386d3ddefb254c39329e12af26381f92f9454d16f42bec7820b", + "items": [ + "d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e5100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"6c7fcdcb688717cc38f781d9a1c7feb6\",\"sn\":0,\"ts_ms\":1792887520100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"6c7fcdcb688717cc38f781d9a1c7feb6\",\"sn\":1,\"ts_ms\":1792887520150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"6c7fcdcb688717cc38f781d9a1c7feb6\",\"sn\":2,\"ts_ms\":1792887520250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"6c7fcdcb688717cc38f781d9a1c7feb6\",\"sn\":3,\"ts_ms\":1792887520420,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"6c7fcdcb688717cc38f781d9a1c7feb6\",\"sn\":4,\"ts_ms\":1792887559000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"6c7fcdcb688717cc38f781d9a1c7feb6\",sn=0,st=l,sta=s,ts=1792887520100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"6c7fcdcb688717cc38f781d9a1c7feb6\",sn=1,st=l,ts=1792887520150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",sf=h,sid=\"6c7fcdcb688717cc38f781d9a1c7feb6\",sn=2,st=l,ts=1792887520250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"6c7fcdcb688717cc38f781d9a1c7feb6\",sn=3,st=l,sta=p,ts=1792887520420,v=2\ne=ps,sf=h,sid=\"6c7fcdcb688717cc38f781d9a1c7feb6\",sn=4,st=l,sta=e,ts=1792887559000,v=2", + "outcome": { + "outcome": "accept", + "digest": "87c36d8361ea29d8d36c2654dc70cc8afa77cf4be11e2ded770584de93447ab5", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792887520000000, + "end": 1792887560000000, + "ended": "user", + "presented_ms": 38580, + "viewable_ms": 38580, + "audible_ms": 38580, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 320, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 30000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_03", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2221fdbbd31e6450c7048e938682f0eb7c3fb01fb746242d172902d011dbb8943c296dc34adabd2d7ea08760a60b402b80100ff532b9f5e06000059b62d9f5e06000201140068747470733a2f2f656467652e6578616d706c650100764d70f7b3e3eae0405df0a82fa71c4bec4281058abf5c9dc9e65ec462ccb1cc000001000000ae010000aa9600001027004a01000000000000000000000000000000000000000000000000000063b16c2588c926420921acd1ee0955be8bafa34c00575feaf7987ec87cca65b8000000000000000000000000000000000000000000000000000000000000000001030000000000000000000000c6c724209810733051454bd2d2d43deda6e6ca153cf197c45a02c91ac5d30be2dd4f95263847b8c67688e92eec67042e4d96b2423e43ff89d80e30cdb890300a", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e221fdbbd31e6450c7048e938682f0eb7c3fb01fb746242d172902d011dbb894300ff532b9f5e06008095ec2b9f5e06000000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000020030000000000000ccbb165ec882a87aaf3b836c552777becb7ccf33ccf7407a79266650352f9f2d8e7c3f3c74b1bac1233221b4db12d4de07706cbf32a71c2836538ec799c6664040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a04087f56b3118abf3a413b5412fed5a9160660685d1d7ad852d030bdaa5572b516e5b4a45f083210dc9bf800084c6ef45160d8695cec5eff95a7d8cb4e1895d7401", + "items": [ + "c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c00000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"c296dc34adabd2d7ea08760a60b402b8\",\"sn\":0,\"ts_ms\":1792887580100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"c296dc34adabd2d7ea08760a60b402b8\",\"sn\":1,\"ts_ms\":1792887580150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"c296dc34adabd2d7ea08760a60b402b8\",\"sn\":2,\"ts_ms\":1792887580250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"c296dc34adabd2d7ea08760a60b402b8\",\"sn\":3,\"ts_ms\":1792887580430,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"c296dc34adabd2d7ea08760a60b402b8\",\"sn\":4,\"ts_ms\":1792887619000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"c296dc34adabd2d7ea08760a60b402b8\",sn=0,st=l,sta=s,ts=1792887580100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"c296dc34adabd2d7ea08760a60b402b8\",sn=1,st=l,ts=1792887580150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",sf=h,sid=\"c296dc34adabd2d7ea08760a60b402b8\",sn=2,st=l,ts=1792887580250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"c296dc34adabd2d7ea08760a60b402b8\",sn=3,st=l,sta=p,ts=1792887580430,v=2\ne=ps,sf=h,sid=\"c296dc34adabd2d7ea08760a60b402b8\",sn=4,st=l,sta=e,ts=1792887619000,v=2", + "outcome": { + "outcome": "accept", + "digest": "afe47655cbe546f5ed9b256f1f32c96d53fe5e094b2817f1a768eee346ce979b", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792887580000000, + "end": 1792887620000000, + "ended": "user", + "presented_ms": 38570, + "viewable_ms": 38570, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 330, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_04", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29abbb2776987f75cd357922a1551423aaa0fc71d60b8c3ce9f74adfc5428ea1f3c8db05c00e97e2da045e4680c5149a1010086e72e9f5e060000e049319f5e06000101140068747470733a2f2f656467652e6578616d706c6501006af772986210626c12e7c197a2924a4aae1f239d627b0db360c1d44daaf93025000001000000b8010000a096000010270154010000000000000000000000000000000000000000000000000000bd832ca1a2d1b94f2364e60d9e4f6654db87a012c18fef8c4232e14afc1bd09a0000000000000000000000000000000000000000000000000000000000000000020350c300000000000003040000000000000000000000a1803c3ffb4fd4345d102ce088f6bc5902541ff5171fbc30276c06e78143f16568e5c013ad817f96d931e620f3c79d57f99b83aaabf58b1eb0390d6869674c01", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e9abbb2776987f75cd357922a1551423aaa0fc71d60b8c3ce9f74adfc5428ea1f0086e72e9f5e0600801c802f9f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000337a39bd3f6b7e9baed668d597aa6dae34bcfb6ea4cdcf7dd36cd1b67a9584116aa57f38b82fee8df91021bc1708d8347b84262ada79b95d6aea5f9ace0b8c36040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0406e2b96570b81165d015fd29c9f4ccf1536945aa8066bb1fe14bbd10bc3ae206ab6f815f36962dc876b0fdf41a59a3482cac3fdadbf7bc93d14e1c3b2d36d2e0f", + "items": [ + "3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee200000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"3c8db05c00e97e2da045e4680c5149a1\",\"sn\":0,\"ts_ms\":1792887640100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"3c8db05c00e97e2da045e4680c5149a1\",\"sn\":1,\"ts_ms\":1792887640150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"3c8db05c00e97e2da045e4680c5149a1\",\"sn\":2,\"ts_ms\":1792887640250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"3c8db05c00e97e2da045e4680c5149a1\",\"sn\":3,\"ts_ms\":1792887640440,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"3c8db05c00e97e2da045e4680c5149a1\",\"sn\":4,\"ts_ms\":1792887679000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"3c8db05c00e97e2da045e4680c5149a1\",sn=0,st=l,sta=s,ts=1792887640100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"3c8db05c00e97e2da045e4680c5149a1\",sn=1,st=l,ts=1792887640150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",sf=h,sid=\"3c8db05c00e97e2da045e4680c5149a1\",sn=2,st=l,ts=1792887640250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"3c8db05c00e97e2da045e4680c5149a1\",sn=3,st=l,sta=p,ts=1792887640440,v=2\ne=ps,sf=h,sid=\"3c8db05c00e97e2da045e4680c5149a1\",sn=4,st=l,sta=e,ts=1792887679000,v=2", + "outcome": { + "outcome": "accept", + "digest": "d0ddc845fc0971771f48f195575cd43397b00c9223586afd074b37263b734086", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792887640000000, + "end": 1792887680000000, + "ended": "ended", + "presented_ms": 38560, + "viewable_ms": 38560, + "audible_ms": 38560, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 340, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 50000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_05", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2a884776fb05b8e5f56c7af2576ae140c7ff91d61b1d112ad91f8c5c54774366fd6afa6ebca27f0b5b906a7e6cd13a9db01000d7b329f5e06000067dd349f5e06000201140068747470733a2f2f656467652e6578616d706c650100e17ec3f07e2563bd71892d87dc512571d5fb64fbc8881f96815af888c7e73545000002000000c20100004e250000102701c02b0000986c00001027015e01000001000000b004000000000000000000000000000000000000845557688717e60b01c912a2eb7423ed7fc765e5e1188bb69e493bc2c751704b000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000007863b361ea5f665238e10c83fae7bf78bf22e504794fe7ff6b5b298dab98eafe08b9a4814d247caef902fa04292708209728104c7c6bd04a436151e58935dc0a", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6ea884776fb05b8e5f56c7af2576ae140c7ff91d61b1d112ad91f8c5c54774366f000d7b329f5e060080a313339f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000a512307eb525ef68137406bb53edc1cfd963fe40ad9e3b0b1d80c578766f02648514c44dc900d081093ddf8e6883676f5c9d06e065ed016d6254c2d0a80639f5040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040c9b603b5ba94313cf1015c82549d9483deea32e1ad322b6319cf695bf015d0b24cfa46b61c21773935c552965536c36103e35d63d741ad197a640922efbb0b05", + "items": [ + "90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d8100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"d6afa6ebca27f0b5b906a7e6cd13a9db\",\"sn\":0,\"ts_ms\":1792887700100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d6afa6ebca27f0b5b906a7e6cd13a9db\",\"sn\":1,\"ts_ms\":1792887700150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d6afa6ebca27f0b5b906a7e6cd13a9db\",\"sn\":2,\"ts_ms\":1792887700250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d6afa6ebca27f0b5b906a7e6cd13a9db\",\"sn\":3,\"ts_ms\":1792887700450,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d6afa6ebca27f0b5b906a7e6cd13a9db\",\"sn\":4,\"ts_ms\":1792887710000,\"event\":\"stall-start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d6afa6ebca27f0b5b906a7e6cd13a9db\",\"sn\":5,\"ts_ms\":1792887711200,\"event\":\"stall-end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d6afa6ebca27f0b5b906a7e6cd13a9db\",\"sn\":6,\"ts_ms\":1792887739000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"d6afa6ebca27f0b5b906a7e6cd13a9db\",sn=0,st=l,sta=s,ts=1792887700100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"d6afa6ebca27f0b5b906a7e6cd13a9db\",sn=1,st=l,ts=1792887700150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",sf=h,sid=\"d6afa6ebca27f0b5b906a7e6cd13a9db\",sn=2,st=l,ts=1792887700250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"d6afa6ebca27f0b5b906a7e6cd13a9db\",sn=3,st=l,sta=p,ts=1792887700450,v=2\ne=ps,sf=h,sid=\"d6afa6ebca27f0b5b906a7e6cd13a9db\",sn=4,st=l,sta=r,ts=1792887710000,v=2\ne=ps,sf=h,sid=\"d6afa6ebca27f0b5b906a7e6cd13a9db\",sn=5,st=l,sta=p,ts=1792887711200,v=2\ne=ps,sf=h,sid=\"d6afa6ebca27f0b5b906a7e6cd13a9db\",sn=6,st=l,sta=e,ts=1792887739000,v=2", + "outcome": { + "outcome": "accept", + "digest": "e49cd689657300c1493660888afd4d63da290905d1e69d08fa7c275738548212", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792887700000000, + "end": 1792887740000000, + "ended": "user", + "presented_ms": 37350, + "viewable_ms": 37350, + "audible_ms": 37350, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 350, + "stalls": 1, + "stall_ms": 1200, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_06", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2b1bfcd57560e706c5efa57904cba5e75ca4054e2791634e54341d4b26627f9f71c1c32fd6c687f1a0073e8642711c1310100940e369f5e060000ee70389f5e06000201140068747470733a2f2f656467652e6578616d706c6501005570f206877a6caba84fadeb38250de52955fc9bd1aa2dff00adb356e5e21b16000001000000cc0100008c96000010270068010000000000000000000000000000000000000000000000000000ae85f7d7985817e4745baf89ec562fdf39a0b34e1dad3d62945358fe974678e0000000000000000000000000000000000000000000000000000000000000000001037011010000000000030000e0b3a30509c1805ab7c32893d60604270ef81b1c02a19e74c9d4e26ec8d0f5bcca11467639768550dedd8d93c2775f92410e3fd12f226a03bad524ec29a83905", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6eb1bfcd57560e706c5efa57904cba5e75ca4054e2791634e54341d4b26627f9f700940e369f5e0600802aa7369f5e060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000200300000000000038d05149bc5379282245bcf7547fc45a42705115272c20886ddaf10872c388cb9b636335eafac219654e59520ed4ab116ce4e164055dac0d8927a1fceb81ca03040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a04024e8a31785357645d4536a41a40cbe2f3195d4af8c0d5a74c8064de32bf4a4e96e212bc1d23bb35ed7b5e2bd5472228b49b446d163aec69e82ae3c30033f8a0d", + "items": [ + "d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e5100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"1c1c32fd6c687f1a0073e8642711c131\",\"sn\":0,\"ts_ms\":1792887760100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"1c1c32fd6c687f1a0073e8642711c131\",\"sn\":1,\"ts_ms\":1792887760150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"1c1c32fd6c687f1a0073e8642711c131\",\"sn\":2,\"ts_ms\":1792887760250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"1c1c32fd6c687f1a0073e8642711c131\",\"sn\":3,\"ts_ms\":1792887760460,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"1c1c32fd6c687f1a0073e8642711c131\",\"sn\":4,\"ts_ms\":1792887799000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"1c1c32fd6c687f1a0073e8642711c131\",sn=0,st=l,sta=s,ts=1792887760100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"1c1c32fd6c687f1a0073e8642711c131\",sn=1,st=l,ts=1792887760150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",sf=h,sid=\"1c1c32fd6c687f1a0073e8642711c131\",sn=2,st=l,ts=1792887760250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"1c1c32fd6c687f1a0073e8642711c131\",sn=3,st=l,sta=p,ts=1792887760460,v=2\ne=ps,sf=h,sid=\"1c1c32fd6c687f1a0073e8642711c131\",sn=4,st=l,sta=e,ts=1792887799000,v=2", + "outcome": { + "outcome": "accept", + "digest": "2a96a06802700767ad2e3e5508c4edcc6434d93c7ead44ee19594a63ae34027c", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792887760000000, + "end": 1792887800000000, + "ended": "user", + "presented_ms": 38540, + "viewable_ms": 38540, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 360, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 70000 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_07", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e20bae8936ebc5984604f970377092ebb8ccdcf58971eb859fbf88a75db9f0435ea6fefc00993fa44db350e09ec85d6b4d01001ba2399f5e06000075043c9f5e06000201140068747470733a2f2f656467652e6578616d706c65010077fb2a80e3719c732daa820db83d6f3d43920fbb1b4114621c87c68bc36b49e4000001000000d601000082960000d0070172010000000000000000000000000000000000000000000000000000ae20e519795f59757faa1cf42b9b36993fc2427f4d36eab982031f908be54e5800000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000c9211edfd0c88d3117ecfe8f7efe424dc9576d01caa64f73f804fab6b198ed1034c87287fb1a883959e63653bda9024594209810551c1f9923bf3a177bdd740c", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e0bae8936ebc5984604f970377092ebb8ccdcf58971eb859fbf88a75db9f0435e001ba2399f5e060080b13a3a9f5e06000000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000020030000000000000ccbb165ec882a87aaf3b836c552777becb7ccf33ccf7407a79266650352f9f2d8e7c3f3c74b1bac1233221b4db12d4de07706cbf32a71c2836538ec799c6664040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0409ed7c5a5e204480c421b88b3cddf86125986c1b20ad8dd26d7f4b3f0eaec67a3ab9f31869b962155047d1f66ab4952c9778eecb10242f9a5c4bd2150e68c5300", + "items": [ + "c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c00000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"a6fefc00993fa44db350e09ec85d6b4d\",\"sn\":0,\"ts_ms\":1792887820100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"a6fefc00993fa44db350e09ec85d6b4d\",\"sn\":1,\"ts_ms\":1792887820150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"a6fefc00993fa44db350e09ec85d6b4d\",\"sn\":2,\"ts_ms\":1792887820250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"a6fefc00993fa44db350e09ec85d6b4d\",\"sn\":3,\"ts_ms\":1792887820470,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"a6fefc00993fa44db350e09ec85d6b4d\",\"sn\":4,\"ts_ms\":1792887859000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"a6fefc00993fa44db350e09ec85d6b4d\",sn=0,st=l,sta=s,ts=1792887820100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"a6fefc00993fa44db350e09ec85d6b4d\",sn=1,st=l,ts=1792887820150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",sf=h,sid=\"a6fefc00993fa44db350e09ec85d6b4d\",sn=2,st=l,ts=1792887820250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"a6fefc00993fa44db350e09ec85d6b4d\",sn=3,st=l,sta=p,ts=1792887820470,v=2\ne=ps,sf=h,sid=\"a6fefc00993fa44db350e09ec85d6b4d\",sn=4,st=l,sta=e,ts=1792887859000,v=2", + "outcome": { + "outcome": "accept", + "digest": "e8297ff4be6fd7518764383999a4cf2178e5b0bfaa6b51614c16897624ebf8c9", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792887820000000, + "end": 1792887860000000, + "ended": "user", + "presented_ms": 38530, + "viewable_ms": 0, + "audible_ms": 38530, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 370, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_08", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2f42e793f77b68f4518cd02f6ff3cf9263239028589fbdd3438f2e303df1bfb094d6fd8c0813be2b233700b9c92f348410100a2353d9f5e060000fc973f9f5e06000101140068747470733a2f2f656467652e6578616d706c6501001aeffb4b4f42ca71423d8e9cfae97d78ba82ca0fc7e9015f3ce403c3d4f026f2000001000000e0010000789600001027017c01000000000000000000000000000000000000000000000000000065a63ed1c370447c67ab1d403ff7e5417dd6bc0efe507b12693cdc03d94a96c800000000000000000000000000000000000000000000000000000000000000000203905f010000000000030400000000000000000000009c50041fdbfa30c2d3eeb2082355c7399b9a89c84199476d5dbbcbbda01a2c9257f4c123e7c6919b0d4a219d01663256db166115be3041d7ae3c128999ba2506", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6ef42e793f77b68f4518cd02f6ff3cf9263239028589fbdd3438f2e303df1bfb0900a2353d9f5e06008038ce3d9f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000337a39bd3f6b7e9baed668d597aa6dae34bcfb6ea4cdcf7dd36cd1b67a9584116aa57f38b82fee8df91021bc1708d8347b84262ada79b95d6aea5f9ace0b8c36040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040e492023e9f463fa18c9b55f87ed7d8f1955bb7d1525a37574275aedb59a975541f790aa55b529b05132a851ba83e434bc1015d357f213493da54e303a7e9ba0c", + "items": [ + "3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee200000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"4d6fd8c0813be2b233700b9c92f34841\",\"sn\":0,\"ts_ms\":1792887880100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"4d6fd8c0813be2b233700b9c92f34841\",\"sn\":1,\"ts_ms\":1792887880150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"4d6fd8c0813be2b233700b9c92f34841\",\"sn\":2,\"ts_ms\":1792887880250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"4d6fd8c0813be2b233700b9c92f34841\",\"sn\":3,\"ts_ms\":1792887880480,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"4d6fd8c0813be2b233700b9c92f34841\",\"sn\":4,\"ts_ms\":1792887919000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"4d6fd8c0813be2b233700b9c92f34841\",sn=0,st=l,sta=s,ts=1792887880100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"4d6fd8c0813be2b233700b9c92f34841\",sn=1,st=l,ts=1792887880150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",sf=h,sid=\"4d6fd8c0813be2b233700b9c92f34841\",sn=2,st=l,ts=1792887880250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"4d6fd8c0813be2b233700b9c92f34841\",sn=3,st=l,sta=p,ts=1792887880480,v=2\ne=ps,sf=h,sid=\"4d6fd8c0813be2b233700b9c92f34841\",sn=4,st=l,sta=e,ts=1792887919000,v=2", + "outcome": { + "outcome": "accept", + "digest": "b1fbb46b6c194cf00cc5d1cd5231295a0f47dc8ec7358dd1a530b1b0730dad44", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792887880000000, + "end": 1792887920000000, + "ended": "ended", + "presented_ms": 38520, + "viewable_ms": 38520, + "audible_ms": 38520, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 380, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 90000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_09", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2eb96f64c04448e4a12ee39e3e9abdc9c27fae692c7ed2bdf235ba49a66e96e88603b3c4b4a8b3b972c59566fa0f92c93010029c9409f5e060000832b439f5e06000201140068747470733a2f2f656467652e6578616d706c650100b926391f26fee0de6340f7641d8d4c5bbe948051ca7e9c177d5acf9553c4b817000001000000ea0100006e96000010270086010000000000000000000000000000000000000000000000000000fa9fedee48090224b151ccb7208b263e249bad79df8749916e74945c1739a14800000000000000000000000000000000000000000000000000000000000000000103000000000000000000000068ed4762f81a8d19f121d694df3536cfaa2bbeefee951473f6957f974fa719e046680f9e783b7dd88844310415cf3a1333358760dbbdc099a64b415c280b2f0f", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6eeb96f64c04448e4a12ee39e3e9abdc9c27fae692c7ed2bdf235ba49a66e96e880029c9409f5e060080bf61419f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000a512307eb525ef68137406bb53edc1cfd963fe40ad9e3b0b1d80c578766f02648514c44dc900d081093ddf8e6883676f5c9d06e065ed016d6254c2d0a80639f5040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040c0eda0b18b21d32f9734c7a59904048941920373571876d95cf10a94eefc88b940dcb1af3a07de6d3b581f221fd0f7651a27c55d88c3e5dad0df03db5b09b00b", + "items": [ + "90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d8100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"603b3c4b4a8b3b972c59566fa0f92c93\",\"sn\":0,\"ts_ms\":1792887940100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"603b3c4b4a8b3b972c59566fa0f92c93\",\"sn\":1,\"ts_ms\":1792887940150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"603b3c4b4a8b3b972c59566fa0f92c93\",\"sn\":2,\"ts_ms\":1792887940250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"603b3c4b4a8b3b972c59566fa0f92c93\",\"sn\":3,\"ts_ms\":1792887940490,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"603b3c4b4a8b3b972c59566fa0f92c93\",\"sn\":4,\"ts_ms\":1792887979000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"603b3c4b4a8b3b972c59566fa0f92c93\",sn=0,st=l,sta=s,ts=1792887940100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"603b3c4b4a8b3b972c59566fa0f92c93\",sn=1,st=l,ts=1792887940150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",sf=h,sid=\"603b3c4b4a8b3b972c59566fa0f92c93\",sn=2,st=l,ts=1792887940250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"603b3c4b4a8b3b972c59566fa0f92c93\",sn=3,st=l,sta=p,ts=1792887940490,v=2\ne=ps,sf=h,sid=\"603b3c4b4a8b3b972c59566fa0f92c93\",sn=4,st=l,sta=e,ts=1792887979000,v=2", + "outcome": { + "outcome": "accept", + "digest": "b2d3dd476c7a250d1e38fa0d1266e76a3af9c5c9da9bee531fca1daa3484554a", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792887940000000, + "end": 1792887980000000, + "ended": "user", + "presented_ms": 38510, + "viewable_ms": 38510, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 390, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_10", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2c3880469ec73ba99d6e54e1888805887527d500e19c9969160143a4dd2b4f4ff7d9e95eb165f4d25518669db54870bc90100b05c449f5e0600000abf469f5e06000201140068747470733a2f2f656467652e6578616d706c650100da399bf6b671fca6eb6f29387adfa1fadccf76556d3231f359e5156fc2463b32000002000000f40100001c250000102701c02b0000986c00001027019001000001000000b004000000000000000000000000000000000000674a64afb7bdb0ef330fac0954d86a575481c36fd3c5f8378ab5849f7a27cbd500000000000000000000000000000000000000000000000000000000000000000203b0ad0100000000000304000000000000000000000089bcb65e7eccdbbe18e89ae6a3a998bb8a4edf56807aba925cdae48ffcdf23e145aaddce4acb3fb60cdf7a82672563a4351c2c375ffec3f405ffa4d90ac52802", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6ec3880469ec73ba99d6e54e1888805887527d500e19c9969160143a4dd2b4f4ff00b05c449f5e06008046f5449f5e060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000200300000000000038d05149bc5379282245bcf7547fc45a42705115272c20886ddaf10872c388cb9b636335eafac219654e59520ed4ab116ce4e164055dac0d8927a1fceb81ca03040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0409323f3dd5ceddbe2b1d4a80690104effab4f5f23bdf8fee7e07f8efc86175e05aeb97adc91311b8787baeba4dcf98bb811846f3f18e8fb99915bf5c55548760d", + "items": [ + "d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e5100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"7d9e95eb165f4d25518669db54870bc9\",\"sn\":0,\"ts_ms\":1792888000100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7d9e95eb165f4d25518669db54870bc9\",\"sn\":1,\"ts_ms\":1792888000150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7d9e95eb165f4d25518669db54870bc9\",\"sn\":2,\"ts_ms\":1792888000250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7d9e95eb165f4d25518669db54870bc9\",\"sn\":3,\"ts_ms\":1792888000500,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7d9e95eb165f4d25518669db54870bc9\",\"sn\":4,\"ts_ms\":1792888010000,\"event\":\"stall-start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7d9e95eb165f4d25518669db54870bc9\",\"sn\":5,\"ts_ms\":1792888011200,\"event\":\"stall-end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7d9e95eb165f4d25518669db54870bc9\",\"sn\":6,\"ts_ms\":1792888039000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"7d9e95eb165f4d25518669db54870bc9\",sn=0,st=l,sta=s,ts=1792888000100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"7d9e95eb165f4d25518669db54870bc9\",sn=1,st=l,ts=1792888000150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",sf=h,sid=\"7d9e95eb165f4d25518669db54870bc9\",sn=2,st=l,ts=1792888000250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"7d9e95eb165f4d25518669db54870bc9\",sn=3,st=l,sta=p,ts=1792888000500,v=2\ne=ps,sf=h,sid=\"7d9e95eb165f4d25518669db54870bc9\",sn=4,st=l,sta=r,ts=1792888010000,v=2\ne=ps,sf=h,sid=\"7d9e95eb165f4d25518669db54870bc9\",sn=5,st=l,sta=p,ts=1792888011200,v=2\ne=ps,sf=h,sid=\"7d9e95eb165f4d25518669db54870bc9\",sn=6,st=l,sta=e,ts=1792888039000,v=2", + "outcome": { + "outcome": "accept", + "digest": "c4e34615d6b93e97b3bb68c8033ebca21e8afc032350e4ba98b3374b41a6754f", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888000000000, + "end": 1792888040000000, + "ended": "user", + "presented_ms": 37300, + "viewable_ms": 37300, + "audible_ms": 37300, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 400, + "stalls": 1, + "stall_ms": 1200, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 110000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_11", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e24180bfe29f50c988293fada11d73b86e6f19d54afefa0d6037a7797830d8f3680d0dee8720c19bada6a42a3076581361010037f0479f5e06000091524a9f5e06000201140068747470733a2f2f656467652e6578616d706c650100771033caad999fa9a5019ebaa6f172ab58f2936a81c3d1d68a89697c072a8401000001000000fe0100005a9600001027019a010000000000000000000000000000000000000000000000000000d5202bf8a271e4b274d9a714320a2d8cf07b3b974ae230872b4e577c05526049000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000003275057d78c1fe8d9a942540b02cab401ea5d0bac3825006cb50fdffde31fbce442e7c3810b10d2296ca78d82fa33476cf21288be8df06fc4229ac08d326c305", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e4180bfe29f50c988293fada11d73b86e6f19d54afefa0d6037a7797830d8f3680037f0479f5e060080cd88489f5e06000000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000020030000000000000ccbb165ec882a87aaf3b836c552777becb7ccf33ccf7407a79266650352f9f2d8e7c3f3c74b1bac1233221b4db12d4de07706cbf32a71c2836538ec799c6664040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040e91b4a7a595d0e71d1e5381c2c177a741575827af71d68de377e54e825b1c39393c4d587c577160098ee8898df485bf37326daaa005763435b872c754799230b", + "items": [ + "c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c00000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"0d0dee8720c19bada6a42a3076581361\",\"sn\":0,\"ts_ms\":1792888060100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"0d0dee8720c19bada6a42a3076581361\",\"sn\":1,\"ts_ms\":1792888060150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"0d0dee8720c19bada6a42a3076581361\",\"sn\":2,\"ts_ms\":1792888060250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"0d0dee8720c19bada6a42a3076581361\",\"sn\":3,\"ts_ms\":1792888060510,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"0d0dee8720c19bada6a42a3076581361\",\"sn\":4,\"ts_ms\":1792888099000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"0d0dee8720c19bada6a42a3076581361\",sn=0,st=l,sta=s,ts=1792888060100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"0d0dee8720c19bada6a42a3076581361\",sn=1,st=l,ts=1792888060150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",sf=h,sid=\"0d0dee8720c19bada6a42a3076581361\",sn=2,st=l,ts=1792888060250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"0d0dee8720c19bada6a42a3076581361\",sn=3,st=l,sta=p,ts=1792888060510,v=2\ne=ps,sf=h,sid=\"0d0dee8720c19bada6a42a3076581361\",sn=4,st=l,sta=e,ts=1792888099000,v=2", + "outcome": { + "outcome": "accept", + "digest": "5aa582bc34c398036eef0b10890ec6164888d259dbcd21cefe269cfd0ef55b4b", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888060000000, + "end": 1792888100000000, + "ended": "user", + "presented_ms": 38490, + "viewable_ms": 38490, + "audible_ms": 38490, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 410, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_12", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2543750b445bc69c3e80816c9c872a904b4b23b99a61f69acb09d60a3316edf8ffa8082b3bc1d39fa28e739f0c0ff6fcd0100be834b9f5e06000018e64d9f5e06000101140068747470733a2f2f656467652e6578616d706c650100894ae32b63abeacd3a806bbc610495e54dec192897bd620e419eb40e6c0eff6e0000010000000802000050960000102700a4010000000000000000000000000000000000000000000000000000ba085e5969cc2f193f27235a5bf6b14525b965999e61dc11629dfb262a6ee4a900000000000000000000000000000000000000000000000000000000000000000103d0fb0100000000000300006be472292c4018f54a709bafda40850cedce10af0ed9b007036de077bf330e95a7a0f04b4d1abf1644f7adeedd413ee90373cb2242c84a21bc10ccd1fadb9d00", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e543750b445bc69c3e80816c9c872a904b4b23b99a61f69acb09d60a3316edf8f00be834b9f5e060080541c4c9f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000337a39bd3f6b7e9baed668d597aa6dae34bcfb6ea4cdcf7dd36cd1b67a9584116aa57f38b82fee8df91021bc1708d8347b84262ada79b95d6aea5f9ace0b8c36040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0402634f484761b4d83aaff08a0ef36701ae097d07202cb480352c7e943bd63992ee483b6b5ca736e417d6042a23b3933f760c5c2e4c800a4f4352c8f5d58925b02", + "items": [ + "3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee200000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"fa8082b3bc1d39fa28e739f0c0ff6fcd\",\"sn\":0,\"ts_ms\":1792888120100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"fa8082b3bc1d39fa28e739f0c0ff6fcd\",\"sn\":1,\"ts_ms\":1792888120150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"fa8082b3bc1d39fa28e739f0c0ff6fcd\",\"sn\":2,\"ts_ms\":1792888120250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"fa8082b3bc1d39fa28e739f0c0ff6fcd\",\"sn\":3,\"ts_ms\":1792888120520,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"fa8082b3bc1d39fa28e739f0c0ff6fcd\",\"sn\":4,\"ts_ms\":1792888159000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"fa8082b3bc1d39fa28e739f0c0ff6fcd\",sn=0,st=l,sta=s,ts=1792888120100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"fa8082b3bc1d39fa28e739f0c0ff6fcd\",sn=1,st=l,ts=1792888120150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",sf=h,sid=\"fa8082b3bc1d39fa28e739f0c0ff6fcd\",sn=2,st=l,ts=1792888120250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"fa8082b3bc1d39fa28e739f0c0ff6fcd\",sn=3,st=l,sta=p,ts=1792888120520,v=2\ne=ps,sf=h,sid=\"fa8082b3bc1d39fa28e739f0c0ff6fcd\",sn=4,st=l,sta=e,ts=1792888159000,v=2", + "outcome": { + "outcome": "accept", + "digest": "e407669d053bd0275cd85532f63bd100d2857906eab509c8064da7b3669f5578", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888120000000, + "end": 1792888160000000, + "ended": "ended", + "presented_ms": 38480, + "viewable_ms": 38480, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 420, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 130000 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_13", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2f9b85e3103d2215cc73626d587b8270d59e82bbe037aafa16f403e2badfe02403b8c1d0a698bd761a5b47d38282293b2010045174f9f5e0600009f79519f5e06000201140068747470733a2f2f656467652e6578616d706c6501004d1ce7cb263a5d85c510294b620e79e64ba3b368ba15788253fce5386dee4b180000010000001202000046960000102701ae010000000000000000000000000000000000000000000000000000d302f4c0ce7218cf45f750552cad4e49477083581b50239f1333d47693ba5dd60000000000000000000000000000000000000000000000000000000000000000020300000000000000000004000000000000000000000001b6545273b43bd577b2eca66fbe0de6d9db3c1ee3de5f10b169d09fd940ed6de82e929db3cc73ccf3cf13ade83238b6906e10dba77cce4398fd7c7aca27f500", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6ef9b85e3103d2215cc73626d587b8270d59e82bbe037aafa16f403e2badfe02400045174f9f5e060080dbaf4f9f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000a512307eb525ef68137406bb53edc1cfd963fe40ad9e3b0b1d80c578766f02648514c44dc900d081093ddf8e6883676f5c9d06e065ed016d6254c2d0a80639f5040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040fdfa3ba70361a2eb611315dfd1ee8d10955216de2e94ee5b2f77fc6291d3fe72ead72779a235751e3603e5a325096c9fc3cece45b7fb2236d4b43abedffd9d0f", + "items": [ + "90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d8100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"3b8c1d0a698bd761a5b47d38282293b2\",\"sn\":0,\"ts_ms\":1792888180100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"3b8c1d0a698bd761a5b47d38282293b2\",\"sn\":1,\"ts_ms\":1792888180150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"3b8c1d0a698bd761a5b47d38282293b2\",\"sn\":2,\"ts_ms\":1792888180250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"3b8c1d0a698bd761a5b47d38282293b2\",\"sn\":3,\"ts_ms\":1792888180530,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"3b8c1d0a698bd761a5b47d38282293b2\",\"sn\":4,\"ts_ms\":1792888219000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"3b8c1d0a698bd761a5b47d38282293b2\",sn=0,st=l,sta=s,ts=1792888180100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"3b8c1d0a698bd761a5b47d38282293b2\",sn=1,st=l,ts=1792888180150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",sf=h,sid=\"3b8c1d0a698bd761a5b47d38282293b2\",sn=2,st=l,ts=1792888180250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"3b8c1d0a698bd761a5b47d38282293b2\",sn=3,st=l,sta=p,ts=1792888180530,v=2\ne=ps,sf=h,sid=\"3b8c1d0a698bd761a5b47d38282293b2\",sn=4,st=l,sta=e,ts=1792888219000,v=2", + "outcome": { + "outcome": "accept", + "digest": "4feef9d58144eae0813b5a42a27e0e7e906306f3045f4d8c3a0d542b922f6742", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888180000000, + "end": 1792888220000000, + "ended": "user", + "presented_ms": 38470, + "viewable_ms": 38470, + "audible_ms": 38470, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 430, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_14", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2c5864d05689f81d1908008a436da82417aa269fe94269a05aa9db68a1455d958ecef73715336fac0fe2a10f143e381820100ccaa529f5e060000260d559f5e06000201140068747470733a2f2f656467652e6578616d706c650100409ea936c8500238b63a600864cad0d333956ebf27408f8716fd15945e57a6140000010000001c0200003c960000d00701b8010000000000000000000000000000000000000000000000000000f2ae60080d3333f3cd5103b531e9ee5bc4e0c8a028de86912eb82996f121cb6900000000000000000000000000000000000000000000000000000000000000000203f04902000000000003040000000000000000000000133e776a707c190531713b481a8a15d714733db1dc0cc4e1374b5d4961544016d24a1aab6250ef1d64d42e72a07a32be062a6793020204591bfb018eff5b990b", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6ec5864d05689f81d1908008a436da82417aa269fe94269a05aa9db68a1455d95800ccaa529f5e0600806243539f5e060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000200300000000000038d05149bc5379282245bcf7547fc45a42705115272c20886ddaf10872c388cb9b636335eafac219654e59520ed4ab116ce4e164055dac0d8927a1fceb81ca03040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040f6f6c1f34c6defaba35b008473aae793242da52043c81b7af1b716003ce743fbb3be26c73536017c5154cb5d283403a970e4716cbca6adb4cef4ee8e6d9a4c0b", + "items": [ + "d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e5100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ecef73715336fac0fe2a10f143e38182\",\"sn\":0,\"ts_ms\":1792888240100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ecef73715336fac0fe2a10f143e38182\",\"sn\":1,\"ts_ms\":1792888240150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ecef73715336fac0fe2a10f143e38182\",\"sn\":2,\"ts_ms\":1792888240250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ecef73715336fac0fe2a10f143e38182\",\"sn\":3,\"ts_ms\":1792888240540,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ecef73715336fac0fe2a10f143e38182\",\"sn\":4,\"ts_ms\":1792888279000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"ecef73715336fac0fe2a10f143e38182\",sn=0,st=l,sta=s,ts=1792888240100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"ecef73715336fac0fe2a10f143e38182\",sn=1,st=l,ts=1792888240150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",sf=h,sid=\"ecef73715336fac0fe2a10f143e38182\",sn=2,st=l,ts=1792888240250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"ecef73715336fac0fe2a10f143e38182\",sn=3,st=l,sta=p,ts=1792888240540,v=2\ne=ps,sf=h,sid=\"ecef73715336fac0fe2a10f143e38182\",sn=4,st=l,sta=e,ts=1792888279000,v=2", + "outcome": { + "outcome": "accept", + "digest": "99ec0bb741a933eb0ae42cdafa84f9af2903ea1d1654d23aac6942bd5b2c515e", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888240000000, + "end": 1792888280000000, + "ended": "user", + "presented_ms": 38460, + "viewable_ms": 0, + "audible_ms": 38460, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 440, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 150000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_15", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2148613594d1f44acce2c26ba0201695fe1370355dcecabf92ea98b5dfcc02b6f5372a001ff270b9bebbcd9f92f52534b0100533e569f5e060000ada0589f5e06000201140068747470733a2f2f656467652e6578616d706c650100eebbc49ccde8a7040740cdd74bf660c80dbba6982d7dd0f026e0f82f87e3ff8800000200000026020000ea240000102701c02b0000986c0000102701c201000001000000b00400000000000000000000000000000000000063bf2606e84c7571f3ca4471d614ae3575848181336dea30485fe7f127c2e4110000000000000000000000000000000000000000000000000000000000000000010300000000000000000000008b22176abc719d61efe5a45734e4142693f79e5e9fecdb7b53b3f3988cf8a12b725257da981ad946e2e6e8fe9837e84a476d6cc332aa996d1e06cce086af7603", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e148613594d1f44acce2c26ba0201695fe1370355dcecabf92ea98b5dfcc02b6f00533e569f5e060080e9d6569f5e06000000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000020030000000000000ccbb165ec882a87aaf3b836c552777becb7ccf33ccf7407a79266650352f9f2d8e7c3f3c74b1bac1233221b4db12d4de07706cbf32a71c2836538ec799c6664040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040c9eab59595e13962b96ab34319c38fbd7a35ad66018c805c3efc2c8bcc3e60a135989c78c52c80a802a4e156df01f0fad0b67b31a67cfc87a171aa5af9d1e90b", + "items": [ + "c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c00000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"5372a001ff270b9bebbcd9f92f52534b\",\"sn\":0,\"ts_ms\":1792888300100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"5372a001ff270b9bebbcd9f92f52534b\",\"sn\":1,\"ts_ms\":1792888300150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"5372a001ff270b9bebbcd9f92f52534b\",\"sn\":2,\"ts_ms\":1792888300250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"5372a001ff270b9bebbcd9f92f52534b\",\"sn\":3,\"ts_ms\":1792888300550,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"5372a001ff270b9bebbcd9f92f52534b\",\"sn\":4,\"ts_ms\":1792888310000,\"event\":\"stall-start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"5372a001ff270b9bebbcd9f92f52534b\",\"sn\":5,\"ts_ms\":1792888311200,\"event\":\"stall-end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"5372a001ff270b9bebbcd9f92f52534b\",\"sn\":6,\"ts_ms\":1792888339000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"5372a001ff270b9bebbcd9f92f52534b\",sn=0,st=l,sta=s,ts=1792888300100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"5372a001ff270b9bebbcd9f92f52534b\",sn=1,st=l,ts=1792888300150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",sf=h,sid=\"5372a001ff270b9bebbcd9f92f52534b\",sn=2,st=l,ts=1792888300250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"5372a001ff270b9bebbcd9f92f52534b\",sn=3,st=l,sta=p,ts=1792888300550,v=2\ne=ps,sf=h,sid=\"5372a001ff270b9bebbcd9f92f52534b\",sn=4,st=l,sta=r,ts=1792888310000,v=2\ne=ps,sf=h,sid=\"5372a001ff270b9bebbcd9f92f52534b\",sn=5,st=l,sta=p,ts=1792888311200,v=2\ne=ps,sf=h,sid=\"5372a001ff270b9bebbcd9f92f52534b\",sn=6,st=l,sta=e,ts=1792888339000,v=2", + "outcome": { + "outcome": "accept", + "digest": "6d532dbc326153280fbbf8dd85a9fc0206698751e7cdfa17f86c4eeea5728da1", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888300000000, + "end": 1792888340000000, + "ended": "user", + "presented_ms": 37250, + "viewable_ms": 37250, + "audible_ms": 37250, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 450, + "stalls": 1, + "stall_ms": 1200, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_16", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2dc2f847d6d419930d12b4e087144fdce5def0cffeb9e3a044bb876f292e54d5272fff799fee06be8e33152ad5ee5f0890100dad1599f5e06000034345c9f5e06000101140068747470733a2f2f656467652e6578616d706c6501009b74139b739cdde22648debb922219e1a57693c52ee6d7736b73389e7c7a5dd10000010000003002000028960000102701cc0100000000000000000000000000000000000000000000000000001b60b5f75b27174eeb7131766f67b02f444dacdd18d656d06a9ec3c055db0902000000000000000000000000000000000000000000000000000000000000000002031098020000000000030400000000000000000000007402a159a5c8db090c569e2eb790169fd92a889adb47f8f8c81a66c8993910e51afb8775578031f859e357a71da6f68d1083a2bddfe77e30a49e144eb3690a0e", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6edc2f847d6d419930d12b4e087144fdce5def0cffeb9e3a044bb876f292e54d5200dad1599f5e060080706a5a9f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000337a39bd3f6b7e9baed668d597aa6dae34bcfb6ea4cdcf7dd36cd1b67a9584116aa57f38b82fee8df91021bc1708d8347b84262ada79b95d6aea5f9ace0b8c36040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040633cdba32e391e62fed023be48bc0fa8d87e3816e88773209eb2a8ab0028960433bbf769fa649c3d71cf68c1799a963678a3e9f6e44fa23b3bd999c549743c01", + "items": [ + "3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee200000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"72fff799fee06be8e33152ad5ee5f089\",\"sn\":0,\"ts_ms\":1792888360100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"72fff799fee06be8e33152ad5ee5f089\",\"sn\":1,\"ts_ms\":1792888360150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"72fff799fee06be8e33152ad5ee5f089\",\"sn\":2,\"ts_ms\":1792888360250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"72fff799fee06be8e33152ad5ee5f089\",\"sn\":3,\"ts_ms\":1792888360560,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"72fff799fee06be8e33152ad5ee5f089\",\"sn\":4,\"ts_ms\":1792888399000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"72fff799fee06be8e33152ad5ee5f089\",sn=0,st=l,sta=s,ts=1792888360100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"72fff799fee06be8e33152ad5ee5f089\",sn=1,st=l,ts=1792888360150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",sf=h,sid=\"72fff799fee06be8e33152ad5ee5f089\",sn=2,st=l,ts=1792888360250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"72fff799fee06be8e33152ad5ee5f089\",sn=3,st=l,sta=p,ts=1792888360560,v=2\ne=ps,sf=h,sid=\"72fff799fee06be8e33152ad5ee5f089\",sn=4,st=l,sta=e,ts=1792888399000,v=2", + "outcome": { + "outcome": "accept", + "digest": "9e386f097c84a5c44f9a521fe3bd757f43f04312f768e39c9ebf90499afb9f0b", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888360000000, + "end": 1792888400000000, + "ended": "ended", + "presented_ms": 38440, + "viewable_ms": 38440, + "audible_ms": 38440, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 460, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 170000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_17", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2640cc096280157e646ca0dfd66b9c1cbbcb5b38605f20bcd8e721321c7556dfddc33a6431eaa98e88dc728f885e13619010061655d9f5e060000bbc75f9f5e06000201140068747470733a2f2f656467652e6578616d706c650100a0cbd45c0b9bcbd963e707f54b20b68219e30d01e61db52e136f8211f5d1c5a60000010000003a0200001e960000102701d60100000000000000000000000000000000000000000000000000004fc25d76539251a01004d0cfe5c9f166edd82d33ac28fe682fc0ee7968a5535300000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000e1f1e64d6dd9b9d8add487f50d530fdf47feedf1da662bbc59238e7ec883d2d2a3811655535f65d92c169b2ff0e627d07caa51c9ed9dcf0d5a477b4401368109", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e640cc096280157e646ca0dfd66b9c1cbbcb5b38605f20bcd8e721321c7556dfd0061655d9f5e060080f7fd5d9f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000a512307eb525ef68137406bb53edc1cfd963fe40ad9e3b0b1d80c578766f02648514c44dc900d081093ddf8e6883676f5c9d06e065ed016d6254c2d0a80639f5040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040e8ab21fd0c7ce3f2d1aa162f9ffe318b35a2d0dc35441d112e9c6d8c5cc6eba4514c67d82d31809c16a85ed5dcb55442d84bcf1be57e5e62fe41345a77553d07", + "items": [ + "90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d8100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"dc33a6431eaa98e88dc728f885e13619\",\"sn\":0,\"ts_ms\":1792888420100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"dc33a6431eaa98e88dc728f885e13619\",\"sn\":1,\"ts_ms\":1792888420150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"dc33a6431eaa98e88dc728f885e13619\",\"sn\":2,\"ts_ms\":1792888420250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"dc33a6431eaa98e88dc728f885e13619\",\"sn\":3,\"ts_ms\":1792888420570,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"dc33a6431eaa98e88dc728f885e13619\",\"sn\":4,\"ts_ms\":1792888459000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"dc33a6431eaa98e88dc728f885e13619\",sn=0,st=l,sta=s,ts=1792888420100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"dc33a6431eaa98e88dc728f885e13619\",sn=1,st=l,ts=1792888420150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",sf=h,sid=\"dc33a6431eaa98e88dc728f885e13619\",sn=2,st=l,ts=1792888420250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"dc33a6431eaa98e88dc728f885e13619\",sn=3,st=l,sta=p,ts=1792888420570,v=2\ne=ps,sf=h,sid=\"dc33a6431eaa98e88dc728f885e13619\",sn=4,st=l,sta=e,ts=1792888459000,v=2", + "outcome": { + "outcome": "accept", + "digest": "99f8dfb0c4a5b2fbcbe485a101f46afde069548ed4db0a75fbc49727a0cb6b21", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888420000000, + "end": 1792888460000000, + "ended": "user", + "presented_ms": 38430, + "viewable_ms": 38430, + "audible_ms": 38430, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 470, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_18", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2ec43d08a64e338522b0421ccda5bd66e536b92fb4644ce78f8fe62d7330c456e5fbd4a63bae9fe1446c76a64e1f66d770100e8f8609f5e060000425b639f5e06000201140068747470733a2f2f656467652e6578616d706c650100a44cd1c078e9f269c980927aba6757f8dbc0646adf6db5a00df5855dc8296aef0000010000004402000014960000102700e0010000000000000000000000000000000000000000000000000000637720c02eb1bdacd017f5e7ea8aa9a6a946d955602eeb646f37cdaf75f20de50000000000000000000000000000000000000000000000000000000000000000010330e6020000000000030000ff4728ee0bd31d87775ba140d96d6b7424edaaa0e2199c296af3d7b372ec0595df278063b9c88ffc3d78cc44cbf26c8cc9e07fd9d039e5d26d20076ab4e84806", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6eec43d08a64e338522b0421ccda5bd66e536b92fb4644ce78f8fe62d7330c456e00e8f8609f5e0600807e91619f5e060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000200300000000000038d05149bc5379282245bcf7547fc45a42705115272c20886ddaf10872c388cb9b636335eafac219654e59520ed4ab116ce4e164055dac0d8927a1fceb81ca03040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040235df1f44d59648b37c397fcc6be4ea21acd5507831dfb5560b59ebf11d6d6d9fdfd3e5e9b2eb5c12e579f7219e51a84364f56f7ad388eaef3355f513d907c07", + "items": [ + "d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e5100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"5fbd4a63bae9fe1446c76a64e1f66d77\",\"sn\":0,\"ts_ms\":1792888480100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"5fbd4a63bae9fe1446c76a64e1f66d77\",\"sn\":1,\"ts_ms\":1792888480150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"5fbd4a63bae9fe1446c76a64e1f66d77\",\"sn\":2,\"ts_ms\":1792888480250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"5fbd4a63bae9fe1446c76a64e1f66d77\",\"sn\":3,\"ts_ms\":1792888480580,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"5fbd4a63bae9fe1446c76a64e1f66d77\",\"sn\":4,\"ts_ms\":1792888519000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"5fbd4a63bae9fe1446c76a64e1f66d77\",sn=0,st=l,sta=s,ts=1792888480100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"5fbd4a63bae9fe1446c76a64e1f66d77\",sn=1,st=l,ts=1792888480150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",sf=h,sid=\"5fbd4a63bae9fe1446c76a64e1f66d77\",sn=2,st=l,ts=1792888480250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"5fbd4a63bae9fe1446c76a64e1f66d77\",sn=3,st=l,sta=p,ts=1792888480580,v=2\ne=ps,sf=h,sid=\"5fbd4a63bae9fe1446c76a64e1f66d77\",sn=4,st=l,sta=e,ts=1792888519000,v=2", + "outcome": { + "outcome": "accept", + "digest": "cc8313a12a25a0cac0f2987fca1b4697c48299217aadb21c97afd9137a865941", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888480000000, + "end": 1792888520000000, + "ended": "user", + "presented_ms": 38420, + "viewable_ms": 38420, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 480, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 190000 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_19", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2794651631af8bed836fcd6168da9d5c26dbbff505a6bad4d5dbc89a62be3083d4088629f3ddf6119c69bcdcf09aebb1401006f8c649f5e060000c9ee669f5e06000201140068747470733a2f2f656467652e6578616d706c650100a07bf0d816550f928b03e9107e25e4f31258f94e694de67de00e0c21548997a10000010000004e0200000a960000102701ea01000000000000000000000000000000000000000000000000000088cab0beccd30a2ff70372667db194a2583d26b0f9ba9836073f12f7587dffe500000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000500aaf86a25755760a9d3d5b80bce269d70fb5d7ed78fbf175d26842980e411d353001426e5ba9c9061e7ca7f4efe07ab832863e9d8e40add9c5d2941668d303", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e794651631af8bed836fcd6168da9d5c26dbbff505a6bad4d5dbc89a62be3083d006f8c649f5e0600800525659f5e06000000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000020030000000000000ccbb165ec882a87aaf3b836c552777becb7ccf33ccf7407a79266650352f9f2d8e7c3f3c74b1bac1233221b4db12d4de07706cbf32a71c2836538ec799c6664040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0400b0ba73a69110a37e9cdb419263f16629ae6b55a71284a67e284959000f28e59da8e0e1b41ccbf030cbc5917867292f6ad949e571798d0e012095eaa2aed7107", + "items": [ + "c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c00000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"4088629f3ddf6119c69bcdcf09aebb14\",\"sn\":0,\"ts_ms\":1792888540100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"4088629f3ddf6119c69bcdcf09aebb14\",\"sn\":1,\"ts_ms\":1792888540150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"4088629f3ddf6119c69bcdcf09aebb14\",\"sn\":2,\"ts_ms\":1792888540250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"4088629f3ddf6119c69bcdcf09aebb14\",\"sn\":3,\"ts_ms\":1792888540590,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"4088629f3ddf6119c69bcdcf09aebb14\",\"sn\":4,\"ts_ms\":1792888579000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"4088629f3ddf6119c69bcdcf09aebb14\",sn=0,st=l,sta=s,ts=1792888540100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"4088629f3ddf6119c69bcdcf09aebb14\",sn=1,st=l,ts=1792888540150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",sf=h,sid=\"4088629f3ddf6119c69bcdcf09aebb14\",sn=2,st=l,ts=1792888540250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"4088629f3ddf6119c69bcdcf09aebb14\",sn=3,st=l,sta=p,ts=1792888540590,v=2\ne=ps,sf=h,sid=\"4088629f3ddf6119c69bcdcf09aebb14\",sn=4,st=l,sta=e,ts=1792888579000,v=2", + "outcome": { + "outcome": "accept", + "digest": "bbd40219e0061f3973d2488b88dd3256af86bc36d5abe4d521ee59467704b5c2", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888540000000, + "end": 1792888580000000, + "ended": "user", + "presented_ms": 38410, + "viewable_ms": 38410, + "audible_ms": 38410, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 490, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_20", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2842dd38dd6cfb01775ffa9a56605e59464c53a87d4d4da8aa1b582be54a46f42d3cb43259b91f5ee20e22722d9122ed30100f61f689f5e06000050826a9f5e06000101140068747470733a2f2f656467652e6578616d706c650100155a9b91b0edfddc029c1e1655843be6f9c3b77506db1700980fd624cf90f5a300000200000058020000b8240000102701c02b0000986c0000102701f401000001000000b004000000000000000000000000000000000000f3ac4e2a8cfed53237469b7939d0838e4ba3b64c9d24fb828453958c342b494000000000000000000000000000000000000000000000000000000000000000000203503403000000000003040000000000000000000000d0a6ee8637b6aa349168c7849bc0f3897c11588eb0c563eb55080baa2b698edca372fbaa3db777ba695f57315b18ffded17753e37ce855b8764f9cb2ca80710a", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e842dd38dd6cfb01775ffa9a56605e59464c53a87d4d4da8aa1b582be54a46f4200f61f689f5e0600808cb8689f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000337a39bd3f6b7e9baed668d597aa6dae34bcfb6ea4cdcf7dd36cd1b67a9584116aa57f38b82fee8df91021bc1708d8347b84262ada79b95d6aea5f9ace0b8c36040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040ecdfdef36082dfe160dfeb5ca4238747a6c6ad207c3a294c5476e2e9e435f97388bdc1aafad4c9d466d8b7fece3b57bce710637f2c978f4803e24e405afeb50a", + "items": [ + "3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee200000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"d3cb43259b91f5ee20e22722d9122ed3\",\"sn\":0,\"ts_ms\":1792888600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d3cb43259b91f5ee20e22722d9122ed3\",\"sn\":1,\"ts_ms\":1792888600150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d3cb43259b91f5ee20e22722d9122ed3\",\"sn\":2,\"ts_ms\":1792888600250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d3cb43259b91f5ee20e22722d9122ed3\",\"sn\":3,\"ts_ms\":1792888600600,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d3cb43259b91f5ee20e22722d9122ed3\",\"sn\":4,\"ts_ms\":1792888610000,\"event\":\"stall-start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d3cb43259b91f5ee20e22722d9122ed3\",\"sn\":5,\"ts_ms\":1792888611200,\"event\":\"stall-end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d3cb43259b91f5ee20e22722d9122ed3\",\"sn\":6,\"ts_ms\":1792888639000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"d3cb43259b91f5ee20e22722d9122ed3\",sn=0,st=l,sta=s,ts=1792888600100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"d3cb43259b91f5ee20e22722d9122ed3\",sn=1,st=l,ts=1792888600150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",sf=h,sid=\"d3cb43259b91f5ee20e22722d9122ed3\",sn=2,st=l,ts=1792888600250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"d3cb43259b91f5ee20e22722d9122ed3\",sn=3,st=l,sta=p,ts=1792888600600,v=2\ne=ps,sf=h,sid=\"d3cb43259b91f5ee20e22722d9122ed3\",sn=4,st=l,sta=r,ts=1792888610000,v=2\ne=ps,sf=h,sid=\"d3cb43259b91f5ee20e22722d9122ed3\",sn=5,st=l,sta=p,ts=1792888611200,v=2\ne=ps,sf=h,sid=\"d3cb43259b91f5ee20e22722d9122ed3\",sn=6,st=l,sta=e,ts=1792888639000,v=2", + "outcome": { + "outcome": "accept", + "digest": "03e191a8d55c27f174ba051562012cb452d4b0342c43e0aacfe089977a3a0f42", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888600000000, + "end": 1792888640000000, + "ended": "ended", + "presented_ms": 37200, + "viewable_ms": 37200, + "audible_ms": 37200, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 500, + "stalls": 1, + "stall_ms": 1200, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 210000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_21", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2ac7eaa63beb30946982e446db17cb62ece6b7a82af273b0778376e3cde800b821348fa6c2ac95d4f672cc6f38dbe134c01007db36b9f5e060000d7156e9f5e06000201140068747470733a2f2f656467652e6578616d706c650100d7feb8531880bb75b879396f6b691bc6a4b4e463d61cbcec91330bf52350aa4a00000100000062020000f6950000d00700fe010000000000000000000000000000000000000000000000000000b3da88f6d972fa4af3ae7bd8463aaa1e7ccfaeeff0824441e571d3873c7d61dc000000000000000000000000000000000000000000000000000000000000000001030000000000000000000000934b587f6fc2631b8aed47fadeadadb7bf322ac54175f79304998e4a3c80bee04f642a8c45293cf66316dd142cfd1f769152349c97cffd029796dea42878c906", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6eac7eaa63beb30946982e446db17cb62ece6b7a82af273b0778376e3cde800b82007db36b9f5e060080134c6c9f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000a512307eb525ef68137406bb53edc1cfd963fe40ad9e3b0b1d80c578766f02648514c44dc900d081093ddf8e6883676f5c9d06e065ed016d6254c2d0a80639f5040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040871e461d7068b4e204c43d3517ce2358da1e97f2be3cc7cf0271310f172adb402fa4908e085b0c96bdcbbd6f7505aff837e78299252812cff4e6de6eaf89510f", + "items": [ + "90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d8100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"1348fa6c2ac95d4f672cc6f38dbe134c\",\"sn\":0,\"ts_ms\":1792888660100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"1348fa6c2ac95d4f672cc6f38dbe134c\",\"sn\":1,\"ts_ms\":1792888660150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"1348fa6c2ac95d4f672cc6f38dbe134c\",\"sn\":2,\"ts_ms\":1792888660250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"1348fa6c2ac95d4f672cc6f38dbe134c\",\"sn\":3,\"ts_ms\":1792888660610,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"1348fa6c2ac95d4f672cc6f38dbe134c\",\"sn\":4,\"ts_ms\":1792888699000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"1348fa6c2ac95d4f672cc6f38dbe134c\",sn=0,st=l,sta=s,ts=1792888660100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"1348fa6c2ac95d4f672cc6f38dbe134c\",sn=1,st=l,ts=1792888660150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",sf=h,sid=\"1348fa6c2ac95d4f672cc6f38dbe134c\",sn=2,st=l,ts=1792888660250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"1348fa6c2ac95d4f672cc6f38dbe134c\",sn=3,st=l,sta=p,ts=1792888660610,v=2\ne=ps,sf=h,sid=\"1348fa6c2ac95d4f672cc6f38dbe134c\",sn=4,st=l,sta=e,ts=1792888699000,v=2", + "outcome": { + "outcome": "accept", + "digest": "7c2c58e9a980509c43c60309311c2b494969b4c156dd61275e1eb0c3d9245683", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888660000000, + "end": 1792888700000000, + "ended": "user", + "presented_ms": 38390, + "viewable_ms": 0, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 510, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_22", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2b6f4bb3451f727d96cd48a7448523ec0ee14e3c9f42a56419e875c23df7cdefee82cd38bd42a6c1ac904a5d53b8a07e3010004476f9f5e0600005ea9719f5e06000201140068747470733a2f2f656467652e6578616d706c65010011c2ac7da6a9e3ae0ec7f1c905bc34dba3928eb0eaf89f3195b5eb4cda4eee4e0000010000006c020000ec95000010270108020000000000000000000000000000000000000000000000000000309b18978025806eafe541a4a8bce526c6a5f073e4f090a16f86e5db81a9756e00000000000000000000000000000000000000000000000000000000000000000203708203000000000003040000000000000000000000d850e6fb8a983653cd156f152275316b608a0cb2628f710c0ec6290539285c3eba4d68968737bde02bcf75f25d2a71c9b6a6b3b8cf317e98d4f50ef8ac40c706", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6eb6f4bb3451f727d96cd48a7448523ec0ee14e3c9f42a56419e875c23df7cdefe0004476f9f5e0600809adf6f9f5e060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000200300000000000038d05149bc5379282245bcf7547fc45a42705115272c20886ddaf10872c388cb9b636335eafac219654e59520ed4ab116ce4e164055dac0d8927a1fceb81ca03040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040b94f27caec9fdb6fb7f4970d4b1eb4596773837ef907b61950dda9853281d2adfec8c81e678d16c04e6cadcfb1d5faed8537b5ba17220051cc661e52647e7300", + "items": [ + "d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e5100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"e82cd38bd42a6c1ac904a5d53b8a07e3\",\"sn\":0,\"ts_ms\":1792888720100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"e82cd38bd42a6c1ac904a5d53b8a07e3\",\"sn\":1,\"ts_ms\":1792888720150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"e82cd38bd42a6c1ac904a5d53b8a07e3\",\"sn\":2,\"ts_ms\":1792888720250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"e82cd38bd42a6c1ac904a5d53b8a07e3\",\"sn\":3,\"ts_ms\":1792888720620,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"e82cd38bd42a6c1ac904a5d53b8a07e3\",\"sn\":4,\"ts_ms\":1792888759000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"e82cd38bd42a6c1ac904a5d53b8a07e3\",sn=0,st=l,sta=s,ts=1792888720100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"e82cd38bd42a6c1ac904a5d53b8a07e3\",sn=1,st=l,ts=1792888720150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",sf=h,sid=\"e82cd38bd42a6c1ac904a5d53b8a07e3\",sn=2,st=l,ts=1792888720250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"e82cd38bd42a6c1ac904a5d53b8a07e3\",sn=3,st=l,sta=p,ts=1792888720620,v=2\ne=ps,sf=h,sid=\"e82cd38bd42a6c1ac904a5d53b8a07e3\",sn=4,st=l,sta=e,ts=1792888759000,v=2", + "outcome": { + "outcome": "accept", + "digest": "2dc51aef6cb1c82464cbb63af19b1c4c8b138ecd8e48a2936e368d31d9fe4d04", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888720000000, + "end": 1792888760000000, + "ended": "user", + "presented_ms": 38380, + "viewable_ms": 38380, + "audible_ms": 38380, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 520, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 230000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_23", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e20db3c92e0d9badbf25dccc21591821a18306227b9145836d94bcf3423ce5ef7068caa69ac75beac8271b89a597f89cc001008bda729f5e060000e53c759f5e06000201140068747470733a2f2f656467652e6578616d706c65010054a8f7317053a871f2d28d4bc9814c4d54bccf54f0440fdb3aed3161c0b15c1900000100000076020000e29500001027011202000000000000000000000000000000000000000000000000000036cddf50e32e50d2c822e40e5ea694c9ba11c6601137fbd8cad1fd7ef073df9300000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000f1a7e7ae40c1753b8542a12685acd9a237e09c8a9c589badc983b445c91fd5936538b7be0047ef4a9ccb7a7e5250ca33be9dd0dc8bfa73e3f62f94ee0884a308", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e0db3c92e0d9badbf25dccc21591821a18306227b9145836d94bcf3423ce5ef70008bda729f5e0600802173739f5e06000000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000020030000000000000ccbb165ec882a87aaf3b836c552777becb7ccf33ccf7407a79266650352f9f2d8e7c3f3c74b1bac1233221b4db12d4de07706cbf32a71c2836538ec799c6664040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a04065f4f220c4dd92d1e76f23cc9048ef97de619e5aa02a28ada780b9d7369d5ab1e0b72131ba8f84eeefe44bbb34a286348107274a36886ad7c769a6bc1c462f07", + "items": [ + "c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c00000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"68caa69ac75beac8271b89a597f89cc0\",\"sn\":0,\"ts_ms\":1792888780100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"68caa69ac75beac8271b89a597f89cc0\",\"sn\":1,\"ts_ms\":1792888780150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"68caa69ac75beac8271b89a597f89cc0\",\"sn\":2,\"ts_ms\":1792888780250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"68caa69ac75beac8271b89a597f89cc0\",\"sn\":3,\"ts_ms\":1792888780630,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"68caa69ac75beac8271b89a597f89cc0\",\"sn\":4,\"ts_ms\":1792888819000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"68caa69ac75beac8271b89a597f89cc0\",sn=0,st=l,sta=s,ts=1792888780100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"68caa69ac75beac8271b89a597f89cc0\",sn=1,st=l,ts=1792888780150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",sf=h,sid=\"68caa69ac75beac8271b89a597f89cc0\",sn=2,st=l,ts=1792888780250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"68caa69ac75beac8271b89a597f89cc0\",sn=3,st=l,sta=p,ts=1792888780630,v=2\ne=ps,sf=h,sid=\"68caa69ac75beac8271b89a597f89cc0\",sn=4,st=l,sta=e,ts=1792888819000,v=2", + "outcome": { + "outcome": "accept", + "digest": "71c2066117177185d7f76f773645315f1aba27c00b0b73d138f2abfb8b43efbc", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888780000000, + "end": 1792888820000000, + "ended": "user", + "presented_ms": 38370, + "viewable_ms": 38370, + "audible_ms": 38370, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 530, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_24", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e22625d79b13d7a1bdbb8901f94c1c6105ff73f07df8118c5256dfad5beb8ebb70efc632928654105cd93c1ba833bca5190100126e769f5e0600006cd0789f5e06000101140068747470733a2f2f656467652e6578616d706c650100002d04cb2d4240c8535fdfba7046848aa7b365bc8c064697755123b492eae9be00000100000080020000d89500001027001c020000000000000000000000000000000000000000000000000000b46f843275a8058abe935b1307ffdd6e3a7f93b9cd1152245fa105042f24183c0000000000000000000000000000000000000000000000000000000000000000010390d003000000000003000044d420f34ead9bea84733c4e72e9281701aeaad18bb5f171f64d06327b50447d83a1a7e32c6e58109a58924d632b4e1eac6a92c81168c943edf074a2f0440d08", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e2625d79b13d7a1bdbb8901f94c1c6105ff73f07df8118c5256dfad5beb8ebb7000126e769f5e060080a806779f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000337a39bd3f6b7e9baed668d597aa6dae34bcfb6ea4cdcf7dd36cd1b67a9584116aa57f38b82fee8df91021bc1708d8347b84262ada79b95d6aea5f9ace0b8c36040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0401af4c27c4be5c01d994e9773e71674dfbc673695690214d7769617d6600a58a71480aff4dbda3eb920dc3b3bdfe83a1ec2b24545e6f1e56a1949f4c5dfae0d0d", + "items": [ + "3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee200000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"efc632928654105cd93c1ba833bca519\",\"sn\":0,\"ts_ms\":1792888840100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"efc632928654105cd93c1ba833bca519\",\"sn\":1,\"ts_ms\":1792888840150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"efc632928654105cd93c1ba833bca519\",\"sn\":2,\"ts_ms\":1792888840250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"efc632928654105cd93c1ba833bca519\",\"sn\":3,\"ts_ms\":1792888840640,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"efc632928654105cd93c1ba833bca519\",\"sn\":4,\"ts_ms\":1792888879000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"efc632928654105cd93c1ba833bca519\",sn=0,st=l,sta=s,ts=1792888840100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"efc632928654105cd93c1ba833bca519\",sn=1,st=l,ts=1792888840150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",sf=h,sid=\"efc632928654105cd93c1ba833bca519\",sn=2,st=l,ts=1792888840250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"efc632928654105cd93c1ba833bca519\",sn=3,st=l,sta=p,ts=1792888840640,v=2\ne=ps,sf=h,sid=\"efc632928654105cd93c1ba833bca519\",sn=4,st=l,sta=e,ts=1792888879000,v=2", + "outcome": { + "outcome": "accept", + "digest": "b3728bea132802736c6da94ff3fe219845cd75b5b08bf339d3bbd7be0f6019ea", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888840000000, + "end": 1792888880000000, + "ended": "ended", + "presented_ms": 38360, + "viewable_ms": 38360, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 540, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 250000 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_25", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2e6e0b718d8b99068d7f7ae301b4bce64cd0ecc0895259e6c138da269b73ac8c5e9a2046a70c4462e48c93bcf88dab24d010099017a9f5e060000f3637c9f5e06000201140068747470733a2f2f656467652e6578616d706c650100b0cb17305b5c57fcae85fe57180aba47c6eef4ff75056fa9ab00e61fda6164e00000020000008a02000086240000102701c02b0000986c00001027012602000001000000b004000000000000000000000000000000000000d76aea9b9b863792f65b0afc7fd6de195edb45d7ae23ae0249c6efb46f146b7700000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000e4203ef175274407e6434f9d2a183725aa024b190b5b55170d5a7c1d01a3d0fc85cdb18c57c51ec061afc863719c91c381d20a1246de92c8a1f28e09fe2b6006", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6ee6e0b718d8b99068d7f7ae301b4bce64cd0ecc0895259e6c138da269b73ac8c50099017a9f5e0600802f9a7a9f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000a512307eb525ef68137406bb53edc1cfd963fe40ad9e3b0b1d80c578766f02648514c44dc900d081093ddf8e6883676f5c9d06e065ed016d6254c2d0a80639f5040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040eb7342e6c6a92ef26cbda8eb282421e6a99e59673bf5569d0bf57f161833c4e07bd17aa197a038a5f32d003e6ad8b85e512ba1bdcee2a779ca2214b208a8c406", + "items": [ + "90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d8100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"e9a2046a70c4462e48c93bcf88dab24d\",\"sn\":0,\"ts_ms\":1792888900100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"e9a2046a70c4462e48c93bcf88dab24d\",\"sn\":1,\"ts_ms\":1792888900150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"e9a2046a70c4462e48c93bcf88dab24d\",\"sn\":2,\"ts_ms\":1792888900250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"e9a2046a70c4462e48c93bcf88dab24d\",\"sn\":3,\"ts_ms\":1792888900650,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"e9a2046a70c4462e48c93bcf88dab24d\",\"sn\":4,\"ts_ms\":1792888910000,\"event\":\"stall-start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"e9a2046a70c4462e48c93bcf88dab24d\",\"sn\":5,\"ts_ms\":1792888911200,\"event\":\"stall-end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"e9a2046a70c4462e48c93bcf88dab24d\",\"sn\":6,\"ts_ms\":1792888939000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"e9a2046a70c4462e48c93bcf88dab24d\",sn=0,st=l,sta=s,ts=1792888900100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"e9a2046a70c4462e48c93bcf88dab24d\",sn=1,st=l,ts=1792888900150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",sf=h,sid=\"e9a2046a70c4462e48c93bcf88dab24d\",sn=2,st=l,ts=1792888900250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"e9a2046a70c4462e48c93bcf88dab24d\",sn=3,st=l,sta=p,ts=1792888900650,v=2\ne=ps,sf=h,sid=\"e9a2046a70c4462e48c93bcf88dab24d\",sn=4,st=l,sta=r,ts=1792888910000,v=2\ne=ps,sf=h,sid=\"e9a2046a70c4462e48c93bcf88dab24d\",sn=5,st=l,sta=p,ts=1792888911200,v=2\ne=ps,sf=h,sid=\"e9a2046a70c4462e48c93bcf88dab24d\",sn=6,st=l,sta=e,ts=1792888939000,v=2", + "outcome": { + "outcome": "accept", + "digest": "15bc13deeee492f501b842aa425ba5a706f4a682cebafa20db31d319e258db9f", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888900000000, + "end": 1792888940000000, + "ended": "user", + "presented_ms": 37150, + "viewable_ms": 37150, + "audible_ms": 37150, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 550, + "stalls": 1, + "stall_ms": 1200, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_26", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e24c0155f9803056af45d690703bf089fe5f94c5aac8cda6745fe609d5c73c3c9bcb319c9d0621da572307719a0f1c8e77010020957d9f5e0600007af77f9f5e06000201140068747470733a2f2f656467652e6578616d706c65010068ae6da67e8ac81c540eff687c3cb94c44d81b4388506a9c66f7ba6a8a8af2f900000100000094020000c495000010270130020000000000000000000000000000000000000000000000000000bdf1b1a90aca7385765410540f5080f62f4e3653859e4ccc7dbb4d62877fb41100000000000000000000000000000000000000000000000000000000000000000203b01e04000000000003040000000000000000000000bd4fb34db1c4c9346baceea7182526da6fad34170cbfd5b36bb37344805b0b1ff8fe48447fd1d04ff16698296207ec4dfce1b58df183c5fad572903bf0942c03", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e4c0155f9803056af45d690703bf089fe5f94c5aac8cda6745fe609d5c73c3c9b0020957d9f5e060080b62d7e9f5e060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000200300000000000038d05149bc5379282245bcf7547fc45a42705115272c20886ddaf10872c388cb9b636335eafac219654e59520ed4ab116ce4e164055dac0d8927a1fceb81ca03040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a04034ed010b62924dddc6676aee11de97efcc5c1aa5e118f0f95b595a487a5ac7ebb0412c21d9cef26b9a3b1e56e214f674763f1051a4c1c6017cd18b2940b9fa0c", + "items": [ + "d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e5100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"cb319c9d0621da572307719a0f1c8e77\",\"sn\":0,\"ts_ms\":1792888960100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"cb319c9d0621da572307719a0f1c8e77\",\"sn\":1,\"ts_ms\":1792888960150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"cb319c9d0621da572307719a0f1c8e77\",\"sn\":2,\"ts_ms\":1792888960250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"cb319c9d0621da572307719a0f1c8e77\",\"sn\":3,\"ts_ms\":1792888960660,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"cb319c9d0621da572307719a0f1c8e77\",\"sn\":4,\"ts_ms\":1792888999000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"cb319c9d0621da572307719a0f1c8e77\",sn=0,st=l,sta=s,ts=1792888960100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"cb319c9d0621da572307719a0f1c8e77\",sn=1,st=l,ts=1792888960150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",sf=h,sid=\"cb319c9d0621da572307719a0f1c8e77\",sn=2,st=l,ts=1792888960250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"cb319c9d0621da572307719a0f1c8e77\",sn=3,st=l,sta=p,ts=1792888960660,v=2\ne=ps,sf=h,sid=\"cb319c9d0621da572307719a0f1c8e77\",sn=4,st=l,sta=e,ts=1792888999000,v=2", + "outcome": { + "outcome": "accept", + "digest": "16796597260c386dd97aaadbddcf95d1a4842d8d70c9273fe1c89a48a9668cc6", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792888960000000, + "end": 1792889000000000, + "ended": "user", + "presented_ms": 38340, + "viewable_ms": 38340, + "audible_ms": 38340, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 560, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 270000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_27", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e299a8582230ab710da129d398d0f545c2db6a0a116f1b594084a2513af19f2aa27885d29023a8e22c48d9fba585c247fe0100a728819f5e060000018b839f5e06000201140068747470733a2f2f656467652e6578616d706c6501002cafed50e3a7a7a072059752280194610d5a0e5d273c6d65fd40e8077babbab90000010000009e020000ba9500001027003a0200000000000000000000000000000000000000000000000000008f7cc9f372893aea9d9a7640a1e5759191f014378e43b2e684ca1c8fa54253f4000000000000000000000000000000000000000000000000000000000000000001030000000000000000000000e5a5aa5a8b632cb06aacce9d4eb7cbf0f4e6b0b7d377d570188e897ebb715b4c76465cab45c8b1585c9768b10df330d05714b78945b84ae40a1fbb00da8bb50c", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e99a8582230ab710da129d398d0f545c2db6a0a116f1b594084a2513af19f2aa200a728819f5e0600803dc1819f5e06000000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000020030000000000000ccbb165ec882a87aaf3b836c552777becb7ccf33ccf7407a79266650352f9f2d8e7c3f3c74b1bac1233221b4db12d4de07706cbf32a71c2836538ec799c6664040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040c5196196a7b5d67f962ba2bc3ee9b22edae5802a7c3b7f62fafec10617f89d8b6bf9a4e3c239f47b6d0d8421d4ff9590fdcf7346848144c9493550884729900b", + "items": [ + "c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c00000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"7885d29023a8e22c48d9fba585c247fe\",\"sn\":0,\"ts_ms\":1792889020100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7885d29023a8e22c48d9fba585c247fe\",\"sn\":1,\"ts_ms\":1792889020150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7885d29023a8e22c48d9fba585c247fe\",\"sn\":2,\"ts_ms\":1792889020250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7885d29023a8e22c48d9fba585c247fe\",\"sn\":3,\"ts_ms\":1792889020670,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7885d29023a8e22c48d9fba585c247fe\",\"sn\":4,\"ts_ms\":1792889059000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"7885d29023a8e22c48d9fba585c247fe\",sn=0,st=l,sta=s,ts=1792889020100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"7885d29023a8e22c48d9fba585c247fe\",sn=1,st=l,ts=1792889020150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",sf=h,sid=\"7885d29023a8e22c48d9fba585c247fe\",sn=2,st=l,ts=1792889020250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"7885d29023a8e22c48d9fba585c247fe\",sn=3,st=l,sta=p,ts=1792889020670,v=2\ne=ps,sf=h,sid=\"7885d29023a8e22c48d9fba585c247fe\",sn=4,st=l,sta=e,ts=1792889059000,v=2", + "outcome": { + "outcome": "accept", + "digest": "a347bf04ced54a72cbd152de1cce2b245040bed3e59f7f7bd4c56c32da124d85", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889020000000, + "end": 1792889060000000, + "ended": "user", + "presented_ms": 38330, + "viewable_ms": 38330, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 570, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_28", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e210bbddcb0b9e7cbcd1ffc0b257fbe14ca643612d1717ffec4b605300a91ec66857ce89a9e216a705570dad4bc92a4b2901002ebc849f5e060000881e879f5e06000101140068747470733a2f2f656467652e6578616d706c65010004319916540b4553722c1ffe2a54fa9ddd7d47212b188a5e833f3ce5fc5b3762000001000000a8020000b0950000d00701440200000000000000000000000000000000000000000000000000006653d6ad5fde4d4d54d08aaad1dea7392e0114542a6743818970b7432930f2de00000000000000000000000000000000000000000000000000000000000000000203d06c040000000000030400000000000000000000002bbaeb41a4e65aecf95c8b58446a2c0e865c81a829d229545d09769dd3748dbff7344191561e6c5226e4ab80f4d07621a8fb7ee827c05a941eda210d9bc30a04", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e10bbddcb0b9e7cbcd1ffc0b257fbe14ca643612d1717ffec4b605300a91ec668002ebc849f5e060080c454859f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000337a39bd3f6b7e9baed668d597aa6dae34bcfb6ea4cdcf7dd36cd1b67a9584116aa57f38b82fee8df91021bc1708d8347b84262ada79b95d6aea5f9ace0b8c36040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0401d7bc32074224716ca773ba9dd65573b67183343b02e0ed5678b1dbb6715df7f2478e0e0714ca9b703c8248765794f160a841ed29dd2c613255109a3b49fca0f", + "items": [ + "3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee200000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"57ce89a9e216a705570dad4bc92a4b29\",\"sn\":0,\"ts_ms\":1792889080100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"57ce89a9e216a705570dad4bc92a4b29\",\"sn\":1,\"ts_ms\":1792889080150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"57ce89a9e216a705570dad4bc92a4b29\",\"sn\":2,\"ts_ms\":1792889080250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"57ce89a9e216a705570dad4bc92a4b29\",\"sn\":3,\"ts_ms\":1792889080680,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"57ce89a9e216a705570dad4bc92a4b29\",\"sn\":4,\"ts_ms\":1792889119000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"57ce89a9e216a705570dad4bc92a4b29\",sn=0,st=l,sta=s,ts=1792889080100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"57ce89a9e216a705570dad4bc92a4b29\",sn=1,st=l,ts=1792889080150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",sf=h,sid=\"57ce89a9e216a705570dad4bc92a4b29\",sn=2,st=l,ts=1792889080250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"57ce89a9e216a705570dad4bc92a4b29\",sn=3,st=l,sta=p,ts=1792889080680,v=2\ne=ps,sf=h,sid=\"57ce89a9e216a705570dad4bc92a4b29\",sn=4,st=l,sta=e,ts=1792889119000,v=2", + "outcome": { + "outcome": "accept", + "digest": "994f9ddde383f5811158fc01a776b9f95c787533c952668136b5a68c9a6b22e2", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889080000000, + "end": 1792889120000000, + "ended": "ended", + "presented_ms": 38320, + "viewable_ms": 0, + "audible_ms": 38320, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 580, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 290000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_29", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2aefe966c268bb3071efa6040ed5dc0771f93d6ef97271a7c0cc6f48ed594a8cf09bb79103020f7461fb5535c3cb9d6e90100b54f889f5e0600000fb28a9f5e06000201140068747470733a2f2f656467652e6578616d706c650100523592bd894b2e17b4d64abec8efa228b5eddc41fadd0e8ae34f6165a2b47727000001000000b2020000a69500001027014e020000000000000000000000000000000000000000000000000000c77b1a415b10a833683191e05b77b95b6b2a4f8c7681d2f3639d074f153498a300000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000a2074b07cc7f32e8bd9159411a309c7644eb272de04a545afefad51e6e169eb2bf150ec626d4c0b9234528ebca3b34e8ea4a5e2f42f154a5d9496c55da730b03", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6eaefe966c268bb3071efa6040ed5dc0771f93d6ef97271a7c0cc6f48ed594a8cf00b54f889f5e0600804be8889f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000a512307eb525ef68137406bb53edc1cfd963fe40ad9e3b0b1d80c578766f02648514c44dc900d081093ddf8e6883676f5c9d06e065ed016d6254c2d0a80639f5040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0407fc85c506c15e80dfc33e4dab89062bf7496465981a06f312a60941495e637e2aabdc171137cb4a3164bee306a1c4bed00010a7bcdcaf0c94a928f17c8aba80b", + "items": [ + "90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d8100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"09bb79103020f7461fb5535c3cb9d6e9\",\"sn\":0,\"ts_ms\":1792889140100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"09bb79103020f7461fb5535c3cb9d6e9\",\"sn\":1,\"ts_ms\":1792889140150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"09bb79103020f7461fb5535c3cb9d6e9\",\"sn\":2,\"ts_ms\":1792889140250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"09bb79103020f7461fb5535c3cb9d6e9\",\"sn\":3,\"ts_ms\":1792889140690,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"09bb79103020f7461fb5535c3cb9d6e9\",\"sn\":4,\"ts_ms\":1792889179000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"09bb79103020f7461fb5535c3cb9d6e9\",sn=0,st=l,sta=s,ts=1792889140100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"09bb79103020f7461fb5535c3cb9d6e9\",sn=1,st=l,ts=1792889140150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",sf=h,sid=\"09bb79103020f7461fb5535c3cb9d6e9\",sn=2,st=l,ts=1792889140250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"09bb79103020f7461fb5535c3cb9d6e9\",sn=3,st=l,sta=p,ts=1792889140690,v=2\ne=ps,sf=h,sid=\"09bb79103020f7461fb5535c3cb9d6e9\",sn=4,st=l,sta=e,ts=1792889179000,v=2", + "outcome": { + "outcome": "accept", + "digest": "fdfd049e2f5202be212fe387a5510deaf8578c902a04dd954c64539ca81650d5", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889140000000, + "end": 1792889180000000, + "ended": "user", + "presented_ms": 38310, + "viewable_ms": 38310, + "audible_ms": 38310, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 590, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_30", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2cda7548e816556ae38e6b7116db965a06e3ca36286d15462b65ea0b1b33cffa367e94186c99cf16ef7ef6079e385c2eb01003ce38b9f5e06000096458e9f5e06000201140068747470733a2f2f656467652e6578616d706c6501004671484f1619b5a541bb5a9e9976fc4018ed9f4fd487daa5f28664b05d4c6929000002000000bc02000054240000102701c02b0000986c00001027015802000001000000b0040000000000000000000000000000000000005887ffb7a9fab17db45f04b473d1263fc36652660c7e29a0928701ab69199cf700000000000000000000000000000000000000000000000000000000000000000103f0ba0400000000000300009962ae63fcc09e720d823b4990d4d759e293b3c4f8aef58c03520900961ce69244075c5f540bb8f369dc2d6a8ff82da1584dbfcc40b23aba16f1323b320cb50b", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6ecda7548e816556ae38e6b7116db965a06e3ca36286d15462b65ea0b1b33cffa3003ce38b9f5e060080d27b8c9f5e060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000200300000000000038d05149bc5379282245bcf7547fc45a42705115272c20886ddaf10872c388cb9b636335eafac219654e59520ed4ab116ce4e164055dac0d8927a1fceb81ca03040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040e3801728fe3670ad4f7e759fa68d46a54f05aa7f92fc0873cc4a0fd43691bfb9de5e5980f9084722c7d15711e7c4a21ffdd1f6c5b851dc160b968a820f5fc309", + "items": [ + "d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e5100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"67e94186c99cf16ef7ef6079e385c2eb\",\"sn\":0,\"ts_ms\":1792889200100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"67e94186c99cf16ef7ef6079e385c2eb\",\"sn\":1,\"ts_ms\":1792889200150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"67e94186c99cf16ef7ef6079e385c2eb\",\"sn\":2,\"ts_ms\":1792889200250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"67e94186c99cf16ef7ef6079e385c2eb\",\"sn\":3,\"ts_ms\":1792889200700,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"67e94186c99cf16ef7ef6079e385c2eb\",\"sn\":4,\"ts_ms\":1792889210000,\"event\":\"stall-start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"67e94186c99cf16ef7ef6079e385c2eb\",\"sn\":5,\"ts_ms\":1792889211200,\"event\":\"stall-end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"67e94186c99cf16ef7ef6079e385c2eb\",\"sn\":6,\"ts_ms\":1792889239000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"67e94186c99cf16ef7ef6079e385c2eb\",sn=0,st=l,sta=s,ts=1792889200100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"67e94186c99cf16ef7ef6079e385c2eb\",sn=1,st=l,ts=1792889200150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",sf=h,sid=\"67e94186c99cf16ef7ef6079e385c2eb\",sn=2,st=l,ts=1792889200250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"67e94186c99cf16ef7ef6079e385c2eb\",sn=3,st=l,sta=p,ts=1792889200700,v=2\ne=ps,sf=h,sid=\"67e94186c99cf16ef7ef6079e385c2eb\",sn=4,st=l,sta=r,ts=1792889210000,v=2\ne=ps,sf=h,sid=\"67e94186c99cf16ef7ef6079e385c2eb\",sn=5,st=l,sta=p,ts=1792889211200,v=2\ne=ps,sf=h,sid=\"67e94186c99cf16ef7ef6079e385c2eb\",sn=6,st=l,sta=e,ts=1792889239000,v=2", + "outcome": { + "outcome": "accept", + "digest": "12e2f989fe5b315c5d89afdc329f6018e2d857f7733a8ed4378ad0a93fe85898", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889200000000, + "end": 1792889240000000, + "ended": "user", + "presented_ms": 37100, + "viewable_ms": 37100, + "audible_ms": 37100, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 600, + "stalls": 1, + "stall_ms": 1200, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 310000 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_31", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e28d3b1e07feb62cc3b83bb6123b358a55dc0c21b9d015ca8d31856d984d439f399d29bb91ab7e7ecacbe329ab6d59c8f70100c3768f9f5e0600001dd9919f5e06000201140068747470733a2f2f656467652e6578616d706c6501003871e00d5b48f69dfc2ec91e8ba0cba8bdfe97448c6cc4de2b5912c6eb190fff000001000000c602000092950000102701620200000000000000000000000000000000000000000000000000004ef26dd4cba4aafed05a580900ee8d4f3efcf234c23b6abded77b318fc3b7198000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000007fe36d453ac71fe25ccb327ab51e6df2698644fe629db2d8d458697824bce7096e8a23871b2c3c7b1476d05945093381c2344c3e22224e1d204d509622ef4501", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e8d3b1e07feb62cc3b83bb6123b358a55dc0c21b9d015ca8d31856d984d439f3900c3768f9f5e060080590f909f5e06000000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000020030000000000000ccbb165ec882a87aaf3b836c552777becb7ccf33ccf7407a79266650352f9f2d8e7c3f3c74b1bac1233221b4db12d4de07706cbf32a71c2836538ec799c6664040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040644876e585693d0c27657495caa0f26aff9a3af19a6f0b1934d74ec9d701ff25684d3ea13e17b68b7726dcc51a8b25152e9a144b7bc411c02eb3706e3e10b50a", + "items": [ + "c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c00000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"9d29bb91ab7e7ecacbe329ab6d59c8f7\",\"sn\":0,\"ts_ms\":1792889260100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"9d29bb91ab7e7ecacbe329ab6d59c8f7\",\"sn\":1,\"ts_ms\":1792889260150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"9d29bb91ab7e7ecacbe329ab6d59c8f7\",\"sn\":2,\"ts_ms\":1792889260250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"9d29bb91ab7e7ecacbe329ab6d59c8f7\",\"sn\":3,\"ts_ms\":1792889260710,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"9d29bb91ab7e7ecacbe329ab6d59c8f7\",\"sn\":4,\"ts_ms\":1792889299000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"9d29bb91ab7e7ecacbe329ab6d59c8f7\",sn=0,st=l,sta=s,ts=1792889260100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"9d29bb91ab7e7ecacbe329ab6d59c8f7\",sn=1,st=l,ts=1792889260150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",sf=h,sid=\"9d29bb91ab7e7ecacbe329ab6d59c8f7\",sn=2,st=l,ts=1792889260250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"9d29bb91ab7e7ecacbe329ab6d59c8f7\",sn=3,st=l,sta=p,ts=1792889260710,v=2\ne=ps,sf=h,sid=\"9d29bb91ab7e7ecacbe329ab6d59c8f7\",sn=4,st=l,sta=e,ts=1792889299000,v=2", + "outcome": { + "outcome": "accept", + "digest": "5bbc743341ad54cd8a1dce9c913dd57dbcb44ab1a415f78ac90021abdb59ec7d", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889260000000, + "end": 1792889300000000, + "ended": "user", + "presented_ms": 38290, + "viewable_ms": 38290, + "audible_ms": 38290, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 610, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_32", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e22d0405fcbeaa63439cfa757b6cb2a11677814257580df831ffee5712aed416f4c8981defe79a8224cd94197025053a8001004a0a939f5e060000a46c959f5e06000101140068747470733a2f2f656467652e6578616d706c65010077e8bdcf22e5b9e7ba1cdb1f8570dd3484aa9ff98aad9a039c001387903c6432000001000000d0020000889500001027016c0200000000000000000000000000000000000000000000000000004531cf01fb7ccda65fc9a78f4f3a032986e929aab5aa7aefaaa258a1c621614300000000000000000000000000000000000000000000000000000000000000000203100905000000000003040000000000000000000000a0d3e0cb562c6891e94a4e791e96905b6643e91c44cb10d7948ec170611c68333ce2e27b28678c2402a58842d73bf72bb504fa9936726332acfa987dc2b80f0c", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e2d0405fcbeaa63439cfa757b6cb2a11677814257580df831ffee5712aed416f4004a0a939f5e060080e0a2939f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000337a39bd3f6b7e9baed668d597aa6dae34bcfb6ea4cdcf7dd36cd1b67a9584116aa57f38b82fee8df91021bc1708d8347b84262ada79b95d6aea5f9ace0b8c36040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0405d0eadf348691cb6a311d4afa1fe87c769ebbec3c9ff52b676356d705643ee574465e5d3f89f6af69065ebc8b8a328345373e11b5b55dbfab8e3bee918e26b0d", + "items": [ + "3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee200000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"c8981defe79a8224cd94197025053a80\",\"sn\":0,\"ts_ms\":1792889320100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"c8981defe79a8224cd94197025053a80\",\"sn\":1,\"ts_ms\":1792889320150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"c8981defe79a8224cd94197025053a80\",\"sn\":2,\"ts_ms\":1792889320250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"c8981defe79a8224cd94197025053a80\",\"sn\":3,\"ts_ms\":1792889320720,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"c8981defe79a8224cd94197025053a80\",\"sn\":4,\"ts_ms\":1792889359000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"c8981defe79a8224cd94197025053a80\",sn=0,st=l,sta=s,ts=1792889320100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"c8981defe79a8224cd94197025053a80\",sn=1,st=l,ts=1792889320150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",sf=h,sid=\"c8981defe79a8224cd94197025053a80\",sn=2,st=l,ts=1792889320250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"c8981defe79a8224cd94197025053a80\",sn=3,st=l,sta=p,ts=1792889320720,v=2\ne=ps,sf=h,sid=\"c8981defe79a8224cd94197025053a80\",sn=4,st=l,sta=e,ts=1792889359000,v=2", + "outcome": { + "outcome": "accept", + "digest": "68bc8266f12df2bb969c5674d144309b70414bb15900730bfd548db8ca22a637", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889320000000, + "end": 1792889360000000, + "ended": "ended", + "presented_ms": 38280, + "viewable_ms": 38280, + "audible_ms": 38280, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 620, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 330000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_33", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2b6877265463a259c250a608d16c01ce1d7c661a813e701df00e10d8f198147e86f16d10fd2e0dbdb80531a242a17f1f60100d19d969f5e0600002b00999f5e06000201140068747470733a2f2f656467652e6578616d706c650100b699c0d95be1e6450bb3d3757c2f66ac231f6c5714577704ebc81d68d8f76d12000001000000da0200007e95000010270076020000000000000000000000000000000000000000000000000000b44c4001e65ff0f8c8a33f3bd28b4b6143972912cd14e9e885a2b827a8e383620000000000000000000000000000000000000000000000000000000000000000010300000000000000000000002f5903b5e3f9bf8e8f0e97de710a73e6a9dcf49a539a116c25c7a6d585121d95a6b1b1679ff3a1dea50bcb9343bb704096614cded3bb4dc84d2f86c3e8b86f01", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6eb6877265463a259c250a608d16c01ce1d7c661a813e701df00e10d8f198147e800d19d969f5e0600806736979f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000a512307eb525ef68137406bb53edc1cfd963fe40ad9e3b0b1d80c578766f02648514c44dc900d081093ddf8e6883676f5c9d06e065ed016d6254c2d0a80639f5040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0407a0fbd79058474276cd5beeb5a21537116f7777afdb9795fb5755e90b420740300f649dd046085ea553956d6e9f4883823395cd3da9122a5348e78889372b301", + "items": [ + "90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d8100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"6f16d10fd2e0dbdb80531a242a17f1f6\",\"sn\":0,\"ts_ms\":1792889380100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"6f16d10fd2e0dbdb80531a242a17f1f6\",\"sn\":1,\"ts_ms\":1792889380150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"6f16d10fd2e0dbdb80531a242a17f1f6\",\"sn\":2,\"ts_ms\":1792889380250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"6f16d10fd2e0dbdb80531a242a17f1f6\",\"sn\":3,\"ts_ms\":1792889380730,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"6f16d10fd2e0dbdb80531a242a17f1f6\",\"sn\":4,\"ts_ms\":1792889419000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"6f16d10fd2e0dbdb80531a242a17f1f6\",sn=0,st=l,sta=s,ts=1792889380100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"6f16d10fd2e0dbdb80531a242a17f1f6\",sn=1,st=l,ts=1792889380150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",sf=h,sid=\"6f16d10fd2e0dbdb80531a242a17f1f6\",sn=2,st=l,ts=1792889380250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"6f16d10fd2e0dbdb80531a242a17f1f6\",sn=3,st=l,sta=p,ts=1792889380730,v=2\ne=ps,sf=h,sid=\"6f16d10fd2e0dbdb80531a242a17f1f6\",sn=4,st=l,sta=e,ts=1792889419000,v=2", + "outcome": { + "outcome": "accept", + "digest": "93a9923e34a9d297b6a97494669bf96e2afac94eca172c5ac47fde80391742b5", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889380000000, + "end": 1792889420000000, + "ended": "user", + "presented_ms": 38270, + "viewable_ms": 38270, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 630, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_34", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e25c1e89db84bb39a49d1a1c58740867f56cc29e0ee654ed325769e1a27889621097f2d2e7a2f15701ef59bff613ffb624010058319a9f5e060000b2939c9f5e06000201140068747470733a2f2f656467652e6578616d706c6501009e842ae358d978d689a88519a5e0279094dd15c2b9a8d31af95ce7de4e44b509000001000000e40200007495000010270180020000000000000000000000000000000000000000000000000000ed3f09df9cc476dd9f8621a5cb70b8d0086c4fcaa808321e673433fbbb376cc800000000000000000000000000000000000000000000000000000000000000000203305705000000000003040000000000000000000000d7aeb8c8ba7e42936ae05fa560be65773c3da429f0f5f611307adf059303c6375bc8e3f2293f57cbeecd783067f423d9521a8b5d5c5a693f1480100a6f5cc50d", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e5c1e89db84bb39a49d1a1c58740867f56cc29e0ee654ed325769e1a2788962100058319a9f5e060080eec99a9f5e060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000200300000000000038d05149bc5379282245bcf7547fc45a42705115272c20886ddaf10872c388cb9b636335eafac219654e59520ed4ab116ce4e164055dac0d8927a1fceb81ca03040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a04008ebc7973d26bdb4472ba1447dee08aef30e82c413aa7874283ed785e4c9993b616f8abf895fd31ff58d1b03d431e4c0ac0d8920437a4ca9ca7e62a3b7f05804", + "items": [ + "d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e5100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"97f2d2e7a2f15701ef59bff613ffb624\",\"sn\":0,\"ts_ms\":1792889440100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"97f2d2e7a2f15701ef59bff613ffb624\",\"sn\":1,\"ts_ms\":1792889440150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"97f2d2e7a2f15701ef59bff613ffb624\",\"sn\":2,\"ts_ms\":1792889440250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"97f2d2e7a2f15701ef59bff613ffb624\",\"sn\":3,\"ts_ms\":1792889440740,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"97f2d2e7a2f15701ef59bff613ffb624\",\"sn\":4,\"ts_ms\":1792889479000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"97f2d2e7a2f15701ef59bff613ffb624\",sn=0,st=l,sta=s,ts=1792889440100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"97f2d2e7a2f15701ef59bff613ffb624\",sn=1,st=l,ts=1792889440150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",sf=h,sid=\"97f2d2e7a2f15701ef59bff613ffb624\",sn=2,st=l,ts=1792889440250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"97f2d2e7a2f15701ef59bff613ffb624\",sn=3,st=l,sta=p,ts=1792889440740,v=2\ne=ps,sf=h,sid=\"97f2d2e7a2f15701ef59bff613ffb624\",sn=4,st=l,sta=e,ts=1792889479000,v=2", + "outcome": { + "outcome": "accept", + "digest": "5675cbb1cd58c5e7d8c5b7447ce39b79ab62df25019421e690c4124f97b7ceb9", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889440000000, + "end": 1792889480000000, + "ended": "user", + "presented_ms": 38260, + "viewable_ms": 38260, + "audible_ms": 38260, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 640, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 350000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_35", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e21776847521cd8a16b17b5b35e1a126356496de44412aa1ede80bb6c2a9530f5f452dbe69c5a83a9de6c53e6a1417fc640100dfc49d9f5e0600003927a09f5e06000201140068747470733a2f2f656467652e6578616d706c6501007d903fc8234dea4536a2aa2fbc7b5d131cc5b4207e9f5a41748b9b7ba0285558000002000000ee02000022240000102701c02b0000986c00001027018a02000001000000b004000000000000000000000000000000000000ec4d76197ac747082764f082c42ddcf5139934b135916b1f2261c937ea2a3f3b00000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000c90d1ecbfe35108b2085c69ab9b9594aef2442a473b8d632d82a45b75614913dc27f3bf02ea7518e021b73dc145b3dc7d62ccdaaae5cdf1c9b728d5d8bc8d40d", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e1776847521cd8a16b17b5b35e1a126356496de44412aa1ede80bb6c2a9530f5f00dfc49d9f5e060080755d9e9f5e06000000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000020030000000000000ccbb165ec882a87aaf3b836c552777becb7ccf33ccf7407a79266650352f9f2d8e7c3f3c74b1bac1233221b4db12d4de07706cbf32a71c2836538ec799c6664040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0408f856189db31acd8f628bb17cd3ce891b5b2d92d38a1feb38d2220fcaea6c0ce12e2ff98ec23cbc457b4059fd32d528ebc862d798f5615bd1e5fcf19afc83704", + "items": [ + "c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c00000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"452dbe69c5a83a9de6c53e6a1417fc64\",\"sn\":0,\"ts_ms\":1792889500100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"452dbe69c5a83a9de6c53e6a1417fc64\",\"sn\":1,\"ts_ms\":1792889500150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"452dbe69c5a83a9de6c53e6a1417fc64\",\"sn\":2,\"ts_ms\":1792889500250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"452dbe69c5a83a9de6c53e6a1417fc64\",\"sn\":3,\"ts_ms\":1792889500750,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"452dbe69c5a83a9de6c53e6a1417fc64\",\"sn\":4,\"ts_ms\":1792889510000,\"event\":\"stall-start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"452dbe69c5a83a9de6c53e6a1417fc64\",\"sn\":5,\"ts_ms\":1792889511200,\"event\":\"stall-end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"452dbe69c5a83a9de6c53e6a1417fc64\",\"sn\":6,\"ts_ms\":1792889539000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"452dbe69c5a83a9de6c53e6a1417fc64\",sn=0,st=l,sta=s,ts=1792889500100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"452dbe69c5a83a9de6c53e6a1417fc64\",sn=1,st=l,ts=1792889500150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",sf=h,sid=\"452dbe69c5a83a9de6c53e6a1417fc64\",sn=2,st=l,ts=1792889500250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"452dbe69c5a83a9de6c53e6a1417fc64\",sn=3,st=l,sta=p,ts=1792889500750,v=2\ne=ps,sf=h,sid=\"452dbe69c5a83a9de6c53e6a1417fc64\",sn=4,st=l,sta=r,ts=1792889510000,v=2\ne=ps,sf=h,sid=\"452dbe69c5a83a9de6c53e6a1417fc64\",sn=5,st=l,sta=p,ts=1792889511200,v=2\ne=ps,sf=h,sid=\"452dbe69c5a83a9de6c53e6a1417fc64\",sn=6,st=l,sta=e,ts=1792889539000,v=2", + "outcome": { + "outcome": "accept", + "digest": "f0e5c0e1c4ad8664c960a5403b2f0f01ab7925e4c09821c85f0e2872a40a789e", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889500000000, + "end": 1792889540000000, + "ended": "user", + "presented_ms": 37050, + "viewable_ms": 37050, + "audible_ms": 37050, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 650, + "stalls": 1, + "stall_ms": 1200, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_36", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29b11c561d168e07d1e08866c41d8fce0a692f59c09f5df8dee884f809662639ac0b9e4d0c8927dffd9e0465c67f2c3a401006658a19f5e060000c0baa39f5e06000101140068747470733a2f2f656467652e6578616d706c65010003a2d2ead4578cb97dfca0787b755822b685f68ef3d332d0f99fda78dfba3dbb000001000000f80200006095000010270094020000000000000000000000000000000000000000000000000000758a26bc972510bcad15fc93fdbac441dc701f4337cb6e16c39cf105ec41a8210000000000000000000000000000000000000000000000000000000000000000010350a5050000000000030000530e3882da7ab30bb9c68deb6199bcd4fa952d9607b454e3da8a5971a26020a70a82ad027dd119e0fdb30ee9f16723e52419ca48d2a587ca33640a02e773420f", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e9b11c561d168e07d1e08866c41d8fce0a692f59c09f5df8dee884f809662639a006658a19f5e060080fcf0a19f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000337a39bd3f6b7e9baed668d597aa6dae34bcfb6ea4cdcf7dd36cd1b67a9584116aa57f38b82fee8df91021bc1708d8347b84262ada79b95d6aea5f9ace0b8c36040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a04026604b075d6d525fa3dfc89f18d0a70c8e6e9ab6a376deea7a00e40a62a5414175229ae0067760958412abecaaf19f0ac01081c73204006e1aa8d38950a76403", + "items": [ + "3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee200000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"c0b9e4d0c8927dffd9e0465c67f2c3a4\",\"sn\":0,\"ts_ms\":1792889560100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"c0b9e4d0c8927dffd9e0465c67f2c3a4\",\"sn\":1,\"ts_ms\":1792889560150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"c0b9e4d0c8927dffd9e0465c67f2c3a4\",\"sn\":2,\"ts_ms\":1792889560250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"c0b9e4d0c8927dffd9e0465c67f2c3a4\",\"sn\":3,\"ts_ms\":1792889560760,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"c0b9e4d0c8927dffd9e0465c67f2c3a4\",\"sn\":4,\"ts_ms\":1792889599000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"c0b9e4d0c8927dffd9e0465c67f2c3a4\",sn=0,st=l,sta=s,ts=1792889560100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"c0b9e4d0c8927dffd9e0465c67f2c3a4\",sn=1,st=l,ts=1792889560150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",sf=h,sid=\"c0b9e4d0c8927dffd9e0465c67f2c3a4\",sn=2,st=l,ts=1792889560250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"c0b9e4d0c8927dffd9e0465c67f2c3a4\",sn=3,st=l,sta=p,ts=1792889560760,v=2\ne=ps,sf=h,sid=\"c0b9e4d0c8927dffd9e0465c67f2c3a4\",sn=4,st=l,sta=e,ts=1792889599000,v=2", + "outcome": { + "outcome": "accept", + "digest": "9763eeecaec7b8304669d8abf01c450bf4533f0c355a36bff8b508de77b866f3", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889560000000, + "end": 1792889600000000, + "ended": "ended", + "presented_ms": 38240, + "viewable_ms": 38240, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 660, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 370000 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_37", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2be2c28de2ae1ea41aee4135ad7d4e560b35d9912855a39d45a870aa59143ad5471f252e5d2fe3dcc8f03ed3898f229d20100edeba49f5e060000474ea79f5e06000201140068747470733a2f2f656467652e6578616d706c650100627ec1a530cc684588d0c82bc771ef433fadef5ac5378483101236e3615b411f00000100000002030000569500001027019e0200000000000000000000000000000000000000000000000000000791ab3b276ace892a9b036599a21fc51c2f9f3ef85554eb4aac78afc6e18012000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000005be5ed9947c8d6c671d1f1548e8d4fcdad240073f61e721cfe7ae1e1f875a1588049da379f94e63c82588b372f4965f4ec9d4241b455d083a871683e38f07e0f", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6ebe2c28de2ae1ea41aee4135ad7d4e560b35d9912855a39d45a870aa59143ad5400edeba49f5e0600808384a59f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000a512307eb525ef68137406bb53edc1cfd963fe40ad9e3b0b1d80c578766f02648514c44dc900d081093ddf8e6883676f5c9d06e065ed016d6254c2d0a80639f5040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a04096741bafd2c2d35523918ed10ba31a03de32dd2538dbccfd6d77da3ceb9657e7180914addac3881c02cfffa7ad2dfaff3ffb34e76c84ca81dce62c101f56d30b", + "items": [ + "90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d8100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"71f252e5d2fe3dcc8f03ed3898f229d2\",\"sn\":0,\"ts_ms\":1792889620100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"71f252e5d2fe3dcc8f03ed3898f229d2\",\"sn\":1,\"ts_ms\":1792889620150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"71f252e5d2fe3dcc8f03ed3898f229d2\",\"sn\":2,\"ts_ms\":1792889620250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"71f252e5d2fe3dcc8f03ed3898f229d2\",\"sn\":3,\"ts_ms\":1792889620770,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"71f252e5d2fe3dcc8f03ed3898f229d2\",\"sn\":4,\"ts_ms\":1792889659000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"71f252e5d2fe3dcc8f03ed3898f229d2\",sn=0,st=l,sta=s,ts=1792889620100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"71f252e5d2fe3dcc8f03ed3898f229d2\",sn=1,st=l,ts=1792889620150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",sf=h,sid=\"71f252e5d2fe3dcc8f03ed3898f229d2\",sn=2,st=l,ts=1792889620250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"71f252e5d2fe3dcc8f03ed3898f229d2\",sn=3,st=l,sta=p,ts=1792889620770,v=2\ne=ps,sf=h,sid=\"71f252e5d2fe3dcc8f03ed3898f229d2\",sn=4,st=l,sta=e,ts=1792889659000,v=2", + "outcome": { + "outcome": "accept", + "digest": "e7ce21bc0cf1a7dc52a673ecd06690fcac2b429e6bb34173be9d14bb0e4af6a8", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889620000000, + "end": 1792889660000000, + "ended": "user", + "presented_ms": 38230, + "viewable_ms": 38230, + "audible_ms": 38230, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 670, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_38", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29c801696278a565f228c52984386b54fd24ecb22dfac91366e86fb032c1cab6e8071c91bea086554f868f71427b287600100747fa89f5e060000cee1aa9f5e06000201140068747470733a2f2f656467652e6578616d706c650100fc36a5d5f92323e9acbae93183b4d3de1b7926359dc1bc0e6fe20cebc52ffc660000010000000c0300004c950000102701a8020000000000000000000000000000000000000000000000000000a3ba5d104f4cfe3475cb38b8d73ca9d9e197a7eaad422cb72edc32cb4a4a73380000000000000000000000000000000000000000000000000000000000000000020370f30500000000000304000000000000000000000055f2d3529ab18550dfcc1dedd208c140112c043b1b98bc63028267e3d6d05a7804f8add045b030fa0dc274b7c306456113c48bb9ec678dd19657ce07c1694c05", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e9c801696278a565f228c52984386b54fd24ecb22dfac91366e86fb032c1cab6e00747fa89f5e0600800a18a99f5e060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000200300000000000038d05149bc5379282245bcf7547fc45a42705115272c20886ddaf10872c388cb9b636335eafac219654e59520ed4ab116ce4e164055dac0d8927a1fceb81ca03040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040d11a72084521125a495002ce2278713b26637dbe2350a43919f30c4159d4c11a4f0af39bbedf5f54c42588bfb6e9f90184b52bd5aeba25095e6cc04494192203", + "items": [ + "d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e5100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"8071c91bea086554f868f71427b28760\",\"sn\":0,\"ts_ms\":1792889680100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"8071c91bea086554f868f71427b28760\",\"sn\":1,\"ts_ms\":1792889680150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"8071c91bea086554f868f71427b28760\",\"sn\":2,\"ts_ms\":1792889680250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"8071c91bea086554f868f71427b28760\",\"sn\":3,\"ts_ms\":1792889680780,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"8071c91bea086554f868f71427b28760\",\"sn\":4,\"ts_ms\":1792889719000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"8071c91bea086554f868f71427b28760\",sn=0,st=l,sta=s,ts=1792889680100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"8071c91bea086554f868f71427b28760\",sn=1,st=l,ts=1792889680150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",sf=h,sid=\"8071c91bea086554f868f71427b28760\",sn=2,st=l,ts=1792889680250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"8071c91bea086554f868f71427b28760\",sn=3,st=l,sta=p,ts=1792889680780,v=2\ne=ps,sf=h,sid=\"8071c91bea086554f868f71427b28760\",sn=4,st=l,sta=e,ts=1792889719000,v=2", + "outcome": { + "outcome": "accept", + "digest": "255e55afc213300be08db85c7fc4a85181d9d9bdbb6efbf98c7009460d831746", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889680000000, + "end": 1792889720000000, + "ended": "user", + "presented_ms": 38220, + "viewable_ms": 38220, + "audible_ms": 38220, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 680, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 390000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_39", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2a73d91e3a2ce62d4f5bd5ed7b155fabf8faf2e1802077d354efe2799af26ad71bcec84121ade1a98ec227352693a261b0100fb12ac9f5e0600005575ae9f5e06000201140068747470733a2f2f656467652e6578616d706c65010065789d9e2439a9cb94a8c4b1ec46068039ace40bef80558c4495e6eebcfdada10000010000001603000042950000102700b2020000000000000000000000000000000000000000000000000000045f6389cb98a47fa827f448eaeafcb8b7c8c3e6801a0cc35e45ae098c3f4db80000000000000000000000000000000000000000000000000000000000000000010300000000000000000000000007ebdf5cff96f26b100f3bfe126f1193bb7f082241a5156b442de1038993075164555c98074c4da71c62e5cbb0a6b120f3873ec258c8f16fa05c834d708706", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6ea73d91e3a2ce62d4f5bd5ed7b155fabf8faf2e1802077d354efe2799af26ad7100fb12ac9f5e06008091abac9f5e06000000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000020030000000000000ccbb165ec882a87aaf3b836c552777becb7ccf33ccf7407a79266650352f9f2d8e7c3f3c74b1bac1233221b4db12d4de07706cbf32a71c2836538ec799c6664040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a04064d0ab2f1c5a8f99ea35cf3cfe393833982c4165800cb30133aaf701b40b5bf62122bc67681a82965c420967558f3d5d8c6330be0dcddf0fa0855bcd011c730c", + "items": [ + "c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c00000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"bcec84121ade1a98ec227352693a261b\",\"sn\":0,\"ts_ms\":1792889740100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"bcec84121ade1a98ec227352693a261b\",\"sn\":1,\"ts_ms\":1792889740150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"bcec84121ade1a98ec227352693a261b\",\"sn\":2,\"ts_ms\":1792889740250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"bcec84121ade1a98ec227352693a261b\",\"sn\":3,\"ts_ms\":1792889740790,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"bcec84121ade1a98ec227352693a261b\",\"sn\":4,\"ts_ms\":1792889779000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"bcec84121ade1a98ec227352693a261b\",sn=0,st=l,sta=s,ts=1792889740100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"bcec84121ade1a98ec227352693a261b\",sn=1,st=l,ts=1792889740150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",sf=h,sid=\"bcec84121ade1a98ec227352693a261b\",sn=2,st=l,ts=1792889740250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"bcec84121ade1a98ec227352693a261b\",sn=3,st=l,sta=p,ts=1792889740790,v=2\ne=ps,sf=h,sid=\"bcec84121ade1a98ec227352693a261b\",sn=4,st=l,sta=e,ts=1792889779000,v=2", + "outcome": { + "outcome": "accept", + "digest": "f7ff04c5e623b3b91749c236be7296dda754fed7e9b0d16ac055866ba67145ad", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889740000000, + "end": 1792889780000000, + "ended": "user", + "presented_ms": 38210, + "viewable_ms": 38210, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 690, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_40", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e23dd8c92e2697639b8f2bdd8946c17fc71445911776a33bfb1a0bbf86ae0ed03a08b97cb5faeca1316bf3b4b963a1b734010082a6af9f5e060000dc08b29f5e06000101140068747470733a2f2f656467652e6578616d706c650100033a85078c279108afd19b65e2043d7abecbcda58ebaf496d50f0a5fcf57086200000200000020030000f0230000102701c02b0000986c0000102701bc02000001000000b0040000000000000000000000000000000000001912b30be59253e2e4b3dadddf176b3c31f8e800ce2a72a8568311ae1bd95e2e0000000000000000000000000000000000000000000000000000000000000000020390410600000000000304000000000000000000000096530c48a86bd94f978a7a19a80a2e63de8f6b1f29be3e8e627ac7fccfc6084b893133f9da9073e6d52650e3ce3d7f64f2fe3d5017c601072f2f7467956e3f00", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e3dd8c92e2697639b8f2bdd8946c17fc71445911776a33bfb1a0bbf86ae0ed03a0082a6af9f5e060080183fb09f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000337a39bd3f6b7e9baed668d597aa6dae34bcfb6ea4cdcf7dd36cd1b67a9584116aa57f38b82fee8df91021bc1708d8347b84262ada79b95d6aea5f9ace0b8c36040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040b628b88bce5c0d9cd495cd982c616164bd44d2782aed2ab49f43a1e5d660d391841765cf31ab5c8f60ce1889add8406b699a4db06a2dbea1359b93cedb54af01", + "items": [ + "3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee200000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"08b97cb5faeca1316bf3b4b963a1b734\",\"sn\":0,\"ts_ms\":1792889800100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"08b97cb5faeca1316bf3b4b963a1b734\",\"sn\":1,\"ts_ms\":1792889800150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"08b97cb5faeca1316bf3b4b963a1b734\",\"sn\":2,\"ts_ms\":1792889800250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"08b97cb5faeca1316bf3b4b963a1b734\",\"sn\":3,\"ts_ms\":1792889800800,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"08b97cb5faeca1316bf3b4b963a1b734\",\"sn\":4,\"ts_ms\":1792889810000,\"event\":\"stall-start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"08b97cb5faeca1316bf3b4b963a1b734\",\"sn\":5,\"ts_ms\":1792889811200,\"event\":\"stall-end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"08b97cb5faeca1316bf3b4b963a1b734\",\"sn\":6,\"ts_ms\":1792889839000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"08b97cb5faeca1316bf3b4b963a1b734\",sn=0,st=l,sta=s,ts=1792889800100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"08b97cb5faeca1316bf3b4b963a1b734\",sn=1,st=l,ts=1792889800150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",sf=h,sid=\"08b97cb5faeca1316bf3b4b963a1b734\",sn=2,st=l,ts=1792889800250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"08b97cb5faeca1316bf3b4b963a1b734\",sn=3,st=l,sta=p,ts=1792889800800,v=2\ne=ps,sf=h,sid=\"08b97cb5faeca1316bf3b4b963a1b734\",sn=4,st=l,sta=r,ts=1792889810000,v=2\ne=ps,sf=h,sid=\"08b97cb5faeca1316bf3b4b963a1b734\",sn=5,st=l,sta=p,ts=1792889811200,v=2\ne=ps,sf=h,sid=\"08b97cb5faeca1316bf3b4b963a1b734\",sn=6,st=l,sta=e,ts=1792889839000,v=2", + "outcome": { + "outcome": "accept", + "digest": "8214b2afb4784820b5403dc7b31b2489a23beee22011385d461ce2bfebbc9d6c", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889800000000, + "end": 1792889840000000, + "ended": "ended", + "presented_ms": 37000, + "viewable_ms": 37000, + "audible_ms": 37000, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 700, + "stalls": 1, + "stall_ms": 1200, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 410000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_41", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e245e2c158e41a9d9f893637dba6073a55021e08547cd3bfed4ce8eaebebac89e6e306e4c3484a9d109762eebe56d3970e0100093ab39f5e060000639cb59f5e06000201140068747470733a2f2f656467652e6578616d706c650100557ff864b0280b1a8575c3e9c186e64184388e472ddce2d90c59670039e2862e0000010000002a0300002e950000102701c6020000000000000000000000000000000000000000000000000000243a942b3f51dfd77e51c529733fe20db4e93650fc62c0fb5372cbc88d0dc50c0000000000000000000000000000000000000000000000000000000000000000020300000000000000000004000000000000000000000062ae9387eecd83867411e0cc14ea947f8a36ab8b1a3db7f3881a16e3387f1f7aaa0ce363de19537efdba18bae964a119dec77e4683b68c827f1a20f666fea109", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e45e2c158e41a9d9f893637dba6073a55021e08547cd3bfed4ce8eaebebac89e600093ab39f5e0600809fd2b39f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000a512307eb525ef68137406bb53edc1cfd963fe40ad9e3b0b1d80c578766f02648514c44dc900d081093ddf8e6883676f5c9d06e065ed016d6254c2d0a80639f5040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040b0594cc1d9555bc1c6734edfb043b28babcdd395ed2c2aa7f228aed6d587c41772b8d99c3f82eb6eb6e604c5b97830eb52e203d0f2e83cb090bc21aaa4bbe80a", + "items": [ + "90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d8100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"e306e4c3484a9d109762eebe56d3970e\",\"sn\":0,\"ts_ms\":1792889860100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"e306e4c3484a9d109762eebe56d3970e\",\"sn\":1,\"ts_ms\":1792889860150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"e306e4c3484a9d109762eebe56d3970e\",\"sn\":2,\"ts_ms\":1792889860250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"e306e4c3484a9d109762eebe56d3970e\",\"sn\":3,\"ts_ms\":1792889860810,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"e306e4c3484a9d109762eebe56d3970e\",\"sn\":4,\"ts_ms\":1792889899000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"e306e4c3484a9d109762eebe56d3970e\",sn=0,st=l,sta=s,ts=1792889860100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"e306e4c3484a9d109762eebe56d3970e\",sn=1,st=l,ts=1792889860150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",sf=h,sid=\"e306e4c3484a9d109762eebe56d3970e\",sn=2,st=l,ts=1792889860250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"e306e4c3484a9d109762eebe56d3970e\",sn=3,st=l,sta=p,ts=1792889860810,v=2\ne=ps,sf=h,sid=\"e306e4c3484a9d109762eebe56d3970e\",sn=4,st=l,sta=e,ts=1792889899000,v=2", + "outcome": { + "outcome": "accept", + "digest": "60e51e9e3f5fa4bb5af218669ba67e1b7ac4520cd6121651e3ef59fafa6320b7", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889860000000, + "end": 1792889900000000, + "ended": "user", + "presented_ms": 38190, + "viewable_ms": 38190, + "audible_ms": 38190, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 710, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_42", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2ce474692f244f8a1ca13191c700ac8cdaee32e9b49977491ac0c7b151e888564d8eba18990d45bce0ac795142ba962b7010090cdb69f5e060000ea2fb99f5e06000201140068747470733a2f2f656467652e6578616d706c650100088e9d37c9135d4592cdcdd1b9baedddba2a05f67937d0e5a3890d14afb5fad80000010000003403000024950000d00700d002000000000000000000000000000000000000000000000000000034acd43bd5d30b6cb001e72a628029aaa55198637ae4039091c9455415e3e89400000000000000000000000000000000000000000000000000000000000000000103b08f060000000000030000c4f11a0adbcaa173149d6ac882796ca4516eeaf409a6f14c9e1b3844a539f4906c734cdefe017322253de254bf992f6dfea81200de6210c7588027a02268c603", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6ece474692f244f8a1ca13191c700ac8cdaee32e9b49977491ac0c7b151e8885640090cdb69f5e0600802666b79f5e060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000200300000000000038d05149bc5379282245bcf7547fc45a42705115272c20886ddaf10872c388cb9b636335eafac219654e59520ed4ab116ce4e164055dac0d8927a1fceb81ca03040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a04035ad843da6d04423ba275f8912d605b611414963bd51737f8e5bc8cf488394b8d36c57302e152d9db2621a618ce4f4fcb1fda91b48d59dc863c62e5e414b4e06", + "items": [ + "d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e5100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"d8eba18990d45bce0ac795142ba962b7\",\"sn\":0,\"ts_ms\":1792889920100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d8eba18990d45bce0ac795142ba962b7\",\"sn\":1,\"ts_ms\":1792889920150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d8eba18990d45bce0ac795142ba962b7\",\"sn\":2,\"ts_ms\":1792889920250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d8eba18990d45bce0ac795142ba962b7\",\"sn\":3,\"ts_ms\":1792889920820,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"d8eba18990d45bce0ac795142ba962b7\",\"sn\":4,\"ts_ms\":1792889959000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"d8eba18990d45bce0ac795142ba962b7\",sn=0,st=l,sta=s,ts=1792889920100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"d8eba18990d45bce0ac795142ba962b7\",sn=1,st=l,ts=1792889920150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",sf=h,sid=\"d8eba18990d45bce0ac795142ba962b7\",sn=2,st=l,ts=1792889920250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"d8eba18990d45bce0ac795142ba962b7\",sn=3,st=l,sta=p,ts=1792889920820,v=2\ne=ps,sf=h,sid=\"d8eba18990d45bce0ac795142ba962b7\",sn=4,st=l,sta=e,ts=1792889959000,v=2", + "outcome": { + "outcome": "accept", + "digest": "121587410a852a7f86dafc1c2a64a5307a1e542347b4246f64a05f6d41a64a6e", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889920000000, + "end": 1792889960000000, + "ended": "user", + "presented_ms": 38180, + "viewable_ms": 0, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 720, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 430000 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_43", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e278bda9233bf0628ebc1aefc6bd2a0826e2e910168142419937779c9aafdf939650fcf1f49b13ae345ffe84d524c789e301001761ba9f5e06000071c3bc9f5e06000201140068747470733a2f2f656467652e6578616d706c6501002c2ab71cdae22258dd072c0f7513bce5af5ad58d86aeacb6ab6014111d6e6bdd0000010000003e0300001a950000102701da020000000000000000000000000000000000000000000000000000122667a03fc59321aa5785ae4712a64f21d4044f7226fa097296ccfa72089e22000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000005dba73d90a0c784bebc1ce66bf62716fd93b13ab77759256e6ec1869e4ba2129c9c2f10fd679de42c4ea6945c765b59df547efc7b25c7cdafb88de8647129006", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e78bda9233bf0628ebc1aefc6bd2a0826e2e910168142419937779c9aafdf9396001761ba9f5e060080adf9ba9f5e06000000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000020030000000000000ccbb165ec882a87aaf3b836c552777becb7ccf33ccf7407a79266650352f9f2d8e7c3f3c74b1bac1233221b4db12d4de07706cbf32a71c2836538ec799c6664040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040970f63f0fd4d28bda14027f406c982577cff606e3deee54e39aeed15476e748020395bdccbc3af25e58c85e3983ea3f926996f17860c308395117966e4b1be01", + "items": [ + "c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c00000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"50fcf1f49b13ae345ffe84d524c789e3\",\"sn\":0,\"ts_ms\":1792889980100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"50fcf1f49b13ae345ffe84d524c789e3\",\"sn\":1,\"ts_ms\":1792889980150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"50fcf1f49b13ae345ffe84d524c789e3\",\"sn\":2,\"ts_ms\":1792889980250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"50fcf1f49b13ae345ffe84d524c789e3\",\"sn\":3,\"ts_ms\":1792889980830,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"50fcf1f49b13ae345ffe84d524c789e3\",\"sn\":4,\"ts_ms\":1792890019000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"50fcf1f49b13ae345ffe84d524c789e3\",sn=0,st=l,sta=s,ts=1792889980100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"50fcf1f49b13ae345ffe84d524c789e3\",sn=1,st=l,ts=1792889980150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",sf=h,sid=\"50fcf1f49b13ae345ffe84d524c789e3\",sn=2,st=l,ts=1792889980250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"50fcf1f49b13ae345ffe84d524c789e3\",sn=3,st=l,sta=p,ts=1792889980830,v=2\ne=ps,sf=h,sid=\"50fcf1f49b13ae345ffe84d524c789e3\",sn=4,st=l,sta=e,ts=1792890019000,v=2", + "outcome": { + "outcome": "accept", + "digest": "997b1d01c693c3e7bfd6bee537071abb228f496fb4f9a7bdbeaee48c31a85e0a", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792889980000000, + "end": 1792890020000000, + "ended": "user", + "presented_ms": 38170, + "viewable_ms": 38170, + "audible_ms": 38170, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 730, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_44", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e25783ca5036b1ec7d29b824ccba88572ff74edbe0385079f05d05a709cb094e9a970c8ee196b49fd0d310f80d9f768f7b01009ef4bd9f5e060000f856c09f5e06000101140068747470733a2f2f656467652e6578616d706c65010096610c786f03bbdb92e899b5a4aa3659048eba07778a905c25d4e660b8a82cf90000010000004803000010950000102701e40200000000000000000000000000000000000000000000000000009302365d04ffe0dc5347961338f1b2579b41d052c51c86dc7ea386bf4df3c1e400000000000000000000000000000000000000000000000000000000000000000203d0dd0600000000000304000000000000000000000021af1a372640af54cd3bad44e7e480f4f88ad62cdd2ea73ad98ff6ad8293abb1e02d25d0dfb5bc10138658ca40ae5d1adc40e5ff17692f2cf6ba5358e04c2e0a", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e5783ca5036b1ec7d29b824ccba88572ff74edbe0385079f05d05a709cb094e9a009ef4bd9f5e060080348dbe9f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000337a39bd3f6b7e9baed668d597aa6dae34bcfb6ea4cdcf7dd36cd1b67a9584116aa57f38b82fee8df91021bc1708d8347b84262ada79b95d6aea5f9ace0b8c36040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0409831624e0e443fc7347ce261b02112d10775a9717ceba5fa31a3e1b5916907714c7aec6a431e27ec89c969194aa99b26e74ac7b6020494118dcf633f772c300a", + "items": [ + "3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee200000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"970c8ee196b49fd0d310f80d9f768f7b\",\"sn\":0,\"ts_ms\":1792890040100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"970c8ee196b49fd0d310f80d9f768f7b\",\"sn\":1,\"ts_ms\":1792890040150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"970c8ee196b49fd0d310f80d9f768f7b\",\"sn\":2,\"ts_ms\":1792890040250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"970c8ee196b49fd0d310f80d9f768f7b\",\"sn\":3,\"ts_ms\":1792890040840,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"970c8ee196b49fd0d310f80d9f768f7b\",\"sn\":4,\"ts_ms\":1792890079000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"970c8ee196b49fd0d310f80d9f768f7b\",sn=0,st=l,sta=s,ts=1792890040100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"970c8ee196b49fd0d310f80d9f768f7b\",sn=1,st=l,ts=1792890040150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",sf=h,sid=\"970c8ee196b49fd0d310f80d9f768f7b\",sn=2,st=l,ts=1792890040250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"970c8ee196b49fd0d310f80d9f768f7b\",sn=3,st=l,sta=p,ts=1792890040840,v=2\ne=ps,sf=h,sid=\"970c8ee196b49fd0d310f80d9f768f7b\",sn=4,st=l,sta=e,ts=1792890079000,v=2", + "outcome": { + "outcome": "accept", + "digest": "32dea317e1aef290450f48bc59f563bb09531bb342769cd8dd88ca7aaff28dac", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792890040000000, + "end": 1792890080000000, + "ended": "ended", + "presented_ms": 38160, + "viewable_ms": 38160, + "audible_ms": 38160, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 740, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 450000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_45", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2cb7299692ccb6b072a4e1bf9429a7a1dfae77b95adb565f5b094e70630ac729486fe3252e1e978d6a2a070dfbbee6d5b01002588c19f5e0600007feac39f5e06000201140068747470733a2f2f656467652e6578616d706c6501004e9480a9c814f37c27c1501516cc4929e4d58eb13ae4d6c458c1e5f74b5ea67500000200000052030000be230000102701c02b0000986c0000102701ee02000001000000b00400000000000000000000000000000000000062355c39967bcc00d8d02894dc303d4864af246631ff28c296afe03495d5ad6f0000000000000000000000000000000000000000000000000000000000000000010300000000000000000000001eb315117089be9f9aacc1a3d71f872809d9dcb5ced68e5289907313e38ae448c2f32581c4c0cf4a16833487a29d41327bfac9c515cd524bbd1e97d706174505", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6ecb7299692ccb6b072a4e1bf9429a7a1dfae77b95adb565f5b094e70630ac7294002588c19f5e060080bb20c29f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000a512307eb525ef68137406bb53edc1cfd963fe40ad9e3b0b1d80c578766f02648514c44dc900d081093ddf8e6883676f5c9d06e065ed016d6254c2d0a80639f5040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a04019cb7055767be069a5fa8162c83631f1522f4eb3d7ff028b382d69260647504690a10f4bde9a05e9fc44e478d97cb96b5f06825f1d1a7dcfd41f366ac4e5500a", + "items": [ + "90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d8100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"86fe3252e1e978d6a2a070dfbbee6d5b\",\"sn\":0,\"ts_ms\":1792890100100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"86fe3252e1e978d6a2a070dfbbee6d5b\",\"sn\":1,\"ts_ms\":1792890100150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"86fe3252e1e978d6a2a070dfbbee6d5b\",\"sn\":2,\"ts_ms\":1792890100250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"86fe3252e1e978d6a2a070dfbbee6d5b\",\"sn\":3,\"ts_ms\":1792890100850,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"86fe3252e1e978d6a2a070dfbbee6d5b\",\"sn\":4,\"ts_ms\":1792890110000,\"event\":\"stall-start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"86fe3252e1e978d6a2a070dfbbee6d5b\",\"sn\":5,\"ts_ms\":1792890111200,\"event\":\"stall-end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"86fe3252e1e978d6a2a070dfbbee6d5b\",\"sn\":6,\"ts_ms\":1792890139000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"86fe3252e1e978d6a2a070dfbbee6d5b\",sn=0,st=l,sta=s,ts=1792890100100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"86fe3252e1e978d6a2a070dfbbee6d5b\",sn=1,st=l,ts=1792890100150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",sf=h,sid=\"86fe3252e1e978d6a2a070dfbbee6d5b\",sn=2,st=l,ts=1792890100250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"86fe3252e1e978d6a2a070dfbbee6d5b\",sn=3,st=l,sta=p,ts=1792890100850,v=2\ne=ps,sf=h,sid=\"86fe3252e1e978d6a2a070dfbbee6d5b\",sn=4,st=l,sta=r,ts=1792890110000,v=2\ne=ps,sf=h,sid=\"86fe3252e1e978d6a2a070dfbbee6d5b\",sn=5,st=l,sta=p,ts=1792890111200,v=2\ne=ps,sf=h,sid=\"86fe3252e1e978d6a2a070dfbbee6d5b\",sn=6,st=l,sta=e,ts=1792890139000,v=2", + "outcome": { + "outcome": "accept", + "digest": "6af81788a9e3568c54d1edefb52f88d92777a050aa177eaabd1ba2852ef29794", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792890100000000, + "end": 1792890140000000, + "ended": "user", + "presented_ms": 36950, + "viewable_ms": 36950, + "audible_ms": 36950, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 750, + "stalls": 1, + "stall_ms": 1200, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_46", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e22d702bc97f81d9d4e4c10446af164454c008bdd55a9ee52c5d37a4da72b827caebae340670757849e93241d20dbf08080100ac1bc59f5e060000067ec79f5e06000201140068747470733a2f2f656467652e6578616d706c650100a66233226b1a3d0e0a163fe4df2decf0a35d6eb108c25b515c1de246f62a55770000010000005c030000fc940000102701f80200000000000000000000000000000000000000000000000000007461da3a133942379bffe687bc21741ecba557b4f5ff990d7e41335dee0ca34100000000000000000000000000000000000000000000000000000000000000000203f02b070000000000030400000000000000000000009071aee870f6d7bde2f81b2f0004052c423246179a46afa069461cb16da0974454a451b3aa533b99626fa6c73285312ef915d17870c76407d98416c4671c8106", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e2d702bc97f81d9d4e4c10446af164454c008bdd55a9ee52c5d37a4da72b827ca00ac1bc59f5e06008042b4c59f5e060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000200300000000000038d05149bc5379282245bcf7547fc45a42705115272c20886ddaf10872c388cb9b636335eafac219654e59520ed4ab116ce4e164055dac0d8927a1fceb81ca03040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0403d05de07e427ab4e155f3c5413a7ef0d03ce0e4ab4a8374ff50c6e120c998752e686c3debbc62763b8eae23fdab0b1606e4ea6f1e78a96c08ac8e811fdf2ee0b", + "items": [ + "d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e5100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ebae340670757849e93241d20dbf0808\",\"sn\":0,\"ts_ms\":1792890160100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ebae340670757849e93241d20dbf0808\",\"sn\":1,\"ts_ms\":1792890160150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ebae340670757849e93241d20dbf0808\",\"sn\":2,\"ts_ms\":1792890160250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ebae340670757849e93241d20dbf0808\",\"sn\":3,\"ts_ms\":1792890160860,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ebae340670757849e93241d20dbf0808\",\"sn\":4,\"ts_ms\":1792890199000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"ebae340670757849e93241d20dbf0808\",sn=0,st=l,sta=s,ts=1792890160100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"ebae340670757849e93241d20dbf0808\",sn=1,st=l,ts=1792890160150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",sf=h,sid=\"ebae340670757849e93241d20dbf0808\",sn=2,st=l,ts=1792890160250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"ebae340670757849e93241d20dbf0808\",sn=3,st=l,sta=p,ts=1792890160860,v=2\ne=ps,sf=h,sid=\"ebae340670757849e93241d20dbf0808\",sn=4,st=l,sta=e,ts=1792890199000,v=2", + "outcome": { + "outcome": "accept", + "digest": "01eafd8d4f8736017bcc0ba3b8ff11a75271ec25fb4987caaebb53832c8e3072", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792890160000000, + "end": 1792890200000000, + "ended": "user", + "presented_ms": 38140, + "viewable_ms": 38140, + "audible_ms": 38140, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 760, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 470000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_47", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2609eed9574f109f5e396e3a0275722f5492a8b3b7637ef08699ec4e64f7265899341a2fada4accc49696548a8d48a392010033afc89f5e0600008d11cb9f5e06000201140068747470733a2f2f656467652e6578616d706c6501007f96a1159b26296773c8ade421e3257431e49c289c8f34ba40d02ec1e2231e2200000100000066030000f2940000102701020300000000000000000000000000000000000000000000000000005ea607d12edd55a0ff9f9408d8ce18d452170814c346415460ab99173f7bb2ea000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000006a88a9ac159c0122237c5e39552cef7130917cfcf973dcb62b5dd7f9653c2f2c8f8a4c28cf4b1f9051e6d181efb91047c48b3ad298b482d06216a576b147b708", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e609eed9574f109f5e396e3a0275722f5492a8b3b7637ef08699ec4e64f7265890033afc89f5e060080c947c99f5e06000000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000020030000000000000ccbb165ec882a87aaf3b836c552777becb7ccf33ccf7407a79266650352f9f2d8e7c3f3c74b1bac1233221b4db12d4de07706cbf32a71c2836538ec799c6664040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040af695991633deeee1be03ed28d1e9bcec66e480c1de195b59a829097c8476f52205fda97d0daf66b364d4a7a0f86843b5e1a6e6327c87effcaad09ee16c41703", + "items": [ + "c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c00000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"9341a2fada4accc49696548a8d48a392\",\"sn\":0,\"ts_ms\":1792890220100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"9341a2fada4accc49696548a8d48a392\",\"sn\":1,\"ts_ms\":1792890220150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"9341a2fada4accc49696548a8d48a392\",\"sn\":2,\"ts_ms\":1792890220250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"9341a2fada4accc49696548a8d48a392\",\"sn\":3,\"ts_ms\":1792890220870,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"9341a2fada4accc49696548a8d48a392\",\"sn\":4,\"ts_ms\":1792890259000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"9341a2fada4accc49696548a8d48a392\",sn=0,st=l,sta=s,ts=1792890220100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"9341a2fada4accc49696548a8d48a392\",sn=1,st=l,ts=1792890220150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",sf=h,sid=\"9341a2fada4accc49696548a8d48a392\",sn=2,st=l,ts=1792890220250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"9341a2fada4accc49696548a8d48a392\",sn=3,st=l,sta=p,ts=1792890220870,v=2\ne=ps,sf=h,sid=\"9341a2fada4accc49696548a8d48a392\",sn=4,st=l,sta=e,ts=1792890259000,v=2", + "outcome": { + "outcome": "accept", + "digest": "732cc79b7bce39ed48edc710e8ede3201c6d6971d7705f424cb327394bbd99fb", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792890220000000, + "end": 1792890260000000, + "ended": "user", + "presented_ms": 38130, + "viewable_ms": 38130, + "audible_ms": 38130, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 770, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_48", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2e9f831b9a2b987c253cb2add0fc423dc66fb822cf939c0d124d784d934468575fe392e5898d4edb3ec969a73827b0a260100ba42cc9f5e06000014a5ce9f5e06000101140068747470733a2f2f656467652e6578616d706c650100fcc065674c0b83da2bff9bee4dcd76a64fa19105334f1ca6b086d49eb13b169a00000100000070030000e89400001027000c03000000000000000000000000000000000000000000000000000003a43ef36b34e9b971c57194a7c2680c344f7161dc42ff5b7a6d8ccf7299494000000000000000000000000000000000000000000000000000000000000000000103107a070000000000030000e84d26398132aa5f37999e2715b2195aa4c94156ca381be777f51b8869c6374d0e9ffcb0f8d888d40b61841afa84dcd5b80371800844f41cac7f993026380903", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6ee9f831b9a2b987c253cb2add0fc423dc66fb822cf939c0d124d784d93446857500ba42cc9f5e06008050dbcc9f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000337a39bd3f6b7e9baed668d597aa6dae34bcfb6ea4cdcf7dd36cd1b67a9584116aa57f38b82fee8df91021bc1708d8347b84262ada79b95d6aea5f9ace0b8c36040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a04002df757edf9eed247adee442ad656461e0467d79688941662048110e3f414f3ecf65f9166119c979e747131aa3b111bfebe186dc27735356b0b917551dd9fa07", + "items": [ + "3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee200000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"fe392e5898d4edb3ec969a73827b0a26\",\"sn\":0,\"ts_ms\":1792890280100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"fe392e5898d4edb3ec969a73827b0a26\",\"sn\":1,\"ts_ms\":1792890280150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"fe392e5898d4edb3ec969a73827b0a26\",\"sn\":2,\"ts_ms\":1792890280250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"fe392e5898d4edb3ec969a73827b0a26\",\"sn\":3,\"ts_ms\":1792890280880,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"fe392e5898d4edb3ec969a73827b0a26\",\"sn\":4,\"ts_ms\":1792890319000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"fe392e5898d4edb3ec969a73827b0a26\",sn=0,st=l,sta=s,ts=1792890280100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"fe392e5898d4edb3ec969a73827b0a26\",sn=1,st=l,ts=1792890280150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"3b01c38f12c3ff1ecd75e202ec530be7af7bbe775953105b8fdf911c62ef6ee2\",sf=h,sid=\"fe392e5898d4edb3ec969a73827b0a26\",sn=2,st=l,ts=1792890280250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"fe392e5898d4edb3ec969a73827b0a26\",sn=3,st=l,sta=p,ts=1792890280880,v=2\ne=ps,sf=h,sid=\"fe392e5898d4edb3ec969a73827b0a26\",sn=4,st=l,sta=e,ts=1792890319000,v=2", + "outcome": { + "outcome": "accept", + "digest": "71fe53acc27b672053f858b487bab6cf57c30b8275bb91332901b6d060944166", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792890280000000, + "end": 1792890320000000, + "ended": "ended", + "presented_ms": 38120, + "viewable_ms": 38120, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 780, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 490000 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_49", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e20471de7d25456ee3067d7830f1df3462fbb0abc4509016af839758096db695f8beed8d6bc6b3e02042877c1481d42794010041d6cf9f5e0600009b38d29f5e06000201140068747470733a2f2f656467652e6578616d706c650100586ec5880066987dede3703b2c5e8b9a996f1c13d3c3c5a44134b4edf9de85e60000010000007a030000de940000d0070116030000000000000000000000000000000000000000000000000000e311a7ace93274a869dbd1610fa78dbf9c01a6c6d2ede49d4d72f0abb4274e9b000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000005b837fbcaeb1b048530103202860b5db62b5d94d4026b79d1064fde3650118222d4990d0da1eb1573584d06f2b75c93de76412cfa959e4bc965ed373d447570f", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e0471de7d25456ee3067d7830f1df3462fbb0abc4509016af839758096db695f80041d6cf9f5e060080d76ed09f5e0600000000000000000000000000000000000000000000000000000000000000000002000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002003000000000000a512307eb525ef68137406bb53edc1cfd963fe40ad9e3b0b1d80c578766f02648514c44dc900d081093ddf8e6883676f5c9d06e065ed016d6254c2d0a80639f5040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a04014c26ab4c03087c853aec913228e6c4ca56cfb3fbdcbc1464c9d4bb0ea33bf01242c34d976ffb0a42ecae262d506da287864b3eb7037fb1db84b8b2688cadc09", + "items": [ + "90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d8100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"beed8d6bc6b3e02042877c1481d42794\",\"sn\":0,\"ts_ms\":1792890340100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"beed8d6bc6b3e02042877c1481d42794\",\"sn\":1,\"ts_ms\":1792890340150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"beed8d6bc6b3e02042877c1481d42794\",\"sn\":2,\"ts_ms\":1792890340250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"beed8d6bc6b3e02042877c1481d42794\",\"sn\":3,\"ts_ms\":1792890340890,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"beed8d6bc6b3e02042877c1481d42794\",\"sn\":4,\"ts_ms\":1792890379000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"beed8d6bc6b3e02042877c1481d42794\",sn=0,st=l,sta=s,ts=1792890340100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"beed8d6bc6b3e02042877c1481d42794\",sn=1,st=l,ts=1792890340150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"90b1279e771c93d2077dea917fe9343fc65f6e0456fc393f293587915d3c6d81\",sf=h,sid=\"beed8d6bc6b3e02042877c1481d42794\",sn=2,st=l,ts=1792890340250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"beed8d6bc6b3e02042877c1481d42794\",sn=3,st=l,sta=p,ts=1792890340890,v=2\ne=ps,sf=h,sid=\"beed8d6bc6b3e02042877c1481d42794\",sn=4,st=l,sta=e,ts=1792890379000,v=2", + "outcome": { + "outcome": "accept", + "digest": "41b659fcab0bab6c2760eaea0a95d5bec1b880692ddd76b2ada32ea9af7f347a", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792890340000000, + "end": 1792890380000000, + "ended": "user", + "presented_ms": 38110, + "viewable_ms": 0, + "audible_ms": 38110, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 790, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_50", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2be6c910f2029d35a77de33129445c05acb3b643dbb8150adb21fdb4fef76ea8a41e8048ff2ae36bd68844a643085a86b0100c869d39f5e06000022ccd59f5e06000201140068747470733a2f2f656467652e6578616d706c6501007bddac5e6bf44494b7ef6af74da4dfab15213c7e55379c8a40315ece5f1fef37000002000000840300008c230000102701c02b0000986c00001027012003000001000000b0040000000000000000000000000000000000000bd33083c919d9cd6e2fa62dc7e2b142e8f66105354df16d035d28314a1dd64e0000000000000000000000000000000000000000000000000000000000000000020330c8070000000000030400000000000000000000006e55e96e0ea3870450709c1ecfe3ed500edc4aa4eefc239c8a4a8543935484c58c1b780e57c7f39a93506dfa2a6cde7ab39a77e5c50d7bdd12f602c5c0c61d06", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6ebe6c910f2029d35a77de33129445c05acb3b643dbb8150adb21fdb4fef76ea8a00c869d39f5e0600805e02d49f5e060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000200300000000000038d05149bc5379282245bcf7547fc45a42705115272c20886ddaf10872c388cb9b636335eafac219654e59520ed4ab116ce4e164055dac0d8927a1fceb81ca03040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a0402970ced973b4125ba640c4f5d4531ff2ac1682f1838b543de2794037680dc3a363f07101a5f4d63df1cd5dd64fc6da1572cea8103ed67658953ebb6c1dba130a", + "items": [ + "d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e5100000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"41e8048ff2ae36bd68844a643085a86b\",\"sn\":0,\"ts_ms\":1792890400100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"41e8048ff2ae36bd68844a643085a86b\",\"sn\":1,\"ts_ms\":1792890400150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"41e8048ff2ae36bd68844a643085a86b\",\"sn\":2,\"ts_ms\":1792890400250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"41e8048ff2ae36bd68844a643085a86b\",\"sn\":3,\"ts_ms\":1792890400900,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"41e8048ff2ae36bd68844a643085a86b\",\"sn\":4,\"ts_ms\":1792890410000,\"event\":\"stall-start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"41e8048ff2ae36bd68844a643085a86b\",\"sn\":5,\"ts_ms\":1792890411200,\"event\":\"stall-end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"41e8048ff2ae36bd68844a643085a86b\",\"sn\":6,\"ts_ms\":1792890439000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"41e8048ff2ae36bd68844a643085a86b\",sn=0,st=l,sta=s,ts=1792890400100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"41e8048ff2ae36bd68844a643085a86b\",sn=1,st=l,ts=1792890400150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"d16272b0a34812206ffde892e8921a628e6bc2439782049597c9a74ade317e51\",sf=h,sid=\"41e8048ff2ae36bd68844a643085a86b\",sn=2,st=l,ts=1792890400250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"41e8048ff2ae36bd68844a643085a86b\",sn=3,st=l,sta=p,ts=1792890400900,v=2\ne=ps,sf=h,sid=\"41e8048ff2ae36bd68844a643085a86b\",sn=4,st=l,sta=r,ts=1792890410000,v=2\ne=ps,sf=h,sid=\"41e8048ff2ae36bd68844a643085a86b\",sn=5,st=l,sta=p,ts=1792890411200,v=2\ne=ps,sf=h,sid=\"41e8048ff2ae36bd68844a643085a86b\",sn=6,st=l,sta=e,ts=1792890439000,v=2", + "outcome": { + "outcome": "accept", + "digest": "f66ca6fb99c9f5ac5f7e397bd7efe62493d564725381639f896dce435d127037", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792890400000000, + "end": 1792890440000000, + "ended": "user", + "presented_ms": 36900, + "viewable_ms": 36900, + "audible_ms": 36900, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 800, + "stalls": 1, + "stall_ms": 1200, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "joules_micro": 510000 + }, + { + "role": "display", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "fifty_51", + "bundle": { + "v": 1, + "session": "5741533101949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e281e98ce8477c4665c02a824ac20af05427ab8ebc7e3d44b8f0686ec5e3cac971b75383051d0d115064be9be8a6a90bee01004ffdd69f5e060000a95fd99f5e06000201140068747470733a2f2f656467652e6578616d706c6501006b833cd5a0051b1e03ed22e84061d38740cbe9998d062d76d4c1b75e1b03dd3e0000010000008e030000ca9400001027002a0300000000000000000000000000000000000000000000000000005a886b260cb5e026ed30e48f30dfa0a1477cd403fd29908f342345d7599da822000000000000000000000000000000000000000000000000000000000000000001030000000000000000000000186744dc0699e07d8556a993fa08b6a897770f8d388c3c194e56cccc63a2492526ebf63d971925e60f45e50a94e8561c26532ca3df2f97a70267c932e080ac0c", + "acks": [ + { + "record": "5744413101010262346a63fcbfaf78ced9db2ff7980e4aa5626cf590546171fa5047399b901d6e81e98ce8477c4665c02a824ac20af05427ab8ebc7e3d44b8f0686ec5e3cac971004ffdd69f5e060080e595d79f5e06000000000000000000000000000000000000000000000000000000000000000000020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000020030000000000000ccbb165ec882a87aaf3b836c552777becb7ccf33ccf7407a79266650352f9f2d8e7c3f3c74b1bac1233221b4db12d4de07706cbf32a71c2836538ec799c6664040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000040100000000000051be969a94e5392c1b9c5e14f2bb11befa48556b5086654684aa240a4a91e0d1f7bc20f30823ba360720b6df5fed07f4fb79ea02b3faa6a951a94aac2bc5a040dc77ec861669df30f0b02336e241471e3cc611cbece13b2a5404ff11548636e69dc97bb22879f7e3886ddac402d2054345f8756cef50c30204f2a16a5128dc08", + "items": [ + "c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c00000000000000002003000000000000", + "cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de300000000000000000401000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"b75383051d0d115064be9be8a6a90bee\",\"sn\":0,\"ts_ms\":1792890460100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b75383051d0d115064be9be8a6a90bee\",\"sn\":1,\"ts_ms\":1792890460150,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b75383051d0d115064be9be8a6a90bee\",\"sn\":2,\"ts_ms\":1792890460250,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b75383051d0d115064be9be8a6a90bee\",\"sn\":3,\"ts_ms\":1792890460910,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b75383051d0d115064be9be8a6a90bee\",\"sn\":4,\"ts_ms\":1792890499000,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "cmcd": "e=ps,sf=h,sid=\"b75383051d0d115064be9be8a6a90bee\",sn=0,st=l,sta=s,ts=1792890460100,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"cc02a12f7f38316dab3e28b730c8138b506c3fd207cfa8d9f0f3a15a38fe9de3\",sf=h,sid=\"b75383051d0d115064be9be8a6a90bee\",sn=1,st=l,ts=1792890460150,v=2\ne=rr,rc=200,science.transaction.wai-ch=\"c5b2539363f5d26a9807d57f34e26aece1f79dc67c983530f9c1b9a18a22538c\",sf=h,sid=\"b75383051d0d115064be9be8a6a90bee\",sn=2,st=l,ts=1792890460250,v=2\ne=ps,science.transaction.wai-pl=\"1\",science.transaction.wai-u=\"0\",sf=h,sid=\"b75383051d0d115064be9be8a6a90bee\",sn=3,st=l,sta=p,ts=1792890460910,v=2\ne=ps,sf=h,sid=\"b75383051d0d115064be9be8a6a90bee\",sn=4,st=l,sta=e,ts=1792890499000,v=2", + "outcome": { + "outcome": "accept", + "digest": "5655b6a86942fb721801292ad9f037a06350cd4530404e5b996128ec2891a173", + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "presentation": "live", + "start": 1792890460000000, + "end": 1792890500000000, + "ended": "user", + "presented_ms": 38090, + "viewable_ms": 38090, + "audible_ms": 0, + "ad_ms": 0, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 1, + "bytes": 800 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 1, + "bytes": 260 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "startup_ms": 810, + "stalls": 0, + "stall_ms": 0, + "energy": [ + { + "role": "sink_decode", + "class": "unmetered", + "joules_micro": 0 + } + ], + "claims": [], + "notes": 0, + "attachments": 0 + } + }, + { + "name": "bundle_not_json", + "bundle_text": "{\"v\":1,\"session\":", + "outcome": { + "outcome": "refuse", + "reason": "audit_bundle_invalid", + "at": [ + "bundle" + ] + } + }, + { + "name": "bundle_member_missing", + "bundle_text": "{\"v\":1,\"session\":\"57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000008712ce0afd2b0ea1c2d2c36a40722b3423da9440a86d0c308012ca2b30dbc2ec5f429cd418f24c72bf00cbb4a92dabdcbab447675a7b77294bf83eb7e3b21c0a\",\"acks\":[{\"record\":\"574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f\",\"items\":[\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000\"]}],\"claims\":[],\"log\":\"[[{\\\"v\\\":1,\\\"sid\\\":\\\"ac3053630eb9973bb5d0544e657dc170\\\",\\\"sn\\\":0,\\\"ts_ms\\\":1792886600100,\\\"event\\\":\\\"start\\\",\\\"cid\\\":null,\\\"st\\\":\\\"l\\\",\\\"sf\\\":\\\"h\\\",\\\"object\\\":null,\\\"present\\\":null,\\\"switch\\\":null,\\\"layer_change\\\":null,\\\"capable\\\":null,\\\"cohort\\\":null,\\\"metrics\\\":null,\\\"claim\\\":null},{\\\"v\\\":1,\\\"sid\\\":\\\"ac3053630eb9973bb5d0544e657dc170\\\",\\\"sn\\\":1,\\\"ts_ms\\\":1792886600200,\\\"event\\\":\\\"response\\\",\\\"cid\\\":null,\\\"st\\\":\\\"l\\\",\\\"sf\\\":\\\"h\\\",\\\"object\\\":{\\\"ot\\\":null,\\\"url\\\":null,\\\"content_hash\\\":\\\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\\\",\\\"capability\\\":null,\\\"rendition\\\":null,\\\"layer\\\":null,\\\"layers_total\\\":null,\\\"layer_cap\\\":null,\\\"counted\\\":null,\\\"transferred\\\":null,\\\"d_ms\\\":null,\\\"dl_ms\\\":null,\\\"ttfb_ms\\\":null,\\\"ttlb_ms\\\":null,\\\"rc\\\":200,\\\"path\\\":null,\\\"receipt\\\":null},\\\"present\\\":null,\\\"switch\\\":null,\\\"layer_change\\\":null,\\\"capable\\\":null,\\\"cohort\\\":null,\\\"metrics\\\":null,\\\"claim\\\":null},{\\\"v\\\":1,\\\"sid\\\":\\\"ac3053630eb9973bb5d0544e657dc170\\\",\\\"sn\\\":2,\\\"ts_ms\\\":1792886604900,\\\"event\\\":\\\"end\\\",\\\"cid\\\":null,\\\"st\\\":\\\"l\\\",\\\"sf\\\":\\\"h\\\",\\\"object\\\":null,\\\"present\\\":null,\\\"switch\\\":null,\\\"layer_change\\\":null,\\\"capable\\\":null,\\\"cohort\\\":null,\\\"metrics\\\":null,\\\"claim\\\":null}]]\",\"attachments\":[]}", + "outcome": { + "outcome": "refuse", + "reason": "audit_bundle_invalid", + "at": [ + "bundle" + ] + } + }, + { + "name": "session_truncated", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "truncated", + "at": [ + "session" + ] + } + }, + { + "name": "session_trailing_byte", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000008712ce0afd2b0ea1c2d2c36a40722b3423da9440a86d0c308012ca2b30dbc2ec5f429cd418f24c72bf00cbb4a92dabdcbab447675a7b77294bf83eb7e3b21c0a00", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "malformed", + "at": [ + "session" + ] + } + }, + { + "name": "session_version_2", + "bundle": { + "v": 1, + "session": "57415331027fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000008712ce0afd2b0ea1c2d2c36a40722b3423da9440a86d0c308012ca2b30dbc2ec5f429cd418f24c72bf00cbb4a92dabdcbab447675a7b77294bf83eb7e3b21c0a", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "unknown_version", + "at": [ + "session" + ] + } + }, + { + "name": "session_origin_with_path", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e060005011a0068747470733a2f2f63646e2d612e6578616d706c652f6c6976650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a67576300000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000551002b947f41e56850d7e5c24548f1d1a6dd9652f8705553c961378b319422f2d9a7bda3c7bbf982d9d40d0b8adbd0d87a84af5d30ef38facba429ffee01809", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_session_invalid", + "at": [ + "session" + ] + } + }, + { + "name": "session_unmetered_with_joules", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a67576300000000000000000000000000000000000000000000000000000000000000000203050000000000000000040000000000000000000000bc284ecf74157f09e866c9ce5198707d5fa711533c069db24c427453a2fb368ea3ab59f96c668b9d1b82b4c30e0b240bcd462fa0035f93eebaab2ba3f6ed2602", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "energy_invalid", + "at": [ + "session" + ] + } + }, + { + "name": "session_small_order_key", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e20100000000000000000000000000000000000000000000000000000000000000ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a67576300000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000c5f7a65cceff9440958428c3906efe48e25373193129e01d7cbd93d3798e14b39fb3c8a70413bfbca3930d1594792c54b3a9c61472a2f6bf627a8736442dcb0d", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "bad_key", + "at": [ + "session" + ] + } + }, + { + "name": "session_bad_signature", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000008712ce0afd2b0ea1c2d2c36a40722b3423da9440a86d0c308012ca2b30dbc2ec5f429cd418f24c72bf00cbb4a92dabdcbab447675a7b77294bf83eb7e3b21c0b", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "bad_signature", + "at": [ + "session" + ] + } + }, + { + "name": "session_wrong_owner", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa358a4241a77d48bd8339444487081e79c3e36a8d53de244968853241d5dc398939d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a67576300000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000b463b79b82a0758ec5006cef7222bff853002097b64fe68561eff916c72f486c203ffddb3628066832bbbc1cd90794755e7caf8893fcc79496a3f636cec76a0a", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_wrong_owner", + "at": [ + "session" + ] + } + }, + { + "name": "session_unknown_channel", + "bundle": { + "v": 1, + "session": "5741533101dd734893da53b8002675586a06b1b33bbad58b1d271010e7969be1d4241e1b63775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240cf541f02a68b90a9329b43c22c899a0010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a67576300000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000e37be5c5a3dce61bd9bfb5efde2a9f4a1ae74525a1eba193dbb07b902c9d7621d84cb5766bc6332ca7944282629be56ec997c1b79f742fb5f261d54c31519601", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_unknown_channel", + "at": [ + "session" + ] + } + }, + { + "name": "session_unbound", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce2409090909090909090909090909090909010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000000d7d1c5423275ba0edb36f119781577f77bb82ae41230623627a76e67110ec2d24b414922b1701fde67b9ad81b03536b079a7ede92cf86bc31db6e7989bce809", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_session_unbound", + "at": [ + "session" + ] + } + }, + { + "name": "ack_not_given", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000008712ce0afd2b0ea1c2d2c36a40722b3423da9440a86d0c308012ca2b30dbc2ec5f429cd418f24c72bf00cbb4a92dabdcbab447675a7b77294bf83eb7e3b21c0a", + "acks": [], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "record_missing", + "at": [ + "ack", + 0 + ] + } + }, + { + "name": "ack_entries_out_of_order", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100615cce143a8fbf9f14ea7e8c56d30a8ab7f4b69ddf2e430b241c3ff2c6ba7885000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000007c9524b8dd87f0a458524528f6f22f2e8737775891b77938a4f8ba2a335d320a9b99c731fa25d2b588e2f6a134fc1cb2480183f06f306c9e0da8119950eba709", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000002000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000084030000000000008f0f88ef23620bdc5b422014f1dec246410493c8bf5c80ae270dcd3d2adf0ae810425fa426a3bb93a977dca7bedfb1b529a42c603c921037b986b4d91eb8e1d96b5575119b0e435d5366fddf5a7f907786bbec2ed141b3eedf7d4c76461e1fbf34de990e8bf84deb927622cd538af9fe9aa787f209a334d5ea9ea1458031330a", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000", + "58c5a889717aa9f19a18257a79a4a5e1fe5ffcd3bd764110c48caa99bb25f32600000000000000008403000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "ack_invalid", + "at": [ + "ack", + 0 + ] + } + }, + { + "name": "ack_self_acknowledged", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c65010052314249395dfba210dd91e87747f3c042706df61409a311c137d66465815027000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000006350270100c1b43317331231f2c8a0ab7e0c1a2f5005890a8b19bb79447a6280ae7289633252ab8dc25d800d88b65e652a667ede65bb2304b5bec7e66cc34304", + "acks": [ + { + "record": "574441310101029d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce249d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c070a10a28be0f70a0deefb3feff7c38955d6e86dd73161ecb855bc4409ef2408aee013496377df0419942f1bf295001386d30b45e584d8205a2ed80b21a82dc0a", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "self_acknowledged", + "at": [ + "ack", + 0 + ] + } + }, + { + "name": "ack_by_another_key", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100603e217a9b17874698003295a0f118e710b7c97cdc9f85f2a8d3be252b7b66a3000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a6757630000000000000000000000000000000000000000000000000000000000000000020300000000000000000004000000000000000000000079f403860c12121675712dee22e39bf15d8dc24c1d42996fc79b29ccd267cbfea0c53af76ff71a5d295e64fc6bf8810ff659b3380621695fc12bdf8ba82bfd07", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e658a4241a77d48bd8339444487081e79c3e36a8d53de244968853241d5dc398930062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c0ccbf0789ab89325e3980b79fbec2df766c1abca2e2932fb3d231d68b5206b594ca8d11d2411212fba608b349f47365200f1a2d33cd77e526d74d18ad7314f207", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_ack_unbound", + "at": [ + "ack", + 0 + ] + } + }, + { + "name": "ack_from_unlisted_origin", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c6501006e86a3b53f595736566943796fa7e6fc068dfb09c0abc0d69e8accd503dc00f7000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a67576300000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000b1e4eb870b438f3c311040255650f17431041eb61e12359da97b9a87382553647a5f8baa94171715840ab08f543745b54bfe56d4ee75b1485fff6b15b8e54b0f", + "acks": [ + { + "record": "5744413101010263979526fc540a4904d850e7ebe7445fd2d0ff5ef4a1a85822e85ff3a12f32a09d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c000be7197b7af9b0b00c97bb83575fc120780983f1141dd9ae9bc284205b4deb9b438b6c7e553d6d545aa216503f8ceca1bf65ff1533681113d602000af4c0804", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_origin_unknown", + "at": [ + "ack", + 0 + ] + } + }, + { + "name": "ack_past_session_end", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c65010098e00bb99fad54613609edff2fa666dc9e9c1a4fc7999ec3eb2abecbbe3a8cb3000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a67576300000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000d3dd311b68445ecbc74cda1d3fe1e4870b194135eb394e48153f85d2e9c8211b1350d21403c3c0ab40034b2bb84db657758439efa581ecb7cfd6453e700e6908", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060041ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c0a6687fe6dad8767c69e8e22a983fd1fde0b7bba047ce8bc403dbc1b3bf58aba90c6ffb714b64dd932008dcd78c3e525190fafdf2245a8603a0c8c1020a763c0c", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "ack_outside_period", + "at": [ + "ack", + 0 + ] + } + }, + { + "name": "ack_fetch_class_in_live", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100b6aa8f02e3954b72277a1b9078052c9eddcdca4a30c8979116c39fdbad88d4b9000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a67576300000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000feea4bedb710ffbc48866196823b4ce99ce24405de43129b4bbd586402864aea064951b17d89d784ccf54836ffcd677c02d93b2283e0f9b32d8621ca9c0bfc01", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e06000000000000000000000000000000000000000000000000000000000000000000020002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000084030000000000008f0f88ef23620bdc5b422014f1dec246410493c8bf5c80ae270dcd3d2adf0ae810425fa426a3bb93a977dca7bedfb1b529a42c603c921037b986b4d91eb8e1d90400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c09797d665e95f1861a084822645aec294a7320b70320bf560c56057a3481b7ae1876803254d3e0c8eed507836a6fb8ce3bdff263ee2f2adf6969302aaa1ed2a05", + "items": [ + "58c5a889717aa9f19a18257a79a4a5e1fe5ffcd3bd764110c48caa99bb25f32600000000000000008403000000000000", + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_ack_invalid", + "at": [ + "ack", + 0 + ] + } + }, + { + "name": "items_not_whole", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000008712ce0afd2b0ea1c2d2c36a40722b3423da9440a86d0c308012ca2b30dbc2ec5f429cd418f24c72bf00cbb4a92dabdcbab447675a7b77294bf83eb7e3b21c0a", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c010000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "items_invalid", + "at": [ + "ack", + 0, + "entry", + 0 + ] + } + }, + { + "name": "items_do_not_recompute", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000008712ce0afd2b0ea1c2d2c36a40722b3423da9440a86d0c308012ca2b30dbc2ec5f429cd418f24c72bf00cbb4a92dabdcbab447675a7b77294bf83eb7e3b21c0a", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa83aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "items_mismatch", + "at": [ + "ack", + 0, + "entry", + 0 + ] + } + }, + { + "name": "ack_windows_overlap", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c6502008e3b70bf7e79400b716e3c19084789b26389fe231147c5b5edeeb849221c83deb1ad6cac8f9e66a6b3dd3828432aa2f5fbb5309d1f4939877cc1662bf37ab601000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000002e679f48a334d96d88892737f5e6d9de78d13c8c1c785d067e2157d400efe26fd9db2bf729c6e451b250bd67805b5c71419a5cf0ee11c2d5c477691bd45bd106", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e0600c02818f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c0998b85e84fb8af06afc3441b03fcd2881239a1618c1dde6c759fc7e3097ebcab317516ef4c32f5e07304dc8791cd1b07d6c0ef2cbc2305ac89f8136503b46704", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + }, + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce2480e608f19e5e060040ad36f19e5e06008e3b70bf7e79400b716e3c19084789b26389fe231147c5b5edeeb849221c83de01000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000003601000000000000ba130e9ddbce1f6637f2d4d6bcb14ea25447bc2b73d08c0bde1c6b04ad659b0d863b83eea3228a9062d1d815a6af20f7dd683a58fe154da3b46f5db58f06cb8ebbdb357972611d478be5f022d1e4a142eae8f3d95f2c5d50771837643767eabd60e22d292f24fe68afdfc817294cf681f5d9211a36c06811f900239a45e8a102", + "items": [ + "668ab7156c0cdafd3c825e6a0509e61906d7963db2730b433a01ec0836597c6700000000000000003601000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "ack_overlap", + "at": [ + "ack", + 1 + ] + } + }, + { + "name": "ack_chain_broken", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c6502008e3b70bf7e79400b716e3c19084789b26389fe231147c5b5edeeb849221c83deedf64c398051ca4cc6b3831c32ee537a496ad6ee46a00396df91ca7d8e5854cb000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000009097f8a4d66027cd44acded669ea7a593ffe770d19cba89bfb4e7feb2b6364b3cd72554da555fd6369f77fb560b658f914065e18c8c02d26f0e8561871b4830f", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e0600c02818f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c0998b85e84fb8af06afc3441b03fcd2881239a1618c1dde6c759fc7e3097ebcab317516ef4c32f5e07304dc8791cd1b07d6c0ef2cbc2305ac89f8136503b46704", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + }, + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24c02818f19e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000003601000000000000ba130e9ddbce1f6637f2d4d6bcb14ea25447bc2b73d08c0bde1c6b04ad659b0d863b83eea3228a9062d1d815a6af20f7dd683a58fe154da3b46f5db58f06cb8e711f5804b5502c777a7cf4ff7715aacfdd4aa668f5cbb36789c13ff9becbad35f7acbb692264774687b31c7ef630199bf1762525ba3a97808773615221a1990e", + "items": [ + "668ab7156c0cdafd3c825e6a0509e61906d7963db2730b433a01ec0836597c6700000000000000003601000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "ack_chain_broken", + "at": [ + "ack", + 1 + ] + } + }, + { + "name": "log_not_the_bound_one", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000008712ce0afd2b0ea1c2d2c36a40722b3423da9440a86d0c308012ca2b30dbc2ec5f429cd418f24c72bf00cbb4a92dabdcbab447675a7b77294bf83eb7e3b21c0a", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]] ", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_document_mismatch", + "at": [ + "log" + ] + } + }, + { + "name": "log_of_two_sessions", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff00000000000000000000000000000000000000000000000081e4812eeb260200fefb6b1c3bae1d721150f6b4b9ac0cdeb34ab16cc5958f0100000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000cfe4d3e6c495f5312a7614e6324e4b973023dfb535145009c49e846bd5ca32daf78460b6a2b7b03befe9f070b2b9174cc96b3d0dcbb2462deffdd885096b200c", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}],[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "qoe_invalid", + "at": [ + "log" + ] + } + }, + { + "name": "stalls_not_the_logs", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0100000090010000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000009bfa78a8f4e6114e78c91ee9c10289a6c518e592ea7b301efc7a46eff65bf18ddc4c51bda79ea3504f808f44226fb793263cd7078e399b058fd2c28d6bdd8e0a", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_qoe_mismatch", + "at": [ + "log" + ] + } + }, + { + "name": "notes_name_an_unacknowledged_object", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763961bbd5309af4b85f162123a18256d6d5f7da451ccbfa15f081a6f4c8df431860203000000000000000000040000000000000000000000aa34f35a627bf8b32de97afea316cd48d8ca91e385e83b340793863ba75b1fc732d8d8a6708c0036137a08298c447a6d4d76e99f187bb1db7b6b94f8aea06e01", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": "[{\"content_hash\":\"4f92db4b2f1126bbea570457a2bb4af5d4539ac78dadd09c1a0b7be8423907f6\",\"cmsd_static\":\"ot=v\",\"cmsd_dynamic\":null}]", + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_notes_invalid", + "at": [ + "notes" + ] + } + }, + { + "name": "attachment_not_given", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100a75618b02c3f8ec5a36bd8ee59ae91b96a582206d2f4b1d5d6843273608699e0000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a67576300000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000100881aeb7444210b24241784f2b56ceea5d9ccc2901e88bc8e9649aa93ccf103983d490d4ce71e28a522730d7dd2245611cc87c4de033c0a0702a920f92cb99de84ce70801d61b78fa4405da0ae0abfb8b9bd19e7f611de00016b74179b1abb809", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c04a8fc41c86697521341ab0ab735e38c12210d3fec801b0e71f07c69d4f848cef8b7c4388c6f16c41559bc7e04f117478132f31c83493fad48f9660439dc3310f", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "record_missing", + "at": [ + "attachment", + 0 + ] + } + }, + { + "name": "claim_not_named", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e47a628e02ebd980ba5e0fb790cc0161560100a0fee49e5e06008063c8e69e5e06000202150068747470733a2f2f63646e2d612e6578616d706c65150068747470733a2f2f63646e2d622e6578616d706c6505003b1d27d3cccc8c522bd589f7657da1b113200a6573d8ef6d85bd8f05457c18513da4be0a28e087720382c4ea8b0fc399bc59ca6956a7478192c9ecb627df0fa47f3d4d03c5427528551f68f449df2dd3cc76ffd127dc4109a6f6a5adfd3266e5e69cd4f55b49b97be5787cff28bae1989b53f22ef9a0d4a15a56cb1cf6748305fdaf2e91106cc9f6b73e327d1dacda59456eefa80240879213746b940f6e20e302006488bdc79885c458643cc4699f4bf7730c4d7ce9ba3bf335e23f4e49099b0176ca8c47fca83b70baad1c63f9f2f1bbc9c00deac9eebaa3e843beeef683cd327006000000e80300008813000010270170170000a00f000010270310270000a00f0000102701b0360000a00f0000b80b0350460000d0070000102701fc530000401f00001027018403000001000000dc05000000000000010000008403000000000000a7b48adfdc740a41c820db3bc8d7f265da8db0fdc4f997cb3c3ce431c62a40d3ebdbad7c5b33ede9054d283e7e4067186c394988d7d0a332aee09e1272a58f950203d012130000000000030400000000000000000000004fcc5e1422174e83bdcd5a8dde54ad92025cf577f3d10fbd43868ecd0917837eccf240106edbe4f9561aea4281f70dfb6ea5d5712e985d425e73dabca04d2000", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e400a0fee49e5e0600803697e59e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000006202000000000000a4dcb70d985fd047b2ad36239f5fdcb85e1c414f01eb1ed03fbf59bb2b27efd42d2a5a96d82b5511203846c5676b3989652bcdf003f8e8c138d804e71513de20cd3dc6696ec92bbb2caff4d96c6460940f84ec2069f168b6eba073ac98007527f420acef2656fb9956756c7e524fd63e5a10613b4f3ad499df570c3439a4bd0e", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000668ab7156c0cdafd3c825e6a0509e61906d7963db2730b433a01ec0836597c6700000000000000003601000000000000" + ] + }, + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e4803697e59e5e060000cd2fe69e5e0600fdaf2e91106cc9f6b73e327d1dacda59456eefa80240879213746b940f6e20e301000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000004001000000000000acc4721b823695931be9ac381900d99e3654fb960d5c2dec49199d562c02f248986296bbe1bc46edf918a1489014010c5b4ea6a086ad82da076a735e8924da90a75c5dd65ecd72f25b6bf7334eaf6fa1ad18f2a0ac8588fbb094502ccae81dff5434971cc7217670ff93281372bb7cc3944886694b09e9203b4045778075240e", + "items": [ + "45bc67b48205766a0dcb1529580caa95566229ab2b75ab76e306149f187b0e8f00000000000000004001000000000000" + ] + }, + { + "record": "57444131010102aa9823a88a3500d91e003a07aa44da6e732282285eb660753f886af8317618179c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e400a0fee49e5e0600803697e59e5e060000000000000000000000000000000000000000000000000000000000000000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000cc100000000000005b946688e15c0dc062fff6403ca57dfeff256c28bf851a1c6cb0a276892be1e671a13a49b84a40bfb078a0674eb84317572a927c8c72819a4c9271d985970095b11a7462786029d1f1eaf541c3fbb29de5185097b42897287d1eeff8e91c23069f2ea26fee1b198ec93c94b0c6a71ce9f6a269d1052454659e797664856dd709", + "items": [ + "1cc1bee91e1678d1d0cc88e35feb520a88814da36bf757e76929e41ab22b81e70000000000000000c80000000000000058c5a889717aa9f19a18257a79a4a5e1fe5ffcd3bd764110c48caa99bb25f32600000000000000008403000000000000b46f33eda8230cc1d862f234b52230f4f28a2811bfb0551b2b7180382051051a00000000000000008403000000000000ea144235442623eae241fb82d66d987db9a712af44764a88ebe72a39b6d875be00000000000000008403000000000000e60f1e58f1280583b829596ae6b30238dc1d4fe1b746968ed4760a7afe7a79810000000000000000bc02000000000000a337ff2ce9c5dd88f28e86626a6949ebb451c7b834d6efe245fde68f9ebaf8f60000000000000000bc02000000000000" + ] + }, + { + "record": "57444131010102aa9823a88a3500d91e003a07aa44da6e732282285eb660753f886af8317618179c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e4803697e59e5e060000cd2fe69e5e06007f3d4d03c5427528551f68f449df2dd3cc76ffd127dc4109a6f6a5adfd3266e50100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000800c0000000000009449879d18725f52b9da1fad72a4e0a43295e12928f6071d3ade475bd8bd4273283707c4176716cf235199da2437611ea72b40b9ded8a8abfb3430608c5a29b950263fe9aa70b5b3075c20a08ed884b3c153ef3ccdfcde934d224e621657f30ecff7750b6194ccf0750420d0f6d55c6dccdba4b5456a0f8dbf8fc8581a613a0d", + "items": [ + "4f92db4b2f1126bbea570457a2bb4af5d4539ac78dadd09c1a0b7be8423907f6000000000000000084030000000000005a3329c2a4bbba26ba88806b2e6241497a8e5c327227698ef4ea28ca698248ca0000000000000000bc02000000000000e44b87886191603d7af3d1d7ac0cf8b1e919347c9adb95e108159ec7aa64f8350000000000000000bc020000000000000433693c3a9563f9d91ed5834b3603a57fcffeabd7c5515d75d7436fd649ed3300000000000000008403000000000000" + ] + }, + { + "record": "57444131010102aa9823a88a3500d91e003a07aa44da6e732282285eb660753f886af8317618179c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e400cd2fe69e5e06008063c8e69e5e06003da4be0a28e087720382c4ea8b0fc399bc59ca6956a7478192c9ecb627df0fa401000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000008403000000000000c4219ed8ccd91ccb9e86fc1493094ef7580b706227fe6e38b6c68483773817cf73f823ed1659878991de0529d3c0b0f8df6fd11910793dc46583016e46339ea6a6b1814742c79a6c3f0aca0378778a80152218d5b1a25fc5b270e2b95c49b1b979a589ff49136c29ac05a91b9de4871c1c6a3336a04793fd3c20fc76b9bd1b05", + "items": [ + "17a19fb27a17ab820ca3c1a28f780eff76e0aee401a9525db42f2e63109a23df00000000000000008403000000000000" + ] + } + ], + "claims": [ + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3a744df636e2eb68b252d21d9efdb3e1a48746c61612d3bf5729910c0420dbbe97a628e02ebd980ba5e0fb790cc01615600e0b5a429b349be39beb139fc798d6372dcd7acf84b54db1f1bb38deba0136ed89c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e403ffffffff0200e60f1e58f1280583b829596ae6b30238dc1d4fe1b746968ed4760a7afe7a7981a337ff2ce9c5dd88f28e86626a6949ebb451c7b834d6efe245fde68f9ebaf8f6010000000000a00f000010270100001f5b94cc0e845508d2728c57a1f00fee43d393e1cec1bf732016a5558c7e989e651b294dcd2882935bb50d61e858b77132f65f2d7e14934dd596bb2f22723e05", + "marker": "#EXT-X-DATERANGE:ID=\"ad-1\",START-DATE=\"2026-10-25T00:00:06.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "e60f1e58f1280583b829596ae6b30238dc1d4fe1b746968ed4760a7afe7a7981", + "duration_ms": 2000 + }, + { + "content_hash": "a337ff2ce9c5dd88f28e86626a6949ebb451c7b834d6efe245fde68f9ebaf8f6", + "duration_ms": 2000 + } + ] + }, + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa363323e5827fe2af15ba4e106c527f002329c0ad3149835c43792c8f5672ebddd7a628e02ebd980ba5e0fb790cc0161560063e9f2c46288ffe99caf952f59dc5900efe0b3d7d1de5e9acf92380f7793fa079c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e403ffffffff02005a3329c2a4bbba26ba88806b2e6241497a8e5c327227698ef4ea28ca698248cae44b87886191603d7af3d1d7ac0cf8b1e919347c9adb95e108159ec7aa64f835010000000000a00f0000b80b01000012d6cafdf3ce998330ebf16e02a9fc17bce91151ffd23e9dbe07d0d41b2034a0e920062ec1ab7e210d459e54c8537d04bbaad31549cb4bd63f28c9e17d6c7401", + "marker": "#EXT-X-DATERANGE:ID=\"ad-2\",START-DATE=\"2026-10-25T00:00:14.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "5a3329c2a4bbba26ba88806b2e6241497a8e5c327227698ef4ea28ca698248ca", + "duration_ms": 2000 + }, + { + "content_hash": "e44b87886191603d7af3d1d7ac0cf8b1e919347c9adb95e108159ec7aa64f835", + "duration_ms": 2000 + } + ] + }, + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3e1a7319dfdd8051a8ab213ea73f5a190565dfe4c34d013b0948eebb0283ec54200b4d5a1d39d4e0a02d4a74428101c15008a6d2c683403130cef7e2f8246a0d187f041ab3d97b4dd8b4b98aa157def0fdf68a36c7d469ba51b471330c77fa1e7c4b8d6c08ebe3370eaca222cf0ab94866003ffffffff020005c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650cc346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2010000000000a00f0000102701000003c72648a9d566cea6e99629b205829536ad650aca752c84b1eee41dbdcf9a6a1d8f6ee1022e396f731acb82c4e7be767b2ca65ddcf694c6cb201c606339f009", + "marker": "#EXT-X-DATERANGE:ID=\"ad-x\",START-DATE=\"2026-10-25T00:05:11.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "05c5c635f9c168c1520b77a33468576817f538db5179147100d5c33ebef7650c", + "duration_ms": 2000 + }, + { + "content_hash": "c346d314a997bcebc988f3643640370466cb934967da9c4ab5122aa21f641fe2", + "duration_ms": 2000 + } + ] + } + ], + "log": "[[{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":0,\"ts_ms\":1792886400100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":1,\"ts_ms\":1792886400200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":2,\"ts_ms\":1792886400400,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"1cc1bee91e1678d1d0cc88e35feb520a88814da36bf757e76929e41ab22b81e7\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":3,\"ts_ms\":1792886400600,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"58c5a889717aa9f19a18257a79a4a5e1fe5ffcd3bd764110c48caa99bb25f326\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":4,\"ts_ms\":1792886401000,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":5,\"ts_ms\":1792886402400,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"b46f33eda8230cc1d862f234b52230f4f28a2811bfb0551b2b7180382051051a\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":6,\"ts_ms\":1792886403000,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"b46f33eda8230cc1d862f234b52230f4f28a2811bfb0551b2b7180382051051a\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":7,\"ts_ms\":1792886404400,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"668ab7156c0cdafd3c825e6a0509e61906d7963db2730b433a01ec0836597c67\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":8,\"ts_ms\":1792886404500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"ea144235442623eae241fb82d66d987db9a712af44764a88ebe72a39b6d875be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":9,\"ts_ms\":1792886406500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"e60f1e58f1280583b829596ae6b30238dc1d4fe1b746968ed4760a7afe7a7981\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":10,\"ts_ms\":1792886408500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"a337ff2ce9c5dd88f28e86626a6949ebb451c7b834d6efe245fde68f9ebaf8f6\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":11,\"ts_ms\":1792886410500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"4f92db4b2f1126bbea570457a2bb4af5d4539ac78dadd09c1a0b7be8423907f6\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":12,\"ts_ms\":1792886410600,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"45bc67b48205766a0dcb1529580caa95566229ab2b75ab76e306149f187b0e8f\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":13,\"ts_ms\":1792886412500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"5a3329c2a4bbba26ba88806b2e6241497a8e5c327227698ef4ea28ca698248ca\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":14,\"ts_ms\":1792886414500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"e44b87886191603d7af3d1d7ac0cf8b1e919347c9adb95e108159ec7aa64f835\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":15,\"ts_ms\":1792886416500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"0433693c3a9563f9d91ed5834b3603a57fcffeabd7c5515d75d7436fd649ed33\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":16,\"ts_ms\":1792886420000,\"event\":\"stall-start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":17,\"ts_ms\":1792886421500,\"event\":\"stall-end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":18,\"ts_ms\":1792886422000,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"17a19fb27a17ab820ca3c1a28f780eff76e0aee401a9525db42f2e63109a23df\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":19,\"ts_ms\":1792886429500,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": "[{\"content_hash\":\"58c5a889717aa9f19a18257a79a4a5e1fe5ffcd3bd764110c48caa99bb25f326\",\"cmsd_static\":\"ot=v,sf=h,st=l\",\"cmsd_dynamic\":null},{\"content_hash\":\"b46f33eda8230cc1d862f234b52230f4f28a2811bfb0551b2b7180382051051a\",\"cmsd_static\":null,\"cmsd_dynamic\":\"\\\"cdn-b\\\";etp=1200;rtt=40\"}]", + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_bundle_invalid", + "at": [ + "claim", + 2 + ] + } + } + ], + "reports": [ + { + "name": "news_east", + "channel": "news-east", + "sessions": [ + "live_two_origins", + "nothing_presented", + "claim_verdicts" + ], + "served": { + "report": { + "v": 1, + "channel": "7fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3", + "owner": "775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2", + "from": 1792886400000000, + "to": 1792890000000000, + "sessions": [ + "1ceafda75b6b806fcbc71a8b54d44fd772565b110cb68c3c67b178ecff6852ac", + "bcf64a2740b66513c4759f7771da54b3c15a009fc5f9f68aff68ce55caadf78a", + "eb718c8ac12701100ba880402bca56304026283ce1f9b80f53126d72e8bbcab1" + ], + "presented": { + "presented_ms": 55000, + "viewable_ms": 51000, + "audible_ms": 55000, + "ad_ms": 12000 + }, + "ended": { + "ended": 1, + "user": 1, + "hidden": 0, + "idle": 0, + "error": 1 + }, + "ads": { + "claims": 11, + "presented": 3, + "quartiles": [ + 0, + 0, + 1, + 0, + 2 + ], + "viewable": 2, + "refused": { + "audit_ad_not_acknowledged": 1, + "audit_claim_unbound": 1, + "audit_marker_mismatch": 1, + "bad_key": 1, + "bad_signature": 1, + "claim_asset_mismatch": 1, + "claim_duplicate": 1, + "claim_malformed": 1 + } + }, + "rebuffering": { + "sessions": 3, + "suppressed": true, + "presented": null, + "startup_ms": null, + "stalls": null, + "stall_ms": null + }, + "delivery": [ + { + "origin": "https://cdn-a.example", + "class": "ancillary", + "objects": 4, + "bytes": 1230 + }, + { + "origin": "https://cdn-b.example", + "class": "live", + "objects": 16, + "bytes": 11860 + } + ], + "repeats": { + "objects": 1, + "bytes": 900 + }, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "figures": 1, + "joules_micro": 1250000 + } + ], + "unmetered": { + "origin": 0, + "delivery": 0, + "sink_decode": 2, + "display": 3 + }, + "absent": { + "origin": 3, + "delivery": 3, + "sink_decode": 0, + "display": 0 + } + }, + "sig": "e11bc6deb97f62a1e230e17d2dcf6b2f3033d644e516d688762ffce5cfa01032e3b72986b6495684d06b68c6d3cfa6bf3183683ee38919981b068508a953b00f" + }, + "outcome": { + "outcome": "accept", + "digest": "b43a4b79c943c13e54079e7d472e3dbbc81594c3d071422454473df921a8b3eb" + } + }, + { + "name": "film_library", + "channel": "film-library", + "sessions": [ + "on_demand_hidden" + ], + "served": { + "report": { + "v": 1, + "channel": "c54a2ce987d8b6f5085ed3aaedc11cf5ac94033b937abbebbdafab9bcd8c7540", + "owner": "775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2", + "from": 1792886400000000, + "to": 1792890000000000, + "sessions": [ + "8a4397318745bce492ced97880f9eafe1ee4dd5ae1be9fc8dd8cf1a97309f408" + ], + "presented": { + "presented_ms": 15200, + "viewable_ms": 5200, + "audible_ms": 15200, + "ad_ms": 0 + }, + "ended": { + "ended": 0, + "user": 0, + "hidden": 1, + "idle": 0, + "error": 0 + }, + "ads": { + "claims": 0, + "presented": 0, + "quartiles": [ + 0, + 0, + 0, + 0, + 0 + ], + "viewable": 0, + "refused": {} + }, + "rebuffering": { + "sessions": 1, + "suppressed": true, + "presented": null, + "startup_ms": null, + "stalls": null, + "stall_ms": null + }, + "delivery": [ + { + "origin": "https://vod.example:8443", + "class": "fetch", + "objects": 5, + "bytes": 4980 + }, + { + "origin": "https://vod.example:8443", + "class": "ancillary", + "objects": 1, + "bytes": 280 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "energy": [ + { + "role": "sink_decode", + "class": "OnChipCounter", + "figures": 1, + "joules_micro": 3000000 + } + ], + "unmetered": { + "origin": 0, + "delivery": 0, + "sink_decode": 0, + "display": 1 + }, + "absent": { + "origin": 1, + "delivery": 1, + "sink_decode": 0, + "display": 0 + } + }, + "sig": "6527572270bc369a0c9d97b047c7f652cec1a61a15488e7f1ba2154bb8dffbc456af0791ee1164897c0202dfac37ec400a38c363f5061c3fc136fe5056e38502" + }, + "outcome": { + "outcome": "accept", + "digest": "c268188494a815fbb557c220492f909b52defa5dd09db40c46ccb15841a9bdee" + } + }, + { + "name": "fifty", + "channel": "fifty", + "sessions": [ + "fifty_00", + "fifty_01", + "fifty_02", + "fifty_03", + "fifty_04", + "fifty_05", + "fifty_06", + "fifty_07", + "fifty_08", + "fifty_09", + "fifty_10", + "fifty_11", + "fifty_12", + "fifty_13", + "fifty_14", + "fifty_15", + "fifty_16", + "fifty_17", + "fifty_18", + "fifty_19", + "fifty_20", + "fifty_21", + "fifty_22", + "fifty_23", + "fifty_24", + "fifty_25", + "fifty_26", + "fifty_27", + "fifty_28", + "fifty_29", + "fifty_30", + "fifty_31", + "fifty_32", + "fifty_33", + "fifty_34", + "fifty_35", + "fifty_36", + "fifty_37", + "fifty_38", + "fifty_39", + "fifty_40", + "fifty_41", + "fifty_42", + "fifty_43", + "fifty_44", + "fifty_45", + "fifty_46", + "fifty_47", + "fifty_48", + "fifty_49", + "fifty_50", + "fifty_51" + ], + "served": { + "report": { + "v": 1, + "channel": "949637aa5cada67913058abbec4740187d794388d011ee22ffc5f3ceddb0043d", + "owner": "775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2", + "from": 1792886400000000, + "to": 1792972800000000, + "sessions": [ + "01eafd8d4f8736017bcc0ba3b8ff11a75271ec25fb4987caaebb53832c8e3072", + "03e191a8d55c27f174ba051562012cb452d4b0342c43e0aacfe089977a3a0f42", + "121587410a852a7f86dafc1c2a64a5307a1e542347b4246f64a05f6d41a64a6e", + "12e2f989fe5b315c5d89afdc329f6018e2d857f7733a8ed4378ad0a93fe85898", + "15bc13deeee492f501b842aa425ba5a706f4a682cebafa20db31d319e258db9f", + "16796597260c386dd97aaadbddcf95d1a4842d8d70c9273fe1c89a48a9668cc6", + "255e55afc213300be08db85c7fc4a85181d9d9bdbb6efbf98c7009460d831746", + "2a96a06802700767ad2e3e5508c4edcc6434d93c7ead44ee19594a63ae34027c", + "2dc51aef6cb1c82464cbb63af19b1c4c8b138ecd8e48a2936e368d31d9fe4d04", + "32dea317e1aef290450f48bc59f563bb09531bb342769cd8dd88ca7aaff28dac", + "41b659fcab0bab6c2760eaea0a95d5bec1b880692ddd76b2ada32ea9af7f347a", + "4feef9d58144eae0813b5a42a27e0e7e906306f3045f4d8c3a0d542b922f6742", + "5655b6a86942fb721801292ad9f037a06350cd4530404e5b996128ec2891a173", + "5675cbb1cd58c5e7d8c5b7447ce39b79ab62df25019421e690c4124f97b7ceb9", + "5aa582bc34c398036eef0b10890ec6164888d259dbcd21cefe269cfd0ef55b4b", + "5bbc743341ad54cd8a1dce9c913dd57dbcb44ab1a415f78ac90021abdb59ec7d", + "60e51e9e3f5fa4bb5af218669ba67e1b7ac4520cd6121651e3ef59fafa6320b7", + "68bc8266f12df2bb969c5674d144309b70414bb15900730bfd548db8ca22a637", + "6af81788a9e3568c54d1edefb52f88d92777a050aa177eaabd1ba2852ef29794", + "6d532dbc326153280fbbf8dd85a9fc0206698751e7cdfa17f86c4eeea5728da1", + "71c2066117177185d7f76f773645315f1aba27c00b0b73d138f2abfb8b43efbc", + "71fe53acc27b672053f858b487bab6cf57c30b8275bb91332901b6d060944166", + "732cc79b7bce39ed48edc710e8ede3201c6d6971d7705f424cb327394bbd99fb", + "7666bfc8367a02f7eeea5fa6344bd2d43992d37b7f676be66ccaa9ca27dd9b18", + "7c2c58e9a980509c43c60309311c2b494969b4c156dd61275e1eb0c3d9245683", + "8214b2afb4784820b5403dc7b31b2489a23beee22011385d461ce2bfebbc9d6c", + "87c36d8361ea29d8d36c2654dc70cc8afa77cf4be11e2ded770584de93447ab5", + "93a9923e34a9d297b6a97494669bf96e2afac94eca172c5ac47fde80391742b5", + "9763eeecaec7b8304669d8abf01c450bf4533f0c355a36bff8b508de77b866f3", + "994f9ddde383f5811158fc01a776b9f95c787533c952668136b5a68c9a6b22e2", + "997b1d01c693c3e7bfd6bee537071abb228f496fb4f9a7bdbeaee48c31a85e0a", + "99ec0bb741a933eb0ae42cdafa84f9af2903ea1d1654d23aac6942bd5b2c515e", + "99f8dfb0c4a5b2fbcbe485a101f46afde069548ed4db0a75fbc49727a0cb6b21", + "9e386f097c84a5c44f9a521fe3bd757f43f04312f768e39c9ebf90499afb9f0b", + "a347bf04ced54a72cbd152de1cce2b245040bed3e59f7f7bd4c56c32da124d85", + "a5a68880f53a4ddcab408032aefaf8e6eae5cac931707cc3aa9e2239627a2ebe", + "afe47655cbe546f5ed9b256f1f32c96d53fe5e094b2817f1a768eee346ce979b", + "b1fbb46b6c194cf00cc5d1cd5231295a0f47dc8ec7358dd1a530b1b0730dad44", + "b2d3dd476c7a250d1e38fa0d1266e76a3af9c5c9da9bee531fca1daa3484554a", + "b3728bea132802736c6da94ff3fe219845cd75b5b08bf339d3bbd7be0f6019ea", + "bbd40219e0061f3973d2488b88dd3256af86bc36d5abe4d521ee59467704b5c2", + "c4e34615d6b93e97b3bb68c8033ebca21e8afc032350e4ba98b3374b41a6754f", + "cc8313a12a25a0cac0f2987fca1b4697c48299217aadb21c97afd9137a865941", + "d0ddc845fc0971771f48f195575cd43397b00c9223586afd074b37263b734086", + "e407669d053bd0275cd85532f63bd100d2857906eab509c8064da7b3669f5578", + "e49cd689657300c1493660888afd4d63da290905d1e69d08fa7c275738548212", + "e7ce21bc0cf1a7dc52a673ecd06690fcac2b429e6bb34173be9d14bb0e4af6a8", + "e8297ff4be6fd7518764383999a4cf2178e5b0bfaa6b51614c16897624ebf8c9", + "f0e5c0e1c4ad8664c960a5403b2f0f01ab7925e4c09821c85f0e2872a40a789e", + "f66ca6fb99c9f5ac5f7e397bd7efe62493d564725381639f896dce435d127037", + "f7ff04c5e623b3b91749c236be7296dda754fed7e9b0d16ac055866ba67145ad", + "fdfd049e2f5202be212fe387a5510deaf8578c902a04dd954c64539ca81650d5" + ], + "presented": { + "presented_ms": 1980740, + "viewable_ms": 1750750, + "audible_ms": 1444030, + "ad_ms": 0 + }, + "ended": { + "ended": 13, + "user": 39, + "hidden": 0, + "idle": 0, + "error": 0 + }, + "ads": { + "claims": 0, + "presented": 0, + "quartiles": [ + 0, + 0, + 0, + 0, + 0 + ], + "viewable": 0, + "refused": {} + }, + "rebuffering": { + "sessions": 52, + "suppressed": false, + "presented": 52, + "startup_ms": 28860, + "stalls": 11, + "stall_ms": 13200 + }, + "delivery": [ + { + "origin": "https://edge.example", + "class": "live", + "objects": 52, + "bytes": 41600 + }, + { + "origin": "https://edge.example", + "class": "ancillary", + "objects": 52, + "bytes": 13520 + } + ], + "repeats": { + "objects": 0, + "bytes": 0 + }, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "figures": 26, + "joules_micro": 6760000 + } + ], + "unmetered": { + "origin": 0, + "delivery": 0, + "sink_decode": 26, + "display": 34 + }, + "absent": { + "origin": 52, + "delivery": 52, + "sink_decode": 0, + "display": 18 + } + }, + "sig": "ea962c5b341c1e065d2de484c4463d86ad3e4f81261c7083cfbdf22cc93d09670c0db7745b5cd84cfc23439b13745c0523d7596e46c8d0fdd01b1a99aa88db0e" + }, + "outcome": { + "outcome": "accept", + "digest": "95839e240b8100603094815ab8de57ae4f58faf3d755525934e481d36940ef92" + } + }, + { + "name": "report_does_not_recompute", + "channel": "news-east", + "sessions": [ + "live_two_origins", + "nothing_presented", + "claim_verdicts" + ], + "served": { + "report": { + "v": 1, + "channel": "7fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3", + "owner": "775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2", + "from": 1792886400000000, + "to": 1792890000000000, + "sessions": [ + "1ceafda75b6b806fcbc71a8b54d44fd772565b110cb68c3c67b178ecff6852ac", + "bcf64a2740b66513c4759f7771da54b3c15a009fc5f9f68aff68ce55caadf78a", + "eb718c8ac12701100ba880402bca56304026283ce1f9b80f53126d72e8bbcab1" + ], + "presented": { + "presented_ms": 55001, + "viewable_ms": 51000, + "audible_ms": 55000, + "ad_ms": 12000 + }, + "ended": { + "ended": 1, + "user": 1, + "hidden": 0, + "idle": 0, + "error": 1 + }, + "ads": { + "claims": 11, + "presented": 3, + "quartiles": [ + 0, + 0, + 1, + 0, + 2 + ], + "viewable": 2, + "refused": { + "audit_ad_not_acknowledged": 1, + "audit_claim_unbound": 1, + "audit_marker_mismatch": 1, + "bad_key": 1, + "bad_signature": 1, + "claim_asset_mismatch": 1, + "claim_duplicate": 1, + "claim_malformed": 1 + } + }, + "rebuffering": { + "sessions": 3, + "suppressed": true, + "presented": null, + "startup_ms": null, + "stalls": null, + "stall_ms": null + }, + "delivery": [ + { + "origin": "https://cdn-a.example", + "class": "ancillary", + "objects": 4, + "bytes": 1230 + }, + { + "origin": "https://cdn-b.example", + "class": "live", + "objects": 16, + "bytes": 11860 + } + ], + "repeats": { + "objects": 1, + "bytes": 900 + }, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "figures": 1, + "joules_micro": 1250000 + } + ], + "unmetered": { + "origin": 0, + "delivery": 0, + "sink_decode": 2, + "display": 3 + }, + "absent": { + "origin": 3, + "delivery": 3, + "sink_decode": 0, + "display": 0 + } + }, + "sig": "47b713f2b050692d3a8cf7d86eb6584c4c984d7e84efccef0e4d35462b5756f33aff9922a2858f94711efa8a8bb68f17c081dde2e4f962417f23ca22bb42350c" + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_report_mismatch", + "at": [ + "report" + ] + } + }, + { + "name": "report_bad_signature", + "channel": "news-east", + "sessions": [ + "live_two_origins", + "nothing_presented", + "claim_verdicts" + ], + "served": { + "report": { + "v": 1, + "channel": "7fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3", + "owner": "775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2", + "from": 1792886400000000, + "to": 1792890000000000, + "sessions": [ + "1ceafda75b6b806fcbc71a8b54d44fd772565b110cb68c3c67b178ecff6852ac", + "bcf64a2740b66513c4759f7771da54b3c15a009fc5f9f68aff68ce55caadf78a", + "eb718c8ac12701100ba880402bca56304026283ce1f9b80f53126d72e8bbcab1" + ], + "presented": { + "presented_ms": 55000, + "viewable_ms": 51000, + "audible_ms": 55000, + "ad_ms": 12000 + }, + "ended": { + "ended": 1, + "user": 1, + "hidden": 0, + "idle": 0, + "error": 1 + }, + "ads": { + "claims": 11, + "presented": 3, + "quartiles": [ + 0, + 0, + 1, + 0, + 2 + ], + "viewable": 2, + "refused": { + "audit_ad_not_acknowledged": 1, + "audit_claim_unbound": 1, + "audit_marker_mismatch": 1, + "bad_key": 1, + "bad_signature": 1, + "claim_asset_mismatch": 1, + "claim_duplicate": 1, + "claim_malformed": 1 + } + }, + "rebuffering": { + "sessions": 3, + "suppressed": true, + "presented": null, + "startup_ms": null, + "stalls": null, + "stall_ms": null + }, + "delivery": [ + { + "origin": "https://cdn-a.example", + "class": "ancillary", + "objects": 4, + "bytes": 1230 + }, + { + "origin": "https://cdn-b.example", + "class": "live", + "objects": 16, + "bytes": 11860 + } + ], + "repeats": { + "objects": 1, + "bytes": 900 + }, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "figures": 1, + "joules_micro": 1250000 + } + ], + "unmetered": { + "origin": 0, + "delivery": 0, + "sink_decode": 2, + "display": 3 + }, + "absent": { + "origin": 3, + "delivery": 3, + "sink_decode": 0, + "display": 0 + } + }, + "sig": "001bc6deb97f62a1e230e17d2dcf6b2f3033d644e516d688762ffce5cfa01032e3b72986b6495684d06b68c6d3cfa6bf3183683ee38919981b068508a953b00f" + }, + "outcome": { + "outcome": "refuse", + "reason": "bad_signature", + "at": [ + "report" + ] + } + }, + { + "name": "report_names_a_session_not_given", + "channel": "news-east", + "sessions": [ + "live_two_origins", + "claim_verdicts" + ], + "served": { + "report": { + "v": 1, + "channel": "7fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3", + "owner": "775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2", + "from": 1792886400000000, + "to": 1792890000000000, + "sessions": [ + "1ceafda75b6b806fcbc71a8b54d44fd772565b110cb68c3c67b178ecff6852ac", + "bcf64a2740b66513c4759f7771da54b3c15a009fc5f9f68aff68ce55caadf78a", + "eb718c8ac12701100ba880402bca56304026283ce1f9b80f53126d72e8bbcab1" + ], + "presented": { + "presented_ms": 55000, + "viewable_ms": 51000, + "audible_ms": 55000, + "ad_ms": 12000 + }, + "ended": { + "ended": 1, + "user": 1, + "hidden": 0, + "idle": 0, + "error": 1 + }, + "ads": { + "claims": 11, + "presented": 3, + "quartiles": [ + 0, + 0, + 1, + 0, + 2 + ], + "viewable": 2, + "refused": { + "audit_ad_not_acknowledged": 1, + "audit_claim_unbound": 1, + "audit_marker_mismatch": 1, + "bad_key": 1, + "bad_signature": 1, + "claim_asset_mismatch": 1, + "claim_duplicate": 1, + "claim_malformed": 1 + } + }, + "rebuffering": { + "sessions": 3, + "suppressed": true, + "presented": null, + "startup_ms": null, + "stalls": null, + "stall_ms": null + }, + "delivery": [ + { + "origin": "https://cdn-a.example", + "class": "ancillary", + "objects": 4, + "bytes": 1230 + }, + { + "origin": "https://cdn-b.example", + "class": "live", + "objects": 16, + "bytes": 11860 + } + ], + "repeats": { + "objects": 1, + "bytes": 900 + }, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "figures": 1, + "joules_micro": 1250000 + } + ], + "unmetered": { + "origin": 0, + "delivery": 0, + "sink_decode": 2, + "display": 3 + }, + "absent": { + "origin": 3, + "delivery": 3, + "sink_decode": 0, + "display": 0 + } + }, + "sig": "e11bc6deb97f62a1e230e17d2dcf6b2f3033d644e516d688762ffce5cfa01032e3b72986b6495684d06b68c6d3cfa6bf3183683ee38919981b068508a953b00f" + }, + "outcome": { + "outcome": "refuse", + "reason": "record_missing", + "at": [ + "report", + "session", + 0 + ] + } + }, + { + "name": "report_one_session_twice", + "channel": "news-east", + "sessions": [ + "live_two_origins", + "nothing_presented", + "claim_verdicts", + "nothing_presented_again" + ], + "served": { + "report": { + "v": 1, + "channel": "7fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3", + "owner": "775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e2", + "from": 1792886400000000, + "to": 1792890000000000, + "sessions": [ + "1ceafda75b6b806fcbc71a8b54d44fd772565b110cb68c3c67b178ecff6852ac", + "bcf64a2740b66513c4759f7771da54b3c15a009fc5f9f68aff68ce55caadf78a", + "eb718c8ac12701100ba880402bca56304026283ce1f9b80f53126d72e8bbcab1" + ], + "presented": { + "presented_ms": 55000, + "viewable_ms": 51000, + "audible_ms": 55000, + "ad_ms": 12000 + }, + "ended": { + "ended": 1, + "user": 1, + "hidden": 0, + "idle": 0, + "error": 1 + }, + "ads": { + "claims": 11, + "presented": 3, + "quartiles": [ + 0, + 0, + 1, + 0, + 2 + ], + "viewable": 2, + "refused": { + "audit_ad_not_acknowledged": 1, + "audit_claim_unbound": 1, + "audit_marker_mismatch": 1, + "bad_key": 1, + "bad_signature": 1, + "claim_asset_mismatch": 1, + "claim_duplicate": 1, + "claim_malformed": 1 + } + }, + "rebuffering": { + "sessions": 3, + "suppressed": true, + "presented": null, + "startup_ms": null, + "stalls": null, + "stall_ms": null + }, + "delivery": [ + { + "origin": "https://cdn-a.example", + "class": "ancillary", + "objects": 4, + "bytes": 1230 + }, + { + "origin": "https://cdn-b.example", + "class": "live", + "objects": 16, + "bytes": 11860 + } + ], + "repeats": { + "objects": 1, + "bytes": 900 + }, + "energy": [ + { + "role": "sink_decode", + "class": "Estimator", + "figures": 1, + "joules_micro": 1250000 + } + ], + "unmetered": { + "origin": 0, + "delivery": 0, + "sink_decode": 2, + "display": 3 + }, + "absent": { + "origin": 3, + "delivery": 3, + "sink_decode": 0, + "display": 0 + } + }, + "sig": "e11bc6deb97f62a1e230e17d2dcf6b2f3033d644e516d688762ffce5cfa01032e3b72986b6495684d06b68c6d3cfa6bf3183683ee38919981b068508a953b00f" + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_session_duplicate", + "at": [ + "report" + ] + } + } + ] +} diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index 4416f8c46..743491b43 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -383,6 +383,14 @@ ai_usage = ["live_room"] # declared bytes, per-class joules from live-metering's WMH1 and integer amount a verifier # recomputes. SHA-256, BLAKE3, Ed25519 and the room's records only; no new dependency. settlement = ["live_metering", "jwp_hash"] +# Stream audit (extensions/stream-audit): `wai.delivery.audit` — what a content owner gathers from the +# players of a stream another service delivers over HTTP: settlement acknowledgements of every object +# by plain SHA-256 (settlement §14), one chain per origin, under a fresh session key; presentation +# claims (WPC1) for the ads the stream marks; the session record (WAS1) binding them with the spans +# presented, the start time and stalls its delivery-telemetry log recomputes, and the sink's energy +# per role and class; the bundle a collector verifies; and the channel report it signs and any holder +# of the bundles recomputes. SHA-256, BLAKE3, Ed25519 and the modules it reads; no new dependency. +stream_audit = ["settlement", "live_ads", "telemetry"] # Co-performance (extensions/co-performance): `wai.live.coperformance` — the room authority's # co-performance policy (WPP1); intents (WPI1) by digest or as notes, a text line or a camera # move, compared with what played under pinned rules, and the clock's fidelity statement @@ -977,6 +985,11 @@ name = "wai_settlement_vectors" path = "src/bin/wai_settlement_vectors.rs" required-features = ["settlement"] +[[bin]] +name = "wai_stream_audit_vectors" +path = "src/bin/wai_stream_audit_vectors.rs" +required-features = ["stream_audit"] + [[bin]] name = "wai_co_performance_vectors" path = "src/bin/wai_co_performance_vectors.rs" @@ -1777,6 +1790,11 @@ name = "settlement" path = "tests/settlement.rs" required-features = ["settlement"] +[[test]] +name = "stream_audit" +path = "tests/stream_audit.rs" +required-features = ["stream_audit"] + [[test]] name = "co_performance" path = "tests/co_performance.rs" diff --git a/wai/wai-rs/src/bin/wai_stream_audit_vectors.rs b/wai/wai-rs/src/bin/wai_stream_audit_vectors.rs new file mode 100644 index 000000000..cbd914323 --- /dev/null +++ b/wai/wai-rs/src/bin/wai_stream_audit_vectors.rs @@ -0,0 +1,55 @@ +//! Conformance corpus for stream audit (`live-conformance/audit/`, +//! extensions/stream-audit.md §15). +//! +//! wai_stream_audit_vectors gen write vectors.json, from fixed seeds +//! wai_stream_audit_vectors verify vectors.json regenerates byte for byte, +//! and every session and report gets +//! the outcome it states +//! +//! The generator and this verifier are one implementation. The corpus's second +//! implementation is `live-conformance/stream_audit_verify.py`, written from the +//! extension's text and sharing no code with this binary. + +use std::path::Path; +use wai::stream_audit::corpus; + +fn main() { + let a: Vec = std::env::args().collect(); + match (a.get(1).map(String::as_str), a.get(2)) { + (Some("gen"), Some(d)) => { + let p = Path::new(d).join("vectors.json"); + if let Err(e) = std::fs::create_dir_all(d).and_then(|_| std::fs::write(&p, corpus::generate())) { + eprintln!("write {}: {e}", p.display()); + std::process::exit(1); + } + println!("wrote {}", p.display()); + } + (Some("verify"), Some(d)) => std::process::exit(verify(Path::new(d))), + _ => { + eprintln!("usage: wai_stream_audit_vectors gen|verify "); + std::process::exit(2); + } + } +} + +fn verify(dir: &Path) -> i32 { + let p = dir.join("vectors.json"); + let doc = match std::fs::read_to_string(&p) { + Ok(s) => s, + Err(e) => { + eprintln!("read {}: {e}", p.display()); + return 1; + } + }; + let mut fail = 0; + let same = doc == corpus::generate(); + println!("{} vectors.json regenerates byte for byte from its seeds", if same { "PASS" } else { "FAIL" }); + fail += (!same) as i32; + let fails = corpus::verify(&doc); + for f in &fails { + println!("FAIL {f}"); + } + println!("{} every session and report gets the outcome the corpus states", if fails.is_empty() { "PASS" } else { "FAIL" }); + fail += (!fails.is_empty()) as i32; + (fail != 0) as i32 +} diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 38b0b7fc4..d51408ca5 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -589,6 +589,13 @@ pub mod ai_usage; /// cbindgen:ignore #[cfg(feature = "settlement")] pub mod settlement; +// Stream audit (extensions/stream-audit.md): `wai.delivery.audit` — a player's signed evidence of a +// stream another service delivers over HTTP (acknowledgements by plain SHA-256, presentation claims for +// marked ads, the session record), the bundle a collector verifies, and the channel report it signs +// and anyone holding the bundles recomputes. Rust-only, like the other record modules. +/// cbindgen:ignore +#[cfg(feature = "stream_audit")] +pub mod stream_audit; // Co-performance (extensions/co-performance.md): `wai.live.coperformance` — intents compared with // what played under pinned rules and the fidelity statement recomputed; turn schedules, // handovers, the floor, latency budgets and marks; audience inputs to selection windows, the diff --git a/wai/wai-rs/src/live_metering.rs b/wai/wai-rs/src/live_metering.rs index 2a1401b87..ad7b7fe58 100644 --- a/wai/wai-rs/src/live_metering.rs +++ b/wai/wai-rs/src/live_metering.rs @@ -435,6 +435,19 @@ fn energy_ok(joules: u64, class: &ClassRead) -> bool { } } +/// A figure's class read at `*at` of `b`, the cursor moved past it, and the figure held to +/// §3.6's `energy_invalid` rules: for a record of another extension that writes a figure and its +/// class as a per-hop record does (stream-audit §5.3). `Err` is `truncated` or `malformed`; +/// `Ok(None)` a class the rules refuse, `energy_invalid`; `Ok(Some(c))` the class, `None` for the +/// unmetered marker. +#[cfg_attr(not(feature = "stream_audit"), allow(dead_code))] +pub(crate) fn read_figure_class(b: &[u8], at: &mut usize, joules: u64) -> Result>, Reason> { + let mut r = Rd { b, p: *at }; + let class = read_class(&mut r)?; + *at = r.p; + Ok(if energy_ok(joules, &class) { class.ok() } else { None }) +} + fn write_class(c: Option<&EnergyProvenance>, o: &mut Vec) { crate::energy_class::write_optional(c, o); } diff --git a/wai/wai-rs/src/stream_audit.rs b/wai/wai-rs/src/stream_audit.rs new file mode 100644 index 000000000..3afbb1ed7 --- /dev/null +++ b/wai/wai-rs/src/stream_audit.rs @@ -0,0 +1,1377 @@ +//! Stream audit — `wai.delivery.audit` ([extensions/stream-audit.md]). +//! +//! Evidence a content owner gathers from the players of a stream another service packages and +//! delivers over HTTP. For each playback session a player makes a fresh key and signs with it: +//! settlement acknowledgements (`WDA1`) of every object it fetched, named by plain SHA-256 under +//! settlement §14, one chain per origin; a presentation claim (`WPC1`) for each ad the stream +//! marks; and the session record (`WAS1`), which binds them with the spans it presented, its +//! start time and stalls (recomputed from its delivery-telemetry log), its energy per role with +//! each figure's class, and how it ended. The owner's collector verifies a session's **bundle** +//! ([`verify_bundle`]), stores it, and states a **channel report** ([`report`]) that a party +//! holding the bundles recomputes byte for byte ([`check_report`]). +//! +//! What this module implements, by section of the extension: +//! +//! - §3: the channel id and the session ([`channel_id`], the session of ad-presentation §3); +//! - §4, settlement §14: an origin's upstream ([`origin_upstream`]) and the acknowledgements a +//! player signs over its objects' SHA-256 ([`acknowledgements`]); +//! - §5: the session record ([`Session`]): layout, rules, digest and signature; +//! - §6: the marker digest ([`marker_digest`]) and the claims, read with +//! [`crate::live_ads::presentation`]; +//! - §9, §10: the bundle and its verification ([`verify_bundle`]); +//! - §11: the report, its bytes, its signature and its recomputation. +//! +//! The corpus `live-conformance/audit/` is written by [`corpus::generate`] and checked by +//! [`corpus::verify`] here, and by `live-conformance/stream_audit_verify.py`, which shares no +//! code with this module. +//! +//! [extensions/stream-audit.md]: ../../extensions/stream-audit.md + +use std::collections::{BTreeMap, BTreeSet}; + +use ed25519_dalek::{Signer, SigningKey}; +use serde_json::{json, Map, Value}; +use sha2::{Digest, Sha256}; + +use crate::energy_class::EnergyProvenance; +use crate::live_ads::presentation::{self as pres, Claim, Creative, CreativeObject, Requirement}; +use crate::live_metering as metering; +use crate::settlement::{self, Ack, Entry, Item}; +use crate::telemetry::observation::Observation; +use crate::telemetry::qoe::session_metrics; + +pub mod corpus; + +/// The record this extension registers. +pub const CAPABILITY: &str = "wai.delivery.audit"; +/// The magic of a session record (§5.1). +pub const SESSION_MAGIC: [u8; 4] = *b"WAS1"; +/// The version this revision defines. +pub const VERSION: u8 = 1; +/// The domain a session record's signature is made under (§5.4). +pub const SESSION_DOMAIN: &[u8] = b"wai:audit-session\x01"; +/// The domain of a channel id (§3.1). +pub const CHANNEL_DOMAIN: &[u8] = b"wai:audit-channel\x01"; +/// The domain of a marker digest (§6.1). +pub const MARKER_DOMAIN: &[u8] = b"wai:audit-marker\x01"; +/// The domain a report's signature is made under (§11.3). +pub const REPORT_DOMAIN: &[u8] = b"wai:audit-report\x01"; +/// The domain of an origin's upstream (settlement §14.2). +pub const ORIGIN_DOMAIN: &[u8] = b"wai:settle-origin\x01"; +/// `startup_ms` when nothing was presented (§5.1). +pub const NO_STARTUP: u32 = u32::MAX; +/// The fewest sessions whose rebuffering a report states (§11.2; delivery-telemetry §8). +pub const QOE_MIN_SESSIONS: usize = 50; +/// The longest origin (§5.5). +pub const MAX_ORIGIN: usize = 2048; +/// The length of a span on the wire (§5.2). +pub const SPAN_LEN: usize = 11; +/// The shortest acknowledgement window a player cuts (§4.2), in microseconds. +pub const MIN_WINDOW_US: u64 = 1_000_000; +/// The default window (§4.2), in microseconds. +pub const DEFAULT_WINDOW_US: u64 = 10_000_000; +const ZERO: [u8; 32] = [0; 32]; + +/// `presentation` (§5.1). +pub mod presentation { + pub const LIVE: u8 = 1; + pub const ON_DEMAND: u8 = 2; + pub fn name(p: u8) -> &'static str { + if p == LIVE { "live" } else { "on_demand" } + } +} + +/// How a session ended (§8.2). +pub mod ended { + pub const ENDED: u8 = 1; + pub const USER: u8 = 2; + pub const HIDDEN: u8 = 3; + pub const IDLE: u8 = 4; + pub const ERROR: u8 = 5; + pub const NAMES: [&str; 5] = ["ended", "user", "hidden", "idle", "error"]; + pub fn name(e: u8) -> &'static str { + NAMES.get((e as usize).wrapping_sub(1)).copied().unwrap_or("unknown") + } +} + +/// An energy entry's role (§5.3). +pub mod role { + pub const ORIGIN: u8 = 1; + pub const DELIVERY: u8 = 2; + pub const SINK_DECODE: u8 = 3; + pub const DISPLAY: u8 = 4; + pub const NAMES: [&str; 4] = ["origin", "delivery", "sink_decode", "display"]; + pub fn name(r: u8) -> &'static str { + NAMES.get((r as usize).wrapping_sub(1)).copied().unwrap_or("unknown") + } +} + +/// A span's flags (§5.2). +pub mod span_flag { + pub const AUDIBLE: u8 = 1; + pub const AD: u8 = 2; +} + +// --------------------------------------------------------------------------------------- +// Reasons (§14) +// --------------------------------------------------------------------------------------- + +/// Why a bundle or a report is refused, or a claim's verdict (§14). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Reason { + BundleInvalid, + SessionInvalid, + WrongOwner, + UnknownChannel, + SessionUnbound, + AckUnbound, + OriginUnknown, + AckInvalid, + DocumentMismatch, + QoeMismatch, + NotesInvalid, + ClaimUnbound, + MarkerMismatch, + AdNotAcknowledged, + SessionDuplicate, + ReportMismatch, + /// A code of another extension (§14's last paragraph). + Borrowed(&'static str), +} + +impl Reason { + /// The codes this extension adds, in §14's order. + pub const ALL: [Reason; 16] = [ + Reason::BundleInvalid, + Reason::SessionInvalid, + Reason::WrongOwner, + Reason::UnknownChannel, + Reason::SessionUnbound, + Reason::AckUnbound, + Reason::OriginUnknown, + Reason::AckInvalid, + Reason::DocumentMismatch, + Reason::QoeMismatch, + Reason::NotesInvalid, + Reason::ClaimUnbound, + Reason::MarkerMismatch, + Reason::AdNotAcknowledged, + Reason::SessionDuplicate, + Reason::ReportMismatch, + ]; + + /// The codes of other extensions it raises (§14). + pub const REUSED: [&'static str; 17] = [ + "truncated", + "malformed", + "unknown_version", + "bad_key", + "bad_signature", + "record_missing", + "energy_invalid", + "qoe_invalid", + "ack_invalid", + "self_acknowledged", + "items_invalid", + "items_mismatch", + "ack_outside_period", + "ack_overlap", + "ack_chain_broken", + "claim_malformed", + "claim_duplicate", + ]; + + /// The claim verdicts of ad-presentation it gives (§10.2 step 9), beside [`Reason::REUSED`]. + pub const VERDICTS: [&'static str; 1] = ["claim_asset_mismatch"]; + + pub fn code(self) -> &'static str { + match self { + Reason::BundleInvalid => "audit_bundle_invalid", + Reason::SessionInvalid => "audit_session_invalid", + Reason::WrongOwner => "audit_wrong_owner", + Reason::UnknownChannel => "audit_unknown_channel", + Reason::SessionUnbound => "audit_session_unbound", + Reason::AckUnbound => "audit_ack_unbound", + Reason::OriginUnknown => "audit_origin_unknown", + Reason::AckInvalid => "audit_ack_invalid", + Reason::DocumentMismatch => "audit_document_mismatch", + Reason::QoeMismatch => "audit_qoe_mismatch", + Reason::NotesInvalid => "audit_notes_invalid", + Reason::ClaimUnbound => "audit_claim_unbound", + Reason::MarkerMismatch => "audit_marker_mismatch", + Reason::AdNotAcknowledged => "audit_ad_not_acknowledged", + Reason::SessionDuplicate => "audit_session_duplicate", + Reason::ReportMismatch => "audit_report_mismatch", + Reason::Borrowed(c) => c, + } + } +} + +impl std::fmt::Display for Reason { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.code()) + } +} + +impl std::error::Error for Reason {} + +const TRUNCATED: Reason = Reason::Borrowed("truncated"); +const MALFORMED: Reason = Reason::Borrowed("malformed"); +const UNKNOWN_VERSION: Reason = Reason::Borrowed("unknown_version"); +const BAD_KEY: Reason = Reason::Borrowed("bad_key"); +const BAD_SIGNATURE: Reason = Reason::Borrowed("bad_signature"); +const RECORD_MISSING: Reason = Reason::Borrowed("record_missing"); +const ENERGY_INVALID: Reason = Reason::Borrowed("energy_invalid"); +const QOE_INVALID: Reason = Reason::Borrowed("qoe_invalid"); +const ACK_OUTSIDE_PERIOD: Reason = Reason::Borrowed("ack_outside_period"); +const ACK_OVERLAP: Reason = Reason::Borrowed("ack_overlap"); +const ACK_CHAIN_BROKEN: Reason = Reason::Borrowed("ack_chain_broken"); +const ITEMS_INVALID: Reason = Reason::Borrowed("items_invalid"); +const ITEMS_MISMATCH: Reason = Reason::Borrowed("items_mismatch"); + +fn settled(r: settlement::Reason) -> Reason { + Reason::Borrowed(r.code()) +} + +/// A refusal: the reason and where (§10.2). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Refusal { + pub reason: Reason, + pub at: Vec, +} + +fn refuse(reason: Reason, at: Vec) -> Refusal { + Refusal { reason, at } +} + +// --------------------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------------------- + +fn sha256(parts: &[&[u8]]) -> [u8; 32] { + let mut h = Sha256::new(); + for p in parts { + h.update(p); + } + h.finalize().into() +} + +/// Lowercase hexadecimal. +pub fn hex(b: &[u8]) -> String { + b.iter().map(|x| format!("{x:02x}")).collect() +} + +/// Lowercase hexadecimal of whole bytes, read; `None` for anything else. +pub fn unhex(s: &str) -> Option> { + if !s.len().is_multiple_of(2) || !s.bytes().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) { + return None; + } + (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok()).collect() +} + +fn verify_sig(key: &[u8; 32], domain: &[u8], body: &[u8], sig: &[u8; 64]) -> Result<(), Reason> { + let mut m = domain.to_vec(); + m.extend_from_slice(body); + crate::sig::verify_strict(key, &m, sig).map_err(|e| match e { + crate::sig::SigError::BadKey => BAD_KEY, + crate::sig::SigError::BadSignature => BAD_SIGNATURE, + }) +} + +fn sign(key: &SigningKey, domain: &[u8], body: &[u8]) -> [u8; 64] { + let mut m = domain.to_vec(); + m.extend_from_slice(body); + key.sign(&m).to_bytes() +} + +/// The digest of given bytes (settlement §8.2): SHA-256 over all but their last 64. +pub fn given_digest(b: &[u8]) -> [u8; 32] { + sha256(&[&b[..b.len().saturating_sub(64)]]) +} + +/// The SHA-256 of an object's bytes (settlement §14.1). +pub fn object_hash(bytes: &[u8]) -> [u8; 32] { + sha256(&[bytes]) +} + +/// The channel id of a name (§3.1), or `None` for a name that is not 1 to 255 bytes of printable +/// ASCII. +pub fn channel_id(name: &str) -> Option<[u8; 32]> { + let ok = (1..=255).contains(&name.len()) && name.bytes().all(|c| (0x21..=0x7e).contains(&c)); + ok.then(|| sha256(&[CHANNEL_DOMAIN, name.as_bytes()])) +} + +/// The session of a session key in a channel (§3.2): ad-presentation §3's, the channel id in +/// the room id's place. +pub fn session_of(channel: &[u8; 32], player: &[u8; 32]) -> [u8; 16] { + pres::session_of(channel, player) +} + +/// The upstream an origin names (settlement §14.2). +pub fn origin_upstream(origin: &str) -> [u8; 32] { + sha256(&[ORIGIN_DOMAIN, origin.as_bytes()]) +} + +/// The marker digest of a marker's bytes (§6.1). +pub fn marker_digest(marker: &[u8]) -> [u8; 32] { + sha256(&[MARKER_DOMAIN, marker]) +} + +/// §5.5's rule on an origin's serialization. +pub fn origin_ok(o: &[u8]) -> bool { + if o.is_empty() || o.len() > MAX_ORIGIN || !o.iter().all(|c| (0x21..=0x7e).contains(c)) { + return false; + } + let rest = if let Some(r) = o.strip_prefix(b"https://") { + r + } else if let Some(r) = o.strip_prefix(b"http://") { + r + } else { + return false; + }; + !rest.is_empty() && !rest.iter().any(|c| matches!(c, b'/' | b'?' | b'#' | b'@')) +} + +/// A cursor; running out is `truncated`. +struct Rd<'a> { + b: &'a [u8], + p: usize, +} + +impl<'a> Rd<'a> { + fn take(&mut self, n: usize) -> Result<&'a [u8], Reason> { + let e = self.p.checked_add(n).filter(|&e| e <= self.b.len()).ok_or(TRUNCATED)?; + let s = &self.b[self.p..e]; + self.p = e; + Ok(s) + } + fn u8(&mut self) -> Result { + Ok(self.take(1)?[0]) + } + fn u16(&mut self) -> Result { + Ok(u16::from_le_bytes(self.arr()?)) + } + fn u32(&mut self) -> Result { + Ok(u32::from_le_bytes(self.arr()?)) + } + fn u64(&mut self) -> Result { + Ok(u64::from_le_bytes(self.arr()?)) + } + fn arr(&mut self) -> Result<[u8; N], Reason> { + Ok(self.take(N)?.try_into().expect("N bytes")) + } + fn digests(&mut self, n: usize) -> Result, Reason> { + (0..n).map(|_| self.arr()).collect() + } +} + +fn ascending(v: &[T]) -> bool { + v.windows(2).all(|w| w[0] < w[1]) +} + +// --------------------------------------------------------------------------------------- +// §4 Acknowledgements +// --------------------------------------------------------------------------------------- + +/// An object a player received (§4.1): when its body was complete, its origin, whether it is +/// media (an initialization or media segment) and its bytes. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Received { + pub at: u64, + pub origin: String, + pub media: bool, + pub bytes: Vec, +} + +/// What §4.2 gives a player for its objects: each acknowledgement with its item lists, and the +/// repeats. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Acknowledged { + pub acks: Vec<(Ack, Vec>)>, + pub repeat_objects: u32, + pub repeat_bytes: u64, +} + +/// §4.1, §4.2: the acknowledgements a player signs with `key` for the objects it received, +/// in windows of `window_us` from `start`, the last ending at `end`; one chain per origin, +/// acknowledgements in order of origin then window. +pub fn acknowledgements(key: &SigningKey, presentation: u8, start: u64, end: u64, window_us: u64, received: &[Received]) -> Acknowledged { + let media_class = if presentation == presentation::LIVE { settlement::class::LIVE } else { settlement::class::FETCH }; + let player = key.verifying_key().to_bytes(); + // (origin, window) -> objects in order received, by class. + let mut windows: BTreeMap<(String, u64), BTreeMap>> = BTreeMap::new(); + let mut seen: BTreeSet<(String, u64, [u8; 32])> = BTreeSet::new(); + let mut out = Acknowledged::default(); + for r in received { + let k = (r.at - start) / window_us; + let h = object_hash(&r.bytes); + if !seen.insert((r.origin.clone(), k, h)) { + out.repeat_objects += 1; + out.repeat_bytes += r.bytes.len() as u64; + continue; + } + let class = if r.media { media_class } else { settlement::class::ANCILLARY }; + let item = Item { content_hash: h, start: 0, end: r.bytes.len() as u64 }; + windows.entry((r.origin.clone(), k)).or_default().entry(class).or_default().push(item); + } + let mut prev: BTreeMap = BTreeMap::new(); + for ((origin, k), classes) in windows { + let ws = start + k * window_us; + let we = (ws + window_us).min(end); + let entries: Vec = classes.iter().map(|(c, items)| Entry::of(*c, ZERO, ZERO, items)).collect(); + let lists: Vec> = classes.values().map(|items| settlement::item_list(items)).collect(); + let a = Ack { + mode: settlement::mode::ACK, + role: settlement::role::SINK, + upstream: origin_upstream(&origin), + downstream: player, + start: ws, + end: we, + prev: prev.get(&origin).copied().unwrap_or(ZERO), + entries, + sig: [0; 64], + } + .sign(key); + prev.insert(origin, a.digest()); + out.acks.push((a, lists)); + } + out +} + +// --------------------------------------------------------------------------------------- +// §5 The session record +// --------------------------------------------------------------------------------------- + +/// A span of presentation (§5.2). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Span { + pub start_ms: u32, + pub length_ms: u32, + pub visible_bp: u16, + pub flags: u8, +} + +impl Span { + pub fn end(&self) -> u64 { + self.start_ms as u64 + self.length_ms as u64 + } +} + +/// An energy entry (§5.3): `None` for the unmetered marker. +#[derive(Clone, Debug, PartialEq)] +pub struct EnergyEntry { + pub role: u8, + pub joules: u64, + pub class: Option, +} + +/// A session record (§5.1). +#[derive(Clone, Debug, PartialEq)] +pub struct Session { + pub channel: [u8; 32], + pub owner: [u8; 32], + pub player: [u8; 32], + pub session: [u8; 16], + pub presentation: u8, + pub start: u64, + pub end: u64, + pub ended: u8, + pub origins: Vec, + pub acks: Vec<[u8; 32]>, + pub claims: Vec<[u8; 32]>, + pub spans: Vec, + pub startup_ms: u32, + pub stalls: u32, + pub stall_ms: u64, + pub repeat_objects: u32, + pub repeat_bytes: u64, + pub log: [u8; 32], + pub notes: [u8; 32], + pub energy: Vec, + pub attachments: Vec<[u8; 32]>, + pub sig: [u8; 64], +} + +impl Session { + /// The record without `sig`. + pub fn body(&self) -> Vec { + let mut o = SESSION_MAGIC.to_vec(); + o.push(VERSION); + o.extend_from_slice(&self.channel); + o.extend_from_slice(&self.owner); + o.extend_from_slice(&self.player); + o.extend_from_slice(&self.session); + o.push(self.presentation); + o.extend_from_slice(&self.start.to_le_bytes()); + o.extend_from_slice(&self.end.to_le_bytes()); + o.push(self.ended); + o.push(u8::try_from(self.origins.len()).expect("at most 255 origins")); + for s in &self.origins { + o.extend_from_slice(&u16::try_from(s.len()).expect("an origin of at most 65535 bytes").to_le_bytes()); + o.extend_from_slice(s.as_bytes()); + } + for list in [&self.acks, &self.claims] { + o.extend_from_slice(&u16::try_from(list.len()).expect("at most 65535 digests").to_le_bytes()); + for d in list { + o.extend_from_slice(d); + } + } + o.extend_from_slice(&u32::try_from(self.spans.len()).expect("spans").to_le_bytes()); + for s in &self.spans { + o.extend_from_slice(&s.start_ms.to_le_bytes()); + o.extend_from_slice(&s.length_ms.to_le_bytes()); + o.extend_from_slice(&s.visible_bp.to_le_bytes()); + o.push(s.flags); + } + o.extend_from_slice(&self.startup_ms.to_le_bytes()); + o.extend_from_slice(&self.stalls.to_le_bytes()); + o.extend_from_slice(&self.stall_ms.to_le_bytes()); + o.extend_from_slice(&self.repeat_objects.to_le_bytes()); + o.extend_from_slice(&self.repeat_bytes.to_le_bytes()); + o.extend_from_slice(&self.log); + o.extend_from_slice(&self.notes); + o.push(u8::try_from(self.energy.len()).expect("at most 255 energy entries")); + for e in &self.energy { + o.push(e.role); + o.extend_from_slice(&e.joules.to_le_bytes()); + crate::energy_class::write_optional(e.class.as_ref(), &mut o); + } + o.extend_from_slice(&u16::try_from(self.attachments.len()).expect("attachments").to_le_bytes()); + for d in &self.attachments { + o.extend_from_slice(d); + } + o + } + + pub fn to_bytes(&self) -> Vec { + let mut o = self.body(); + o.extend_from_slice(&self.sig); + o + } + + /// Its digest (§5.4). + pub fn digest(&self) -> [u8; 32] { + sha256(&[&self.body()]) + } + + /// Signed with the session key, which becomes `player`. + pub fn sign(mut self, key: &SigningKey) -> Session { + self.player = key.verifying_key().to_bytes(); + self.sig = sign(key, SESSION_DOMAIN, &self.body()); + self + } + + /// Re-signed with `key`, leaving `player` as it is (for a corpus control). + pub fn resign(mut self, key: &SigningKey) -> Session { + self.sig = sign(key, SESSION_DOMAIN, &self.body()); + self + } + + /// §5.5's rules other than the energy figures'. + fn rules_hold(&self) -> bool { + let spans_ok = { + let limit = (self.end.saturating_sub(self.start)) / 1000; + let mut prev = 0u64; + self.spans.iter().all(|s| { + let ok = s.length_ms >= 1 && s.visible_bp <= 10_000 && s.flags <= 3 && s.start_ms as u64 >= prev && s.end() <= limit; + prev = s.end(); + ok + }) + }; + matches!(self.presentation, presentation::LIVE | presentation::ON_DEMAND) + && self.start < self.end + && (1..=5).contains(&self.ended) + && !self.origins.is_empty() + && self.origins.iter().all(|o| origin_ok(o.as_bytes())) + && ascending(&self.origins) + && !self.acks.is_empty() + && ascending(&self.acks) + && ascending(&self.claims) + && ascending(&self.attachments) + && spans_ok + && ((self.startup_ms == NO_STARTUP) == self.spans.is_empty()) + && (self.stalls != 0 || self.stall_ms == 0) + && (if self.repeat_objects == 0 { self.repeat_bytes == 0 } else { self.repeat_bytes >= self.repeat_objects as u64 }) + && self.log != ZERO + && self.energy.windows(2).all(|w| w[0].role < w[1].role) + && self.energy.iter().all(|e| (1..=4).contains(&e.role)) + } + + /// §5.5: read a record, hold it to its rules, then its signature. + pub fn parse(b: &[u8]) -> Result { + if b.len() < 4 { + return Err(TRUNCATED); + } + if b[..4] != SESSION_MAGIC { + return Err(MALFORMED); + } + if b.len() < 5 { + return Err(TRUNCATED); + } + if b[4] != VERSION { + return Err(UNKNOWN_VERSION); + } + let mut r = Rd { b, p: 5 }; + let (channel, owner, player) = (r.arr()?, r.arr()?, r.arr()?); + let session = r.arr()?; + let presentation = r.u8()?; + let (start, end) = (r.u64()?, r.u64()?); + let ended = r.u8()?; + let n = r.u8()? as usize; + let mut raw_origins = Vec::with_capacity(n); + for _ in 0..n { + let len = r.u16()? as usize; + raw_origins.push(r.take(len)?.to_vec()); + } + let acks = { + let n = r.u16()? as usize; + r.digests(n)? + }; + let claims = { + let n = r.u16()? as usize; + r.digests(n)? + }; + let m = r.u32()? as usize; + let mut spans = Vec::with_capacity(m.min(b.len() / SPAN_LEN)); + for _ in 0..m { + spans.push(Span { start_ms: r.u32()?, length_ms: r.u32()?, visible_bp: u16::from_le_bytes(r.arr()?), flags: r.u8()? }); + } + let startup_ms = r.u32()?; + let stalls = r.u32()?; + let stall_ms = r.u64()?; + let repeat_objects = r.u32()?; + let repeat_bytes = r.u64()?; + let (log, notes) = (r.arr()?, r.arr()?); + let e = r.u8()? as usize; + let mut energy = Vec::with_capacity(e); + let mut energy_ok = true; + for _ in 0..e { + let role = r.u8()?; + let joules = r.u64()?; + let class = metering::read_figure_class(b, &mut r.p, joules).map_err(|x| Reason::Borrowed(x.code()))?; + match class { + Some(c) => energy.push(EnergyEntry { role, joules, class: c }), + None => { + energy_ok = false; + energy.push(EnergyEntry { role, joules, class: None }); + } + } + } + let attachments = { + let n = r.u16()? as usize; + r.digests(n)? + }; + let sig = r.arr()?; + if r.p != b.len() { + return Err(MALFORMED); + } + let origins_ascii = raw_origins.iter().all(|o| o.is_ascii()); + let origins: Vec = raw_origins.iter().map(|o| String::from_utf8_lossy(o).into_owned()).collect(); + let s = Session { + channel, + owner, + player, + session, + presentation, + start, + end, + ended, + origins, + acks, + claims, + spans, + startup_ms, + stalls, + stall_ms, + repeat_objects, + repeat_bytes, + log, + notes, + energy, + attachments, + sig, + }; + if !origins_ascii || !raw_origins.iter().all(|o| origin_ok(o)) || !s.rules_hold() { + return Err(Reason::SessionInvalid); + } + if !energy_ok { + return Err(ENERGY_INVALID); + } + verify_sig(&s.player, SESSION_DOMAIN, &b[..b.len() - 64], &s.sig)?; + Ok(s) + } + + /// The figures of §10.3 its spans give: presented, in view, audible, within ads. + pub fn presented(&self) -> [u64; 4] { + let mut f = [0u64; 4]; + for s in &self.spans { + let l = s.length_ms as u64; + f[0] += l; + if s.visible_bp >= pres::HALF_PIXELS { + f[1] += l; + } + if s.flags & span_flag::AUDIBLE != 0 { + f[2] += l; + } + if s.flags & span_flag::AD != 0 { + f[3] += l; + } + } + f + } +} + +// --------------------------------------------------------------------------------------- +// §9 The bundle +// --------------------------------------------------------------------------------------- + +/// A claim as a bundle carries it (§9). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BundleClaim { + pub claim: Vec, + pub marker: String, + pub creative: Creative, +} + +/// A bundle (§9). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Bundle { + pub session: Vec, + pub acks: Vec<(Vec, Vec>)>, + pub claims: Vec, + pub log: String, + pub notes: Option, + pub attachments: Vec<(String, Vec)>, +} + +fn exact<'a>(v: &'a Value, members: &[&str]) -> Option<&'a Map> { + let o = v.as_object()?; + (o.len() == members.len() && members.iter().all(|m| o.contains_key(*m))).then_some(o) +} + +fn hex_of(v: &Value) -> Option> { + unhex(v.as_str()?) +} + +impl Bundle { + /// §9: read a bundle, or `audit_bundle_invalid`. + pub fn read(b: &[u8]) -> Result { + let v: Value = serde_json::from_slice(b).map_err(|_| Reason::BundleInvalid)?; + Bundle::from_json(&v).ok_or(Reason::BundleInvalid) + } + + fn from_json(v: &Value) -> Option { + let o = exact(v, &["v", "session", "acks", "claims", "log", "notes", "attachments"])?; + if o["v"] != json!(1) { + return None; + } + let session = hex_of(&o["session"])?; + let acks = o["acks"] + .as_array()? + .iter() + .map(|a| { + let m = exact(a, &["record", "items"])?; + let items = m["items"].as_array()?.iter().map(hex_of).collect::>>()?; + Some((hex_of(&m["record"])?, items)) + }) + .collect::>>()?; + let claims = o["claims"] + .as_array()? + .iter() + .map(|c| { + let m = exact(c, &["claim", "marker", "creative"])?; + let objects = m["creative"] + .as_array() + .filter(|a| !a.is_empty())? + .iter() + .map(|x| { + let m = exact(x, &["content_hash", "duration_ms"])?; + let h: [u8; 32] = hex_of(&m["content_hash"])?.try_into().ok()?; + let d = m["duration_ms"].as_u64().filter(|d| (1..=u32::MAX as u64).contains(d))? as u32; + Some(CreativeObject { content_hash: h, duration_ms: d }) + }) + .collect::>>()?; + Some(BundleClaim { claim: hex_of(&m["claim"])?, marker: m["marker"].as_str()?.to_string(), creative: Creative { objects } }) + }) + .collect::>>()?; + let log = o["log"].as_str()?.to_string(); + let notes = match &o["notes"] { + Value::Null => None, + Value::String(s) => Some(s.clone()), + _ => return None, + }; + let attachments = o["attachments"] + .as_array()? + .iter() + .map(|a| { + let m = exact(a, &["name", "record"])?; + Some((m["name"].as_str()?.to_string(), hex_of(&m["record"])?)) + }) + .collect::>>()?; + Some(Bundle { session, acks, claims, log, notes, attachments }) + } + + /// The bundle as JSON (§9). + pub fn to_json(&self) -> Value { + json!({ + "v": 1, + "session": hex(&self.session), + "acks": self.acks.iter().map(|(r, items)| json!({"record": hex(r), "items": items.iter().map(|i| hex(i)).collect::>()})).collect::>(), + "claims": self.claims.iter().map(|c| json!({ + "claim": hex(&c.claim), + "marker": c.marker, + "creative": c.creative.objects.iter().map(|o| json!({"content_hash": hex(&o.content_hash), "duration_ms": o.duration_ms})).collect::>(), + })).collect::>(), + "log": self.log, + "notes": self.notes, + "attachments": self.attachments.iter().map(|(n, r)| json!({"name": n, "record": hex(r)})).collect::>(), + }) + } +} + +// --------------------------------------------------------------------------------------- +// §10 Verification +// --------------------------------------------------------------------------------------- + +/// A claim's verdict and, presented, its facts (§10.2 step 9). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ClaimVerdict { + /// `None` when presented. + pub refused: Option<&'static str>, + pub quartile: u8, + pub run_ms: u64, + pub viewable_ms: u64, +} + +impl ClaimVerdict { + pub fn presented(&self) -> bool { + self.refused.is_none() + } + pub fn viewable(&self) -> bool { + self.presented() && self.run_ms >= pres::RUN_MS + } + fn to_json(&self) -> Value { + match self.refused { + Some(r) => json!({"verdict": r}), + None => json!({"verdict": "presented", "quartile": self.quartile, "run_ms": self.run_ms, "viewable_ms": self.viewable_ms, "viewable": self.viewable()}), + } + } +} + +/// An accepted session (§10.3). +#[derive(Clone, Debug, PartialEq)] +pub struct Accepted { + pub digest: [u8; 32], + pub record: Session, + /// (origin, class) → (objects, bytes). + pub delivery: BTreeMap<(String, u8), (u64, u64)>, + pub claims: Vec, + pub notes: usize, +} + +impl Accepted { + /// §10.3's outcome. + pub fn to_json(&self) -> Value { + let s = &self.record; + let [presented_ms, viewable_ms, audible_ms, ad_ms] = s.presented(); + json!({ + "outcome": "accept", + "digest": hex(&self.digest), + "channel": hex(&s.channel), + "presentation": presentation::name(s.presentation), + "start": s.start, + "end": s.end, + "ended": ended::name(s.ended), + "presented_ms": presented_ms, + "viewable_ms": viewable_ms, + "audible_ms": audible_ms, + "ad_ms": ad_ms, + "delivery": self.delivery.iter().map(|((o, c), (n, b))| json!({"origin": o, "class": settlement::class::name(*c), "objects": n, "bytes": b})).collect::>(), + "repeats": {"objects": s.repeat_objects, "bytes": s.repeat_bytes}, + "startup_ms": if s.startup_ms == NO_STARTUP { Value::Null } else { json!(s.startup_ms) }, + "stalls": s.stalls, + "stall_ms": s.stall_ms, + "energy": s.energy.iter().map(|e| json!({"role": role::name(e.role), "class": metering::tag_label(metering::tag_of(e.class.as_ref())), "joules_micro": e.joules})).collect::>(), + "claims": self.claims.iter().map(ClaimVerdict::to_json).collect::>(), + "notes": self.notes, + "attachments": s.attachments.len(), + }) + } +} + +/// A bundle's outcome (§10.3). +#[derive(Clone, Debug, PartialEq)] +pub enum Outcome { + Accept(Box), + Refuse(Refusal), +} + +impl Outcome { + pub fn to_json(&self) -> Value { + match self { + Outcome::Accept(a) => a.to_json(), + Outcome::Refuse(r) => json!({"outcome": "refuse", "reason": r.reason.code(), "at": r.at}), + } + } +} + +/// The video rule (ad-presentation §6.1 `viewability` 1), the facts a presented claim states. +fn video_rule() -> Requirement { + Requirement { require: pres::require::CLAIM, rule: pres::rule::VIDEO, token_type: 0, issuer: Vec::new() } +} + +/// §10.2: verify a bundle for an owner serving `channels`. +pub fn verify_bundle(bytes: &[u8], owner: &[u8; 32], channels: &[String]) -> Outcome { + match verify_inner(bytes, owner, channels) { + Ok(a) => Outcome::Accept(Box::new(a)), + Err(r) => Outcome::Refuse(r), + } +} + +fn verify_inner(bytes: &[u8], owner: &[u8; 32], channels: &[String]) -> Result { + let at = |s: &str| vec![json!(s)]; + let at_i = |s: &str, i: usize| vec![json!(s), json!(i)]; + // 1. The bundle. + let bundle = Bundle::read(bytes).map_err(|r| refuse(r, at("bundle")))?; + // 2. The record. + let s = Session::parse(&bundle.session).map_err(|r| refuse(r, at("session")))?; + // 3. Its binding. + if &s.owner != owner { + return Err(refuse(Reason::WrongOwner, at("session"))); + } + if !channels.iter().any(|c| channel_id(c) == Some(s.channel)) { + return Err(refuse(Reason::UnknownChannel, at("session"))); + } + if session_of(&s.channel, &s.player) != s.session { + return Err(refuse(Reason::SessionUnbound, at("session"))); + } + // 4. Each acknowledgement. + let upstreams: BTreeMap<[u8; 32], &String> = s.origins.iter().map(|o| (origin_upstream(o), o)).collect(); + let by_digest: BTreeMap<[u8; 32], usize> = bundle.acks.iter().enumerate().rev().map(|(i, (r, _))| (given_digest(r), i)).collect(); + let media_class = if s.presentation == presentation::LIVE { settlement::class::LIVE } else { settlement::class::FETCH }; + let mut acks: Vec<(usize, Ack)> = Vec::with_capacity(s.acks.len()); + let mut delivery: BTreeMap<(String, u8), (u64, u64)> = BTreeMap::new(); + let mut acknowledged: BTreeSet<[u8; 32]> = BTreeSet::new(); + for (i, d) in s.acks.iter().enumerate() { + let k = *by_digest.get(d).ok_or_else(|| refuse(RECORD_MISSING, at_i("ack", i)))?; + let (raw, lists) = &bundle.acks[k]; + let a = Ack::parse(raw).map_err(|r| refuse(settled(r), at_i("ack", i)))?; + if a.mode != settlement::mode::ACK || a.downstream != s.player { + return Err(refuse(Reason::AckUnbound, at_i("ack", i))); + } + let origin = *upstreams.get(&a.upstream).ok_or_else(|| refuse(Reason::OriginUnknown, at_i("ack", i)))?; + if a.start < s.start || a.end > s.end { + return Err(refuse(ACK_OUTSIDE_PERIOD, at_i("ack", i))); + } + let entries_ok = a.role == settlement::role::SINK + && a.entries.iter().all(|e| e.grant == ZERO && e.proof == ZERO && (e.class == settlement::class::ANCILLARY || e.class == media_class)); + if !entries_ok { + return Err(refuse(Reason::AckInvalid, at_i("ack", i))); + } + if lists.len() > a.entries.len() { + return Err(refuse(Reason::BundleInvalid, at_i("ack", i))); + } + for (j, e) in a.entries.iter().enumerate() { + let place = vec![json!("ack"), json!(i), json!("entry"), json!(j)]; + let list = lists.get(j).ok_or_else(|| refuse(RECORD_MISSING, place.clone()))?; + let items = settlement::read_items(list).map_err(|_| refuse(ITEMS_INVALID, place.clone()))?; + if !e.recomputes(&items) { + return Err(refuse(ITEMS_MISMATCH, place)); + } + if items.iter().any(|it| it.start != 0) { + return Err(refuse(Reason::AckInvalid, place)); + } + let slot = delivery.entry((origin.clone(), e.class)).or_default(); + slot.0 += e.objects as u64; + slot.1 += e.bytes; + acknowledged.extend(items.iter().map(|it| it.content_hash)); + } + acks.push((i, a)); + } + let named: BTreeSet<[u8; 32]> = s.acks.iter().copied().collect(); + let mut once = BTreeSet::new(); + for (k, (raw, _)) in bundle.acks.iter().enumerate() { + let d = given_digest(raw); + if !named.contains(&d) || !once.insert(d) { + return Err(refuse(Reason::BundleInvalid, at_i("ack", k))); + } + } + // 5. The chains. + let mut chains: BTreeMap<[u8; 32], Vec<&(usize, Ack)>> = BTreeMap::new(); + for x in &acks { + chains.entry(x.1.upstream).or_default().push(x); + } + let mut breaks: Vec<(usize, Reason)> = Vec::new(); + for chain in chains.values_mut() { + chain.sort_by_key(|(i, a)| (a.start, *i)); + for (n, (i, a)) in chain.iter().enumerate() { + if n == 0 { + if a.prev != ZERO { + breaks.push((*i, ACK_CHAIN_BROKEN)); + } + continue; + } + let p = &chain[n - 1].1; + if p.end > a.start { + breaks.push((*i, ACK_OVERLAP)); + } else if a.prev != p.digest() { + breaks.push((*i, ACK_CHAIN_BROKEN)); + } + } + } + if let Some((i, r)) = breaks.into_iter().min_by_key(|(i, _)| *i) { + return Err(refuse(r, at_i("ack", i))); + } + // 6. The log. + if sha256(&[bundle.log.as_bytes()]) != s.log { + return Err(refuse(Reason::DocumentMismatch, at("log"))); + } + let m = read_one_log(bundle.log.as_bytes()).ok_or_else(|| refuse(QOE_INVALID, at("log")))?; + let startup = (s.startup_ms != NO_STARTUP).then_some(s.startup_ms as u64); + if m.0 != hex(&s.session) || m.1.vst_ms != startup || m.1.stalls != s.stalls as u64 || m.1.stall_ms != s.stall_ms { + return Err(refuse(Reason::QoeMismatch, at("log"))); + } + // 7. The notes. + let notes = match (&bundle.notes, s.notes == ZERO) { + (None, true) => 0, + (Some(n), false) if sha256(&[n.as_bytes()]) == s.notes => read_notes(n.as_bytes(), &acknowledged).ok_or_else(|| refuse(Reason::NotesInvalid, at("notes")))?, + _ => return Err(refuse(Reason::DocumentMismatch, at("notes"))), + }; + // 8. The attachments. + let given: Vec<[u8; 32]> = bundle.attachments.iter().map(|(_, r)| sha256(&[r])).collect(); + for (i, d) in s.attachments.iter().enumerate() { + if !given.contains(d) { + return Err(refuse(RECORD_MISSING, at_i("attachment", i))); + } + } + for (j, d) in given.iter().enumerate() { + if !s.attachments.contains(d) { + return Err(refuse(Reason::DocumentMismatch, at_i("attachment", j))); + } + } + // 9. The claims. + let by_claim: BTreeMap<[u8; 32], usize> = bundle.claims.iter().enumerate().rev().map(|(i, c)| (given_digest(&c.claim), i)).collect(); + let mut order = Vec::with_capacity(s.claims.len()); + for (i, d) in s.claims.iter().enumerate() { + order.push(*by_claim.get(d).ok_or_else(|| refuse(RECORD_MISSING, at_i("claim", i)))?); + } + let named: BTreeSet<[u8; 32]> = s.claims.iter().copied().collect(); + let mut once = BTreeSet::new(); + for (k, c) in bundle.claims.iter().enumerate() { + let d = given_digest(&c.claim); + if !named.contains(&d) || !once.insert(d) { + return Err(refuse(Reason::BundleInvalid, at_i("claim", k))); + } + } + let mut impressions: BTreeSet<([u8; 32], u8)> = BTreeSet::new(); + let claims = order.iter().map(|&k| judge(&bundle.claims[k], &s, &acknowledged, &mut impressions)).collect(); + Ok(Accepted { digest: s.digest(), record: s, delivery, claims, notes }) +} + +/// A logs document of exactly one log (live-metering §6.1): its `sid` and its metrics. +fn read_one_log(b: &[u8]) -> Option<(String, crate::telemetry::qoe::SessionMetrics)> { + let v: Value = serde_json::from_slice(b).ok()?; + let logs = v.as_array().filter(|a| a.len() == 1)?; + let obs = logs[0].as_array().filter(|a| !a.is_empty())?; + let obs: Vec = obs.iter().map(Observation::from_json).collect::>().ok()?; + let m = session_metrics(&obs).ok()?; + Some((obs[0].sid.clone(), m)) +} + +/// §7.3: a notes document, read, every content hash acknowledged; its count of responses. +fn read_notes(b: &[u8], acknowledged: &BTreeSet<[u8; 32]>) -> Option { + let v: Value = serde_json::from_slice(b).ok()?; + let a = v.as_array()?; + for n in a { + let m = exact(n, &["content_hash", "cmsd_static", "cmsd_dynamic"])?; + let h: [u8; 32] = m["content_hash"].as_str().filter(|s| s.len() == 64).and_then(unhex)?.try_into().ok()?; + if !acknowledged.contains(&h) { + return None; + } + for k in ["cmsd_static", "cmsd_dynamic"] { + if !(m[k].is_null() || m[k].is_string()) { + return None; + } + } + } + Some(a.len()) +} + +/// §10.2 step 9: one claim's verdict. +fn judge(c: &BundleClaim, s: &Session, acknowledged: &BTreeSet<[u8; 32]>, impressions: &mut BTreeSet<([u8; 32], u8)>) -> ClaimVerdict { + let no = |r: &'static str| ClaimVerdict { refused: Some(r), quartile: 0, run_ms: 0, viewable_ms: 0 }; + let claim = match Claim::parse(&c.claim) { + Ok(x) => x, + Err(r) => return no(r.code()), + }; + if let Err(r) = claim.check_signature() { + return no(r.code()); + } + if claim.room != s.channel || claim.session != s.session || claim.player != s.player { + return no(Reason::ClaimUnbound.code()); + } + if claim.cue != marker_digest(c.marker.as_bytes()) { + return no(Reason::MarkerMismatch.code()); + } + if !impressions.insert((claim.cue, claim.slot)) { + return no("claim_duplicate"); + } + let d = c.creative.duration_ms(); + if claim.creative_hash != c.creative.digest() || claim.spans.iter().any(|x| x.end() > d) || claim.objects != c.creative.touched(&claim.spans) { + return no("claim_asset_mismatch"); + } + if claim.objects.iter().any(|h| !acknowledged.contains(h)) { + return no(Reason::AdNotAcknowledged.code()); + } + let (run_ms, viewable_ms) = pres::runs(&claim, &video_rule()); + ClaimVerdict { refused: None, quartile: pres::quartile(&claim.spans, d), run_ms, viewable_ms } +} + +// --------------------------------------------------------------------------------------- +// §11 The channel report +// --------------------------------------------------------------------------------------- + +/// The report of a channel over a period (§11.1, §11.2), from accepted sessions; sessions of +/// other channels, other owners or outside the period are not read. +pub fn report(channel_name: &str, owner: &[u8; 32], from: u64, to: u64, accepted: &[&Accepted]) -> Result { + let channel = channel_id(channel_name).ok_or_else(|| refuse(Reason::UnknownChannel, vec![json!("report")]))?; + let mut sessions: Vec<&Accepted> = accepted.iter().copied().filter(|a| a.record.channel == channel && &a.record.owner == owner && a.record.start >= from && a.record.start < to).collect(); + sessions.sort_by_key(|a| a.digest); + sessions.dedup_by_key(|a| a.digest); + let mut seen = BTreeSet::new(); + if sessions.iter().any(|a| !seen.insert(a.record.session)) { + return Err(refuse(Reason::SessionDuplicate, vec![json!("report")])); + } + let mut presented = [0u64; 4]; + let mut ends = [0u64; 5]; + let (mut claims, mut shown, mut viewable) = (0u64, 0u64, 0u64); + let mut quartiles = [0u64; 5]; + let mut refused: BTreeMap<&str, u64> = BTreeMap::new(); + let (mut with_span, mut startup, mut stalls, mut stall_ms) = (0u64, 0u64, 0u64, 0u64); + let mut delivery: BTreeMap<(String, u8), (u64, u64)> = BTreeMap::new(); + let (mut rep_o, mut rep_b) = (0u64, 0u64); + let mut energy: BTreeMap<(u8, u8), (u64, u64)> = BTreeMap::new(); + let mut unmetered = [0u64; 4]; + let mut absent = [0u64; 4]; + for a in &sessions { + let s = &a.record; + for (t, x) in presented.iter_mut().zip(s.presented()) { + *t += x; + } + ends[s.ended as usize - 1] += 1; + for c in &a.claims { + claims += 1; + match c.refused { + Some(r) => *refused.entry(r).or_default() += 1, + None => { + shown += 1; + if (1..=5).contains(&c.quartile) { + quartiles[c.quartile as usize - 1] += 1; + } + viewable += c.viewable() as u64; + } + } + } + if !s.spans.is_empty() { + with_span += 1; + startup += s.startup_ms as u64; + } + stalls += s.stalls as u64; + stall_ms += s.stall_ms; + for (k, (o, b)) in &a.delivery { + let slot = delivery.entry(k.clone()).or_default(); + slot.0 += o; + slot.1 += b; + } + rep_o += s.repeat_objects as u64; + rep_b += s.repeat_bytes; + for r in 1..=4u8 { + match s.energy.iter().find(|e| e.role == r) { + None => absent[r as usize - 1] += 1, + Some(e) if e.class.is_none() => unmetered[r as usize - 1] += 1, + Some(e) => { + let slot = energy.entry((r, metering::tag_of(e.class.as_ref()))).or_default(); + slot.0 += 1; + slot.1 += e.joules; + } + } + } + } + let n = sessions.len(); + let suppressed = n < QOE_MIN_SESSIONS; + let q = |v: u64| if suppressed { Value::Null } else { json!(v) }; + let by_role = |v: &[u64; 4]| Value::Object(role::NAMES.iter().zip(v).map(|(k, n)| (k.to_string(), json!(n))).collect()); + Ok(json!({ + "v": 1, + "channel": hex(&channel), + "owner": hex(owner), + "from": from, + "to": to, + "sessions": sessions.iter().map(|a| hex(&a.digest)).collect::>(), + "presented": {"presented_ms": presented[0], "viewable_ms": presented[1], "audible_ms": presented[2], "ad_ms": presented[3]}, + "ended": Value::Object(ended::NAMES.iter().zip(ends).map(|(k, n)| (k.to_string(), json!(n))).collect()), + "ads": {"claims": claims, "presented": shown, "quartiles": quartiles, "viewable": viewable, "refused": refused}, + "rebuffering": {"sessions": n, "suppressed": suppressed, "presented": q(with_span), "startup_ms": q(startup), "stalls": q(stalls), "stall_ms": q(stall_ms)}, + "delivery": delivery.iter().map(|((o, c), (k, b))| json!({"origin": o, "class": settlement::class::name(*c), "objects": k, "bytes": b})).collect::>(), + "repeats": {"objects": rep_o, "bytes": rep_b}, + "energy": energy.iter().map(|((r, t), (f, j))| json!({"role": role::name(*r), "class": metering::tag_label(*t), "figures": f, "joules_micro": j})).collect::>(), + "unmetered": by_role(&unmetered), + "absent": by_role(&absent), + })) +} + +/// A report's bytes (§11.3): members in bytewise order of their names, no white space, +/// integers in decimal, strings with no escape but `\"` and `\\`. +pub fn report_bytes(v: &Value) -> Vec { + let mut o = String::new(); + canonical(v, &mut o); + o.into_bytes() +} + +fn canonical(v: &Value, o: &mut String) { + match v { + Value::Null => o.push_str("null"), + Value::Bool(b) => o.push_str(if *b { "true" } else { "false" }), + Value::Number(n) => o.push_str(&n.to_string()), + Value::String(s) => { + o.push('"'); + for c in s.chars() { + match c { + '"' => o.push_str("\\\""), + '\\' => o.push_str("\\\\"), + c if (c as u32) < 0x20 => o.push_str(&format!("\\u{:04x}", c as u32)), + c => o.push(c), + } + } + o.push('"'); + } + Value::Array(a) => { + o.push('['); + for (i, x) in a.iter().enumerate() { + if i > 0 { + o.push(','); + } + canonical(x, o); + } + o.push(']'); + } + Value::Object(m) => { + let mut keys: Vec<&String> = m.keys().collect(); + keys.sort(); + o.push('{'); + for (i, k) in keys.iter().enumerate() { + if i > 0 { + o.push(','); + } + canonical(&Value::String((*k).clone()), o); + o.push(':'); + canonical(&m[*k], o); + } + o.push('}'); + } + } +} + +/// A served report (§11.3): the report and the owner's signature over its bytes. +pub fn serve_report(report: &Value, key: &SigningKey) -> Value { + let sig = sign(key, REPORT_DOMAIN, &report_bytes(report)); + json!({"report": report, "sig": hex(&sig)}) +} + +/// §11.4: recompute a served report from the bundles given, for an owner serving `channels`, +/// the report's channel named `channel_name`. +pub fn check_report(served: &Value, bundles: &[Vec], channel_name: &str, owner: &[u8; 32], channels: &[String]) -> Result<(), Refusal> { + let place = || vec![json!("report")]; + let mut accepted = Vec::with_capacity(bundles.len()); + for (k, b) in bundles.iter().enumerate() { + match verify_bundle(b, owner, channels) { + Outcome::Accept(a) => accepted.push(*a), + Outcome::Refuse(r) => { + let mut at = vec![json!("bundle"), json!(k)]; + at.extend(r.at); + return Err(refuse(r.reason, at)); + } + } + } + let m = exact(served, &["report", "sig"]).ok_or_else(|| refuse(Reason::ReportMismatch, place()))?; + let r = &m["report"]; + let sig: [u8; 64] = hex_of(&m["sig"]).and_then(|s| s.try_into().ok()).ok_or_else(|| refuse(Reason::ReportMismatch, place()))?; + let (from, to) = (r["from"].as_u64(), r["to"].as_u64()); + let names = r["sessions"].as_array().and_then(|a| a.iter().map(|x| hex_of(x).and_then(|d| <[u8; 32]>::try_from(d).ok())).collect::>>()); + let (Some(from), Some(to), Some(names)) = (from, to, names) else { + return Err(refuse(Reason::ReportMismatch, place())); + }; + let refs: Vec<&Accepted> = accepted.iter().collect(); + let recomputed = report(channel_name, owner, from, to, &refs)?; + let digests: BTreeSet<[u8; 32]> = accepted.iter().map(|a| a.digest).collect(); + for (i, d) in names.iter().enumerate() { + if !digests.contains(d) { + return Err(refuse(RECORD_MISSING, vec![json!("report"), json!("session"), json!(i)])); + } + } + let bytes = report_bytes(&recomputed); + if report_bytes(r) != bytes { + return Err(refuse(Reason::ReportMismatch, place())); + } + verify_sig(owner, REPORT_DOMAIN, &bytes, &sig).map_err(|_| refuse(BAD_SIGNATURE, place())) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The corpus regenerates and this module gives every vector of it the outcome it states. + #[test] + fn corpus_round_trip() { + let doc = corpus::generate(); + let fails = corpus::verify(&doc); + assert!(fails.is_empty(), "{}", fails.join("\n")); + } + + fn got(at: u64, origin: &str, media: bool, bytes: &[u8]) -> Received { + Received { at, origin: origin.into(), media, bytes: bytes.to_vec() } + } + + /// §4.2: one acknowledgement per origin and window with objects, chained per origin; a + /// repeat within a window counted and not listed, the same bytes in the next window listed. + #[test] + fn windows_chains_and_repeats() { + let k = SigningKey::from_bytes(&[7; 32]); + let w = DEFAULT_WINDOW_US; + let r = [got(1, "https://a.example", false, b"pl"), got(2, "https://b.example", true, b"seg"), got(3, "https://b.example", true, b"seg"), got(w + 1, "https://b.example", true, b"seg")]; + let a = acknowledgements(&k, presentation::LIVE, 0, 2 * w, w, &r); + assert_eq!((a.repeat_objects, a.repeat_bytes), (1, 3)); + assert_eq!(a.acks.len(), 3); + let b: Vec<&Ack> = a.acks.iter().map(|(x, _)| x).filter(|x| x.upstream == origin_upstream("https://b.example")).collect(); + assert_eq!((b[0].prev, b[1].prev), (ZERO, b[0].digest())); + assert_eq!((b[1].start, b[1].end), (w, 2 * w)); + assert_eq!(b[0].entries[0].class, settlement::class::LIVE); + let vod = acknowledgements(&k, presentation::ON_DEMAND, 0, w, w, &r[1..2]); + assert_eq!(vod.acks[0].0.entries[0].class, settlement::class::FETCH); + } + + /// §5.5: an origin is a scheme and an authority, nothing after. + #[test] + fn origins() { + for o in ["https://a.example", "http://a.example:8080", "https://[::1]:443"] { + assert!(origin_ok(o.as_bytes()), "{o}"); + } + for o in ["https://a.example/", "ftp://a.example", "https://", "https://a.example?x", "https://u@a.example", "https://a example"] { + assert!(!origin_ok(o.as_bytes()), "{o}"); + } + } + + /// §11.3: members in bytewise order, no white space, only `\"` and `\\` escaped. + #[test] + fn report_bytes_are_canonical() { + let v = json!({"b": [1, {"z": null, "a": true}], "a": "x\"y\\z", "B": 0}); + assert_eq!(String::from_utf8(report_bytes(&v)).unwrap(), r#"{"B":0,"a":"x\"y\\z","b":[1,{"a":true,"z":null}]}"#); + } + + /// §3: a channel name is 1 to 255 bytes of printable ASCII. + #[test] + fn channel_names() { + assert!(channel_id("news-east").is_some()); + assert!(channel_id("").is_none() && channel_id("a b").is_none() && channel_id(&"x".repeat(256)).is_none()); + } +} diff --git a/wai/wai-rs/src/stream_audit/corpus.rs b/wai/wai-rs/src/stream_audit/corpus.rs new file mode 100644 index 000000000..af39d5ebf --- /dev/null +++ b/wai/wai-rs/src/stream_audit/corpus.rs @@ -0,0 +1,837 @@ +//! The conformance corpus of extensions/stream-audit.md §15: `live-conformance/audit/vectors.json`. +//! +//! [`generate`] writes it from fixed seeds: every key is SHA-256 of this corpus's label and an +//! index, every object's bytes a SHA-256 stream of another label and its name, every time a +//! constant after 2026-10-25T00:00:00Z, and Ed25519 signing is deterministic (RFC 8032). The +//! objects are opaque test bytes standing for playlists and segments, which nothing parses; the +//! byte counts, durations and joule figures are synthetic constants chosen to exercise the rules, +//! and claim nothing about any deployment. Each vector states the outcome it was written to have, +//! and the generator asserts that this module reaches it. +//! +//! [`verify`] checks a corpus file against this module. `live-conformance/stream_audit_verify.py` +//! checks the same file from the extension's text, sharing no code with this module. + +use super::*; +use crate::energy_class::{CounterFiltering, Uncertainty}; +use crate::live_ads::presentation::{geometry, Span as AdSpan, NOT_EXPOSED}; + +const S: u64 = 1_000_000; +/// 2026-10-25T00:00:00Z. +const T0: u64 = 1_792_886_400 * S; +const OWNER: u8 = 0; +const STRANGER: u8 = 1; +const SERVED: [&str; 3] = ["film-library", "fifty", "news-east"]; +const CDN_A: &str = "https://cdn-a.example"; +const CDN_B: &str = "https://cdn-b.example"; +const VOD: &str = "https://vod.example:8443"; +const EDGE: &str = "https://edge.example"; +/// The small-order point of encoding 1 (the identity): a key strict verification refuses. +const SMALL_ORDER: [u8; 32] = { + let mut k = [0u8; 32]; + k[0] = 1; + k +}; + +fn key(i: u8) -> SigningKey { + SigningKey::from_bytes(&sha256(&[b"wai:stream-audit/corpus/key\x00", &[i]])) +} + +fn pk(i: u8) -> [u8; 32] { + key(i).verifying_key().to_bytes() +} + +/// The objects every session draws from: a name, its length. +const OBJECTS: [(&str, usize); 31] = [ + ("p0", 300), + ("p1", 310), + ("p2", 320), + ("init", 200), + ("s0", 900), + ("s1", 900), + ("s2", 900), + ("s3", 900), + ("s4", 900), + ("s5", 900), + ("a0", 700), + ("a1", 700), + ("a2", 700), + ("a3", 700), + ("m0", 280), + ("i0", 180), + ("v0", 1200), + ("v1", 1200), + ("v2", 1200), + ("v3", 1200), + ("d0", 900), + ("e0", 640), + ("e1", 640), + ("e2", 640), + ("e3", 640), + ("x9", 640), + ("fp", 260), + ("f0", 800), + ("f1", 800), + ("f2", 800), + ("f3", 800), +]; + +/// `n` opaque bytes named `name`: a SHA-256 stream. +fn object(name: &str) -> Vec { + let n = OBJECTS.iter().find(|(m, _)| *m == name).unwrap_or_else(|| panic!("no object {name}")).1; + let mut o = Vec::with_capacity(n + 32); + let mut i = 0u32; + while o.len() < n { + o.extend_from_slice(&sha256(&[b"wai:stream-audit/corpus/object\x00", name.as_bytes(), &i.to_le_bytes()])); + i += 1; + } + o.truncate(n); + o +} + +fn h(name: &str) -> [u8; 32] { + object_hash(&object(name)) +} + +/// A log observation of delivery-telemetry §2, every member in its order. +fn obs(sid: &str, sn: u64, ts_us: u64, event: &str, st: &str, object: Value, present: Value) -> Value { + json!({ + "v": 1, "sid": sid, "sn": sn, "ts_ms": ts_us / 1000, "event": event, "cid": null, "st": st, "sf": "h", + "object": object, "present": present, "switch": null, "layer_change": null, "capable": null, + "cohort": null, "metrics": null, "claim": null, + }) +} + +fn response(hash: &[u8; 32]) -> Value { + json!({ + "ot": null, "url": null, "content_hash": hex(hash), "capability": null, "rendition": null, "layer": null, + "layers_total": null, "layer_cap": null, "counted": null, "transferred": null, "d_ms": null, "dl_ms": null, + "ttfb_ms": null, "ttlb_ms": null, "rc": 200, "path": null, "receipt": null, + }) +} + +/// A claim a session's plan asks for. +#[derive(Clone)] +struct ClaimPlan { + marker: String, + /// The marker the claim's `cue` is the digest of, where it differs from `marker`. + cue_marker: Option, + creative: Vec<(&'static str, u32)>, + spans: Vec, + room: Option<[u8; 32]>, + slot: u8, + geometry: u8, + /// The objects the claim lists, where they differ from those its spans touch. + objects: Option>, + flip_sig: bool, + bad_key: bool, +} + +impl ClaimPlan { + fn new(marker: &str, creative: Vec<(&'static str, u32)>, spans: Vec) -> ClaimPlan { + ClaimPlan { marker: marker.into(), cue_marker: None, creative, spans, room: None, slot: 0, geometry: geometry::PLAYER, objects: None, flip_sig: false, bad_key: false } + } +} + +fn marker(id: &str, start: &str) -> String { + format!("#EXT-X-DATERANGE:ID=\"{id}\",START-DATE=\"2026-10-25T00:{start}Z\",DURATION=4.000,SCTE35-OUT=0xFC302000") +} + +fn ad_span(start_ms: u32, length_ms: u32, visible_bp: u16) -> AdSpan { + AdSpan { start_ms, length_ms, visible_bp, flags: pres::span_flag::AUDIBLE } +} + +/// A CMSD note a plan asks for: the object, its `CMSD-Static` and its `CMSD-Dynamic`. +type Note = (&'static str, Option<&'static str>, Option<&'static str>); + +/// A session's plan: what its player received, observed and presented. +#[derive(Clone)] +struct Plan { + channel: &'static str, + key: u8, + presentation: u8, + start: u64, + end: u64, + ended: u8, + window: u64, + /// (offset µs, origin, object, media). + received: Vec<(u64, &'static str, &'static str, bool)>, + /// (offset µs, event) for `start`, `present`, `stall-start`, `stall-end`, `end`. + events: Vec<(u64, &'static str)>, + spans: Vec, + claims: Vec, + notes: Option>, + energy: Vec, + attachments: Vec<(&'static str, &'static str)>, +} + +/// A session built from its plan, before its bundle. +#[derive(Clone)] +struct Built { + record: Session, + acks: Vec<(Ack, Vec>)>, + claims: Vec, + log: String, + observations: Vec, + notes: Option, + attachments: Vec<(String, Vec)>, + key: SigningKey, +} + +impl Built { + fn bundle(&self) -> Bundle { + Bundle { + session: self.record.to_bytes(), + acks: self.acks.iter().map(|(a, l)| (a.to_bytes(), l.clone())).collect(), + claims: self.claims.clone(), + log: self.log.clone(), + notes: self.notes.clone(), + attachments: self.attachments.clone(), + } + } + + /// The record re-derived from the parts, and signed. + fn reseal(mut self) -> Built { + let mut acks: Vec<[u8; 32]> = self.acks.iter().map(|(a, _)| a.digest()).collect(); + acks.sort(); + self.record.acks = acks; + let mut claims: Vec<[u8; 32]> = self.claims.iter().map(|c| given_digest(&c.claim)).collect(); + claims.sort(); + self.record.claims = claims; + self.record = self.record.resign(&self.key); + self + } +} + +fn build(p: &Plan) -> Built { + let k = key(p.key); + let player = k.verifying_key().to_bytes(); + let channel = channel_id(p.channel).expect("a channel name"); + let session = session_of(&channel, &player); + let sid = hex(&session); + let st = if p.presentation == presentation::LIVE { "l" } else { "v" }; + let received: Vec = p.received.iter().map(|(t, o, n, m)| Received { at: p.start + t, origin: o.to_string(), media: *m, bytes: object(n) }).collect(); + let ackd = acknowledgements(&k, p.presentation, p.start, p.end, p.window, &received); + // The log: the control events and a response for every object, in time order. + let mut timeline: Vec<(u64, Value, Value, &str)> = Vec::new(); + for (t, e) in &p.events { + let present = if *e == "present" { json!({"unit": 0, "layers": 1, "first": true, "late_ms": null, "decode_us": null}) } else { Value::Null }; + timeline.push((*t, Value::Null, present, e)); + } + for (t, _, n, _) in &p.received { + timeline.push((*t, response(&h(n)), Value::Null, "response")); + } + timeline.sort_by_key(|x| x.0); + let log_obs: Vec = timeline.into_iter().enumerate().map(|(sn, (t, o, pr, e))| obs(&sid, sn as u64, p.start + t, e, st, o, pr)).collect(); + let observations: Vec = log_obs.iter().map(|v| Observation::from_json(v).expect("an observation")).collect(); + let log = serde_json::to_string(&json!([log_obs])).expect("JSON"); + let metrics = session_metrics(&observations).expect("a log"); + let notes = p.notes.as_ref().map(|ns| { + let a: Vec = ns.iter().map(|(n, s, d)| json!({"content_hash": hex(&h(n)), "cmsd_static": s, "cmsd_dynamic": d})).collect(); + serde_json::to_string(&a).expect("JSON") + }); + let claims: Vec = p + .claims + .iter() + .map(|c| { + let creative = Creative { objects: c.creative.iter().map(|(n, d)| CreativeObject { content_hash: h(n), duration_ms: *d }).collect() }; + let objects = match &c.objects { + Some(o) => o.iter().map(|n| h(n)).collect(), + None => creative.touched(&c.spans), + }; + let room = c.room.unwrap_or(channel); + let mut claim = Claim { + room, + cue: marker_digest(c.cue_marker.as_ref().unwrap_or(&c.marker).as_bytes()), + session: session_of(&room, &player), + slot: c.slot, + creative_hash: creative.digest(), + player, + geometry: c.geometry, + frames: NOT_EXPOSED, + objects, + spans: c.spans.clone(), + attestation: Vec::new(), + sig: [0; 64], + } + .sign(&k); + if c.room.is_some() { + // A claim for another channel names that channel's session of the same key. + claim.session = session_of(&room, &player); + } + if c.bad_key { + claim.player = SMALL_ORDER; + } + if c.flip_sig { + claim.sig[0] ^= 1; + } + BundleClaim { claim: claim.to_bytes(), marker: c.marker.clone(), creative } + }) + .collect(); + let attachments: Vec<(String, Vec)> = p.attachments.iter().map(|(n, o)| (n.to_string(), object(o))).collect(); + let mut digests: Vec<[u8; 32]> = attachments.iter().map(|(_, r)| sha256(&[r])).collect(); + digests.sort(); + let mut origins: Vec = p.received.iter().map(|(_, o, _, _)| o.to_string()).collect(); + origins.sort(); + origins.dedup(); + let record = Session { + channel, + owner: pk(OWNER), + player, + session, + presentation: p.presentation, + start: p.start, + end: p.end, + ended: p.ended, + origins, + acks: Vec::new(), + claims: Vec::new(), + spans: p.spans.clone(), + startup_ms: metrics.vst_ms.map_or(NO_STARTUP, |v| v as u32), + stalls: metrics.stalls as u32, + stall_ms: metrics.stall_ms, + repeat_objects: ackd.repeat_objects, + repeat_bytes: ackd.repeat_bytes, + log: sha256(&[log.as_bytes()]), + notes: notes.as_ref().map_or(ZERO, |n| sha256(&[n.as_bytes()])), + energy: p.energy.clone(), + attachments: digests, + sig: [0; 64], + }; + Built { record, acks: ackd.acks, claims, log, observations, notes, attachments, key: k }.reseal() +} + +fn span(start_ms: u32, length_ms: u32, visible_bp: u16, flags: u8) -> Span { + Span { start_ms, length_ms, visible_bp, flags } +} + +fn estimator(role: u8, joules: u64) -> EnergyEntry { + EnergyEntry { role, joules, class: Some(EnergyProvenance::Estimator) } +} + +fn unmetered(role: u8) -> EnergyEntry { + EnergyEntry { role, joules: 0, class: None } +} + +const AUD: u8 = span_flag::AUDIBLE; +const AD: u8 = span_flag::AUDIBLE | span_flag::AD; + +/// A live session from two origins: two marked ads, one in view and one half out of it, a +/// repeat, CMSD notes, a stall, and a sink-decode `Estimator` figure beside an unmetered display. +fn live_two_origins() -> Plan { + Plan { + channel: "news-east", + key: 10, + presentation: presentation::LIVE, + start: T0, + end: T0 + 30 * S, + ended: ended::USER, + window: DEFAULT_WINDOW_US, + received: vec![ + (200_000, CDN_A, "p0", false), + (400_000, CDN_B, "init", true), + (600_000, CDN_B, "s0", true), + (2_400_000, CDN_B, "s1", true), + (3_000_000, CDN_B, "s1", true), + (4_400_000, CDN_A, "p1", false), + (4_500_000, CDN_B, "s2", true), + (6_500_000, CDN_B, "a0", true), + (8_500_000, CDN_B, "a1", true), + (10_500_000, CDN_B, "s3", true), + (10_600_000, CDN_A, "p2", false), + (12_500_000, CDN_B, "a2", true), + (14_500_000, CDN_B, "a3", true), + (16_500_000, CDN_B, "s4", true), + (22_000_000, CDN_B, "s5", true), + ], + events: vec![(100_000, "start"), (1_000_000, "present"), (20_000_000, "stall-start"), (21_500_000, "stall-end"), (29_500_000, "end")], + spans: vec![span(1000, 5000, 10_000, AUD), span(6000, 4000, 10_000, AD), span(10_000, 4000, 10_000, AUD), span(14_000, 4000, 3000, AD), span(18_000, 2000, 10_000, AUD), span(21_500, 8000, 10_000, AUD)], + claims: vec![ + ClaimPlan::new(&marker("ad-1", "00:06.000"), vec![("a0", 2000), ("a1", 2000)], vec![ad_span(0, 4000, 10_000)]), + ClaimPlan::new(&marker("ad-2", "00:14.000"), vec![("a2", 2000), ("a3", 2000)], vec![ad_span(0, 4000, 3000)]), + ], + notes: Some(vec![("s0", Some("ot=v,sf=h,st=l"), None), ("s1", None, Some("\"cdn-b\";etp=1200;rtt=40"))]), + energy: vec![estimator(role::SINK_DECODE, 1_250_000), unmetered(role::DISPLAY)], + attachments: vec![], + } +} + +/// An on-demand session ended `hidden`, an `OnChipCounter` figure and an attachment. +fn on_demand_hidden() -> Plan { + let counter = EnergyProvenance::on_chip_counter(Uncertainty::new(50_000, 18_000_000).expect("a declaration"), CounterFiltering::On).expect("a class"); + Plan { + channel: "film-library", + key: 11, + presentation: presentation::ON_DEMAND, + start: T0 + 100 * S, + end: T0 + 120 * S, + ended: ended::HIDDEN, + window: DEFAULT_WINDOW_US, + received: vec![(100_000, VOD, "m0", false), (300_000, VOD, "i0", true), (500_000, VOD, "v0", true), (2_500_000, VOD, "v1", true), (4_500_000, VOD, "v2", true), (6_500_000, VOD, "v3", true)], + events: vec![(50_000, "start"), (800_000, "present"), (18_000_000, "end")], + spans: vec![span(800, 5200, 10_000, AUD), span(6000, 10_000, 0, AUD)], + claims: vec![], + notes: None, + energy: vec![EnergyEntry { role: role::SINK_DECODE, joules: 3_000_000, class: Some(counter) }, unmetered(role::DISPLAY)], + attachments: vec![("wai.live.termination", "x9")], + } +} + +/// A session that presented nothing and ended `error`. +fn nothing_presented() -> Plan { + Plan { + channel: "news-east", + key: 12, + presentation: presentation::LIVE, + start: T0 + 200 * S, + end: T0 + 205 * S, + ended: ended::ERROR, + window: DEFAULT_WINDOW_US, + received: vec![(200_000, CDN_A, "p0", false)], + events: vec![(100_000, "start"), (4_900_000, "end")], + spans: vec![], + claims: vec![], + notes: None, + energy: vec![unmetered(role::SINK_DECODE), unmetered(role::DISPLAY)], + attachments: vec![], + } +} + +/// A session whose claims get every verdict §10.2 step 9 gives. +fn claim_verdicts() -> Plan { + let x = || vec![("e0", 2000), ("e1", 2000)]; + let full = || vec![ad_span(0, 4000, 10_000)]; + let mx = marker("ad-x", "05:11.000"); + let mut dup = ClaimPlan::new(&mx, x(), vec![ad_span(0, 2000, 10_000)]); + dup.slot = 0; + let mut malformed = ClaimPlan::new(&marker("ad-m", "05:12.000"), x(), full()); + malformed.geometry = 9; + let mut bad_sig = ClaimPlan::new(&marker("ad-s", "05:13.000"), x(), full()); + bad_sig.flip_sig = true; + let mut bad_key = ClaimPlan::new(&marker("ad-k", "05:14.000"), x(), full()); + bad_key.bad_key = true; + let mut unbound = ClaimPlan::new(&marker("ad-u", "05:15.000"), x(), full()); + unbound.room = Some(channel_id("film-library").expect("a name")); + let mut wrong_marker = ClaimPlan::new(&marker("ad-w", "05:16.000"), x(), full()); + wrong_marker.cue_marker = Some(marker("ad-w", "05:17.000")); + let mut asset = ClaimPlan::new(&marker("ad-a", "05:18.000"), x(), full()); + asset.objects = Some(vec!["e0"]); + let not_acked = ClaimPlan::new(&marker("ad-n", "05:19.000"), vec![("e2", 2000), ("x9", 2000)], full()); + Plan { + channel: "news-east", + key: 13, + presentation: presentation::LIVE, + start: T0 + 300 * S, + end: T0 + 330 * S, + ended: ended::ENDED, + window: DEFAULT_WINDOW_US, + received: vec![(300_000, CDN_B, "d0", true), (8_000_000, CDN_B, "e0", true), (9_000_000, CDN_B, "e1", true), (12_000_000, CDN_B, "e2", true), (14_000_000, CDN_B, "e3", true)], + events: vec![(500_000, "start"), (1_000_000, "present"), (29_500_000, "end")], + spans: vec![span(1000, 10_000, 10_000, AUD), span(11_000, 4000, 10_000, AD), span(15_000, 14_000, 10_000, AUD)], + claims: vec![ClaimPlan::new(&mx, x(), full()), dup, malformed, bad_sig, bad_key, unbound, wrong_marker, asset, not_acked], + notes: None, + energy: vec![unmetered(role::SINK_DECODE), unmetered(role::DISPLAY)], + attachments: vec![], + } +} + +/// The `k`-th of the 52 sessions of channel `fifty`, whose report states its rebuffering. +fn fifty(k: u8) -> Plan { + let start = T0 + 1000 * S + k as u64 * 60 * S; + let vst = 300 + 10 * k as u32; + let present = 100 + vst as u64; + let mut events = vec![(100_000, "start"), (present * 1000, "present")]; + let mut spans = Vec::new(); + if k.is_multiple_of(5) { + events.push((10_000_000, "stall-start")); + events.push((11_200_000, "stall-end")); + spans.push(span(present as u32, 10_000 - present as u32, 10_000, AUD)); + spans.push(span(11_200, 39_000 - 11_200, 10_000, AUD)); + } else { + spans.push(span(present as u32, 39_000 - present as u32, if k.is_multiple_of(7) { 2000 } else { 10_000 }, if k.is_multiple_of(3) { 0 } else { AUD })); + } + events.push((39_000_000, "end")); + let seg = ["f0", "f1", "f2", "f3"][k as usize % 4]; + let mut energy = vec![if k.is_multiple_of(2) { estimator(role::SINK_DECODE, (k as u64 + 1) * 10_000) } else { unmetered(role::SINK_DECODE) }]; + if !k.is_multiple_of(3) { + energy.push(unmetered(role::DISPLAY)); + } + Plan { + channel: "fifty", + key: 100 + k, + presentation: presentation::LIVE, + start, + end: start + 40 * S, + ended: if k.is_multiple_of(4) { ended::ENDED } else { ended::USER }, + window: DEFAULT_WINDOW_US, + received: vec![(150_000, EDGE, "fp", false), (250_000, EDGE, seg, true)], + events, + spans, + claims: vec![], + notes: None, + energy, + attachments: vec![], + } +} + +/// A session vector: its bundle (or the bytes standing for one), and what it was written to reach. +struct Vector { + name: String, + bundle: Result, + expect: &'static str, + /// For the reference producer's tests: the plan's inputs. + script: Option, + cmcd: Option, +} + +fn accepted(name: &str, b: &Built, script: bool, p: &Plan) -> Vector { + let script = script.then(|| { + json!({ + "key": p.key, + "window_us": p.window, + "received": p.received.iter().map(|(t, o, n, m)| json!({"at": p.start + t, "origin": o, "object": n, "media": m})).collect::>(), + "events": p.events.iter().map(|(t, e)| json!({"at": p.start + t, "event": e})).collect::>(), + }) + }); + let cmcd = metering::cmcd_batch_body(&b.observations).expect("a batch"); + Vector { name: name.into(), bundle: Ok(b.bundle()), expect: "accept", script, cmcd: Some(cmcd) } +} + +fn refused(name: &str, b: Bundle, expect: &'static str) -> Vector { + Vector { name: name.into(), bundle: Ok(b), expect, script: None, cmcd: None } +} + +/// An acknowledgement of `items` by class, signed by `signer`, as the corpus's controls need it. +#[allow(clippy::too_many_arguments)] +fn ack(signer: &SigningKey, upstream: [u8; 32], downstream: [u8; 32], start: u64, end: u64, prev: [u8; 32], classes: &[(u8, &[&str])]) -> (Ack, Vec>) { + let mut entries = Vec::new(); + let mut lists = Vec::new(); + for (c, names) in classes { + let items: Vec = names.iter().map(|n| Item { content_hash: h(n), start: 0, end: object(n).len() as u64 }).collect(); + entries.push(Entry::of(*c, ZERO, ZERO, &items)); + lists.push(settlement::item_list(&items)); + } + let a = Ack { mode: settlement::mode::ACK, role: settlement::role::SINK, upstream, downstream, start, end, prev, entries, sig: [0; 64] }.sign(signer); + (a, lists) +} + +fn vectors() -> (Vec, Vec<(String, Built)>) { + let plans = [("live_two_origins", live_two_origins()), ("on_demand_hidden", on_demand_hidden()), ("nothing_presented", nothing_presented()), ("claim_verdicts", claim_verdicts())]; + let mut v = Vec::new(); + let mut built = Vec::new(); + for (n, p) in &plans { + let b = build(p); + v.push(accepted(n, &b, *n == "live_two_origins", p)); + built.push((n.to_string(), b)); + } + // The same session key and session as `nothing_presented`, a record of its own: accepted + // alone, and refused beside it in one report (§11.1). + let mut again = nothing_presented(); + again.end += S; + let b = build(&again); + v.push(accepted("nothing_presented_again", &b, false, &again)); + built.push(("nothing_presented_again".into(), b)); + for k in 0..52u8 { + let p = fifty(k); + let b = build(&p); + let n = format!("fifty_{k:02}"); + v.push(accepted(&n, &b, false, &p)); + built.push((n, b)); + } + + // The controls, each one defect of `nothing_presented` (C) or of `live_two_origins` (A). + let c = build(¬hing_presented()); + let a = build(&live_two_origins()); + let player = c.record.player; + let up_a = origin_upstream(CDN_A); + let (cs, ce) = (c.record.start, c.record.end); + + v.push(Vector { name: "bundle_not_json".into(), bundle: Err("{\"v\":1,\"session\":".into()), expect: "audit_bundle_invalid", script: None, cmcd: None }); + let mut j = c.bundle().to_json(); + j.as_object_mut().expect("an object").remove("notes"); + v.push(Vector { name: "bundle_member_missing".into(), bundle: Err(j.to_string()), expect: "audit_bundle_invalid", script: None, cmcd: None }); + + let mut t = c.bundle(); + t.session.truncate(40); + v.push(refused("session_truncated", t, "truncated")); + let mut t = c.bundle(); + t.session.push(0); + v.push(refused("session_trailing_byte", t, "malformed")); + let mut t = c.bundle(); + t.session[4] = 2; + v.push(refused("session_version_2", t, "unknown_version")); + + let mut x = c.clone(); + x.record.origins = vec![format!("{CDN_A}/live")]; + v.push(refused("session_origin_with_path", x.reseal().bundle(), "audit_session_invalid")); + let mut x = c.clone(); + x.record.energy[0] = EnergyEntry { role: role::SINK_DECODE, joules: 5, class: None }; + v.push(refused("session_unmetered_with_joules", x.reseal().bundle(), "energy_invalid")); + let mut x = c.clone(); + x.record.player = SMALL_ORDER; + v.push(refused("session_small_order_key", x.reseal().bundle(), "bad_key")); + let mut t = c.bundle(); + let n = t.session.len(); + t.session[n - 1] ^= 1; + v.push(refused("session_bad_signature", t, "bad_signature")); + let mut x = c.clone(); + x.record.owner = pk(STRANGER); + v.push(refused("session_wrong_owner", x.reseal().bundle(), "audit_wrong_owner")); + let mut x = c.clone(); + x.record.channel = channel_id("other-channel").expect("a name"); + x.record.session = session_of(&x.record.channel, &player); + v.push(refused("session_unknown_channel", x.reseal().bundle(), "audit_unknown_channel")); + let mut x = c.clone(); + x.record.session = [9; 16]; + v.push(refused("session_unbound", x.reseal().bundle(), "audit_session_unbound")); + + let mut t = c.bundle(); + t.acks.clear(); + v.push(refused("ack_not_given", t, "record_missing")); + let k = &c.key; + let mut x = c.clone(); + x.acks = vec![ack(k, up_a, player, cs, ce, ZERO, &[(4, &["p0"]), (1, &["s0"])])]; + v.push(refused("ack_entries_out_of_order", x.reseal().bundle(), "ack_invalid")); + let mut x = c.clone(); + x.acks = vec![ack(k, player, player, cs, ce, ZERO, &[(4, &["p0"])])]; + v.push(refused("ack_self_acknowledged", x.reseal().bundle(), "self_acknowledged")); + let mut x = c.clone(); + x.acks = vec![ack(&key(STRANGER), up_a, pk(STRANGER), cs, ce, ZERO, &[(4, &["p0"])])]; + v.push(refused("ack_by_another_key", x.reseal().bundle(), "audit_ack_unbound")); + let mut x = c.clone(); + x.acks = vec![ack(k, origin_upstream("https://elsewhere.example"), player, cs, ce, ZERO, &[(4, &["p0"])])]; + v.push(refused("ack_from_unlisted_origin", x.reseal().bundle(), "audit_origin_unknown")); + let mut x = c.clone(); + x.acks = vec![ack(k, up_a, player, cs, ce + 1, ZERO, &[(4, &["p0"])])]; + v.push(refused("ack_past_session_end", x.reseal().bundle(), "ack_outside_period")); + let mut x = c.clone(); + x.acks = vec![ack(k, up_a, player, cs, ce, ZERO, &[(2, &["s0"]), (4, &["p0"])])]; + v.push(refused("ack_fetch_class_in_live", x.reseal().bundle(), "audit_ack_invalid")); + let mut t = c.bundle(); + t.acks[0].1[0].truncate(47); + v.push(refused("items_not_whole", t, "items_invalid")); + let mut t = c.bundle(); + t.acks[0].1[0][3] ^= 1; + v.push(refused("items_do_not_recompute", t, "items_mismatch")); + let mut x = c.clone(); + let first = ack(k, up_a, player, cs, cs + 3 * S, ZERO, &[(4, &["p0"])]); + let second = ack(k, up_a, player, cs + 2 * S, ce, first.0.digest(), &[(4, &["p1"])]); + x.acks = vec![first, second]; + v.push(refused("ack_windows_overlap", x.reseal().bundle(), "ack_overlap")); + let mut x = c.clone(); + let first = ack(k, up_a, player, cs, cs + 3 * S, ZERO, &[(4, &["p0"])]); + let second = ack(k, up_a, player, cs + 3 * S, ce, ZERO, &[(4, &["p1"])]); + x.acks = vec![first, second]; + v.push(refused("ack_chain_broken", x.reseal().bundle(), "ack_chain_broken")); + + let mut t = c.bundle(); + t.log.push(' '); + v.push(refused("log_not_the_bound_one", t, "audit_document_mismatch")); + let mut x = c.clone(); + let one: Value = serde_json::from_str(&x.log).expect("JSON"); + x.log = serde_json::to_string(&json!([one[0], one[0]])).expect("JSON"); + x.record.log = sha256(&[x.log.as_bytes()]); + v.push(refused("log_of_two_sessions", x.reseal().bundle(), "qoe_invalid")); + let mut x = c.clone(); + x.record.stalls = 1; + x.record.stall_ms = 400; + v.push(refused("stalls_not_the_logs", x.reseal().bundle(), "audit_qoe_mismatch")); + let mut x = c.clone(); + x.notes = Some(serde_json::to_string(&json!([{"content_hash": hex(&h("s3")), "cmsd_static": "ot=v", "cmsd_dynamic": null}])).expect("JSON")); + x.record.notes = sha256(&[x.notes.as_ref().expect("notes").as_bytes()]); + v.push(refused("notes_name_an_unacknowledged_object", x.reseal().bundle(), "audit_notes_invalid")); + let mut x = c.clone(); + x.record.attachments = vec![sha256(&[&object("x9")])]; + v.push(refused("attachment_not_given", x.reseal().bundle(), "record_missing")); + let mut t = a.bundle(); + t.claims.push(build(&claim_verdicts()).claims[0].clone()); + v.push(refused("claim_not_named", t, "audit_bundle_invalid")); + (v, built) +} + +/// A report vector: the channel, the sessions it is given, the served report and the outcome. +struct ReportVector { + name: &'static str, + channel: &'static str, + sessions: Vec, + /// The sessions the served report reads. + over: Vec, + from: u64, + to: u64, + tamper: fn(&mut Value), + expect: &'static str, +} + +fn report_vectors() -> Vec { + let names = |v: &[&str]| v.iter().map(|s| s.to_string()).collect::>(); + let news = || names(&["live_two_origins", "nothing_presented", "claim_verdicts"]); + let hour = (T0, T0 + 3600 * S); + vec![ + ReportVector { name: "news_east", channel: "news-east", sessions: news(), over: news(), from: hour.0, to: hour.1, tamper: |_| {}, expect: "accept" }, + ReportVector { name: "film_library", channel: "film-library", sessions: names(&["on_demand_hidden"]), over: names(&["on_demand_hidden"]), from: hour.0, to: hour.1, tamper: |_| {}, expect: "accept" }, + ReportVector { name: "fifty", channel: "fifty", sessions: (0..52).map(|k| format!("fifty_{k:02}")).collect(), over: (0..52).map(|k| format!("fifty_{k:02}")).collect(), from: T0, to: T0 + 86_400 * S, tamper: |_| {}, expect: "accept" }, + ReportVector { + name: "report_does_not_recompute", + channel: "news-east", + sessions: news(), + over: news(), + from: hour.0, + to: hour.1, + tamper: |r| { + let p = &mut r["report"]["presented"]["presented_ms"]; + *p = json!(p.as_u64().expect("a figure") + 1); + }, + expect: "audit_report_mismatch", + }, + ReportVector { name: "report_bad_signature", channel: "news-east", sessions: news(), over: news(), from: hour.0, to: hour.1, tamper: |r| r["sig"] = json!(format!("00{}", &r["sig"].as_str().expect("hex")[2..])), expect: "bad_signature" }, + ReportVector { + name: "report_names_a_session_not_given", + channel: "news-east", + sessions: names(&["live_two_origins", "claim_verdicts"]), + over: news(), + from: hour.0, + to: hour.1, + tamper: |_| {}, + expect: "record_missing", + }, + ReportVector { + name: "report_one_session_twice", + channel: "news-east", + sessions: names(&["live_two_origins", "nothing_presented", "claim_verdicts", "nothing_presented_again"]), + over: news(), + from: hour.0, + to: hour.1, + tamper: |_| {}, + expect: "audit_session_duplicate", + }, + ] +} + +fn bundle_bytes(v: &Value) -> Vec { + match &v["bundle_text"] { + Value::String(s) => s.clone().into_bytes(), + _ => serde_json::to_vec(&v["bundle"]).expect("JSON"), + } +} + +fn served_channels(doc: &Value) -> Vec { + doc["channels"].as_array().into_iter().flatten().filter_map(|c| c.as_str().map(String::from)).collect() +} + +fn owner_of(doc: &Value) -> [u8; 32] { + unhex(doc["owner"].as_str().unwrap_or("")).and_then(|b| b.try_into().ok()).unwrap_or(ZERO) +} + +fn report_outcome(doc: &Value, v: &Value) -> Value { + let bundles: Vec> = v["sessions"] + .as_array() + .into_iter() + .flatten() + .filter_map(|n| doc["sessions"].as_array()?.iter().find(|s| s["name"] == *n).map(bundle_bytes)) + .collect(); + match check_report(&v["served"], &bundles, v["channel"].as_str().unwrap_or(""), &owner_of(doc), &served_channels(doc)) { + Ok(()) => json!({"outcome": "accept", "digest": hex(&sha256(&[&report_bytes(&v["served"]["report"])]))}), + Err(r) => json!({"outcome": "refuse", "reason": r.reason.code(), "at": r.at}), + } +} + +/// The corpus file, from fixed seeds. +pub fn generate() -> String { + let (vs, built) = vectors(); + let mut objects = Map::new(); + for (n, _) in OBJECTS { + objects.insert(n.into(), json!(hex(&object(n)))); + } + let mut doc = json!({ + "corpus": CAPABILITY, + "extension": "extensions/stream-audit.md", + "owner": hex(&pk(OWNER)), + "channels": SERVED, + "objects": objects, + "sessions": [], + "reports": [], + }); + let channels: Vec = SERVED.iter().map(|s| s.to_string()).collect(); + let mut sessions = Vec::new(); + for v in vs { + let mut j = json!({"name": v.name}); + let bytes = match &v.bundle { + Ok(b) => { + j["bundle"] = b.to_json(); + serde_json::to_vec(&j["bundle"]).expect("JSON") + } + Err(t) => { + j["bundle_text"] = json!(t); + t.clone().into_bytes() + } + }; + if let Some(s) = v.script { + j["script"] = s; + } + if let Some(c) = v.cmcd { + j["cmcd"] = json!(c); + } + let got = verify_bundle(&bytes, &pk(OWNER), &channels).to_json(); + let reached = if got["outcome"] == "accept" { "accept" } else { got["reason"].as_str().expect("a reason") }; + assert_eq!(reached, v.expect, "session vector {}: {got}", v.name); + j["outcome"] = got; + sessions.push(j); + } + doc["sessions"] = Value::Array(sessions); + let owner_key = key(OWNER); + let mut reports = Vec::new(); + for r in report_vectors() { + // The report served is over the sessions it should read; a vector given other bundles + // is checked against it as served. + let accepted: Vec = r + .over + .iter() + .filter_map(|n| built.iter().find(|(m, _)| m == n)) + .filter_map(|(_, b)| match verify_bundle(&serde_json::to_vec(&b.bundle().to_json()).expect("JSON"), &pk(OWNER), &channels) { + Outcome::Accept(a) => Some(*a), + Outcome::Refuse(_) => None, + }) + .collect(); + assert_eq!(accepted.len(), r.over.len(), "report vector {}: every session it reads is accepted", r.name); + let full: Vec<&Accepted> = accepted.iter().collect(); + let rep = report(r.channel, &pk(OWNER), r.from, r.to, &full).expect("a report"); + let mut served = serve_report(&rep, &owner_key); + (r.tamper)(&mut served); + if r.name == "report_does_not_recompute" { + served["sig"] = json!(hex(&sign(&owner_key, REPORT_DOMAIN, &report_bytes(&served["report"])))); + } + let mut j = json!({"name": r.name, "channel": r.channel, "sessions": r.sessions, "served": served}); + let got = report_outcome(&doc, &j); + let reached = if got["outcome"] == "accept" { "accept" } else { got["reason"].as_str().expect("a reason") }; + assert_eq!(reached, r.expect, "report vector {}: {got}", r.name); + j["outcome"] = got; + reports.push(j); + } + doc["reports"] = Value::Array(reports); + let mut s = serde_json::to_string_pretty(&doc).expect("JSON"); + s.push('\n'); + s +} + +/// Every vector of `doc` against this module: the failures. +pub fn verify(doc: &str) -> Vec { + let doc: Value = match serde_json::from_str(doc) { + Ok(d) => d, + Err(e) => return vec![format!("vectors.json does not parse: {e}")], + }; + let channels = served_channels(&doc); + let owner = owner_of(&doc); + let mut fails = Vec::new(); + for v in doc["sessions"].as_array().into_iter().flatten() { + let got = verify_bundle(&bundle_bytes(v), &owner, &channels).to_json(); + if got != v["outcome"] { + fails.push(format!("session {}: stated {}, got {got}", v["name"], v["outcome"])); + } + } + for v in doc["reports"].as_array().into_iter().flatten() { + let got = report_outcome(&doc, v); + if got != v["outcome"] { + fails.push(format!("report {}: stated {}, got {got}", v["name"], v["outcome"])); + } + } + fails +} diff --git a/wai/wai-rs/tests/stream_audit.rs b/wai/wai-rs/tests/stream_audit.rs new file mode 100644 index 000000000..fa6ab8d64 --- /dev/null +++ b/wai/wai-rs/tests/stream_audit.rs @@ -0,0 +1,113 @@ +//! `wai.delivery.audit` (extensions/stream-audit.md): the committed corpus +//! `live-conformance/audit/vectors.json` regenerates byte for byte from its fixed seeds and is the +//! only file there besides its README, every session and report in it gets the outcome it states, +//! every reason the extension adds and every code it borrows refuses a vector, the accepted +//! vectors are the cases §15 names, and the extension's §14 lists exactly the reasons the +//! reference reports. +#![cfg(feature = "stream_audit")] + +use std::collections::BTreeSet; +use std::path::PathBuf; +use wai::stream_audit::{corpus, Reason}; + +fn wai_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") +} + +fn read(p: &str) -> String { + std::fs::read_to_string(wai_dir().join(p)).unwrap_or_else(|e| panic!("{p}: {e}")) +} + +fn committed() -> String { + read("live-conformance/audit/vectors.json") +} + +fn doc() -> serde_json::Value { + serde_json::from_str(&committed()).unwrap() +} + +#[test] +fn the_committed_corpus_regenerates_from_its_seeds() { + assert!(committed() == corpus::generate(), "live-conformance/audit/vectors.json differs from `wai_stream_audit_vectors gen`"); + let mut files: Vec = std::fs::read_dir(wai_dir().join("live-conformance/audit")).unwrap().flatten().map(|e| e.file_name().to_string_lossy().into_owned()).collect(); + files.sort(); + assert_eq!(files, ["README.md", "vectors.json"]); +} + +#[test] +fn every_session_and_report_gets_its_stated_outcome() { + let fails = corpus::verify(&committed()); + assert!(fails.is_empty(), "{}", fails.join("\n")); +} + +/// Every reason this extension adds, and every code it borrows, refuses a vector or is a claim's +/// verdict; the accepted sessions and reports are §15's, with the figures each was written for. +#[test] +fn the_corpus_covers_every_reason_and_case() { + let doc = doc(); + let mut seen: BTreeSet = BTreeSet::new(); + for section in ["sessions", "reports"] { + for v in doc[section].as_array().unwrap() { + if let Some(r) = v["outcome"]["reason"].as_str() { + seen.insert(r.to_string()); + } + for c in v["outcome"]["claims"].as_array().into_iter().flatten() { + seen.insert(c["verdict"].as_str().unwrap().to_string()); + } + } + } + for r in Reason::ALL { + assert!(seen.contains(r.code()), "nothing in the corpus is refused {r}"); + } + for r in Reason::REUSED.iter().chain(Reason::VERDICTS.iter()) { + assert!(seen.contains(*r), "nothing in the corpus is refused {r}"); + } + let session = |n: &str| doc["sessions"].as_array().unwrap().iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no session {n}"))["outcome"].clone(); + let live = session("live_two_origins"); + assert_eq!(live["outcome"], "accept"); + assert_eq!(live["presentation"], "live"); + assert_eq!(live["claims"][0]["verdict"], "presented"); + let viewable: Vec = live["claims"].as_array().unwrap().iter().map(|c| c["viewable"].as_bool().unwrap()).collect(); + assert!(viewable.contains(&true) && viewable.contains(&false), "one ad in view, one half out of it"); + assert!(live["repeats"]["objects"].as_u64().unwrap() >= 1, "a repeat"); + assert_eq!(live["notes"], 2); + let origins: BTreeSet<&str> = live["delivery"].as_array().unwrap().iter().map(|d| d["origin"].as_str().unwrap()).collect(); + assert_eq!(origins.len(), 2, "two origins"); + assert_eq!(live["energy"][0]["class"], "Estimator"); + assert_eq!(live["energy"][1]["class"], "unmetered"); + let vod = session("on_demand_hidden"); + assert_eq!((vod["ended"].as_str(), vod["presentation"].as_str(), vod["attachments"].as_u64()), (Some("hidden"), Some("on_demand"), Some(1))); + assert_eq!(vod["energy"][0]["class"], "OnChipCounter"); + let none = session("nothing_presented"); + assert_eq!((none["ended"].as_str(), none["presented_ms"].as_u64()), (Some("error"), Some(0))); + assert_eq!(none["startup_ms"], serde_json::Value::Null); + let cv = session("claim_verdicts"); + let verdicts: BTreeSet<&str> = cv["claims"].as_array().unwrap().iter().map(|c| c["verdict"].as_str().unwrap()).collect(); + for v in ["presented", "claim_malformed", "bad_key", "bad_signature", "audit_claim_unbound", "audit_marker_mismatch", "claim_duplicate", "claim_asset_mismatch", "audit_ad_not_acknowledged"] { + assert!(verdicts.contains(v), "no claim of claim_verdicts is {v}"); + } + let report = |n: &str| doc["reports"].as_array().unwrap().iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no report {n}")).clone(); + let news = report("news_east"); + assert_eq!(news["outcome"]["outcome"], "accept"); + assert_eq!(news["served"]["report"]["rebuffering"]["suppressed"], true, "fewer than 50 sessions"); + let fifty = report("fifty"); + assert_eq!(fifty["outcome"]["outcome"], "accept"); + let reb = &fifty["served"]["report"]["rebuffering"]; + assert_eq!((reb["suppressed"].as_bool(), reb["sessions"].as_u64()), (Some(false), Some(52))); + assert!(reb["stalls"].as_u64().unwrap() > 0); + let energy = fifty["served"]["report"]["energy"].as_array().unwrap(); + assert!(energy.iter().all(|e| e["class"] != "unmetered"), "the unmetered marker is never a total"); + assert!(fifty["served"]["report"]["unmetered"]["sink_decode"].as_u64().unwrap() > 0); + assert!(fifty["served"]["report"]["absent"]["display"].as_u64().unwrap() > 0); +} + +/// §14 of the extension lists exactly the codes `Reason::ALL` has, each once, in order. +#[test] +fn the_extension_lists_the_reasons() { + let text = read("extensions/stream-audit.md"); + let at = text.find("\n## 14. Reasons").expect("a §14"); + let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); + let listed: Vec = text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect(); + let want: Vec = Reason::ALL.iter().map(|r| r.code().to_string()).collect(); + assert_eq!(listed, want, "§14's table and Reason::ALL differ"); +} -- 2.54.0 (Apple Git-157) From a0a5e0e605b1acf8e20df93f116570ba0066e295 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 25 Oct 2026 21:27:31 -0400 Subject: [PATCH 082/242] =?UTF-8?q?wai:=20receipt=20ledger=20=E2=80=94=20t?= =?UTF-8?q?he=20tariff,=20the=20invoice=20and=20its=20verifier=20(wai.deli?= =?UTF-8?q?very.ledger)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit extensions/receipt-ledger.md: a tariff (WLT1) a seller and a buyer both sign, pricing every unit WAI's records state in one currency (a room session's units, acknowledged bytes by object class, a package's packages, entries and stored bytes, counted ad impressions) with the scope of rooms, parties, packagers and deciders it bills; sources verified by their own extensions; an invoice (WLI1) whose lines cite their records, whose energy is per scope and per class with its largest declared uncertainty, chained so no record is billed twice; and the ad and delivery reports. wai-rs: the receipt_ledger module (feature receipt_ledger), its corpus generator and wai_receipt_ledger_vectors; live-conformance/ledger/ and receipt_ledger_verify.py, the second implementation. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/receipt-ledger.md | 544 +++ wai/live-conformance/ledger/README.md | 61 + wai/live-conformance/ledger/vectors.json | 3165 +++++++++++++++++ wai/live-conformance/receipt_ledger_verify.py | 880 +++++ wai/wai-rs/Cargo.toml | 17 + .../src/bin/wai_receipt_ledger_vectors.rs | 67 + wai/wai-rs/src/lib.rs | 9 + wai/wai-rs/src/receipt_ledger.rs | 1704 +++++++++ wai/wai-rs/src/receipt_ledger/corpus.rs | 492 +++ wai/wai-rs/tests/receipt_ledger.rs | 97 + 10 files changed, 7036 insertions(+) create mode 100644 wai/extensions/receipt-ledger.md create mode 100644 wai/live-conformance/ledger/README.md create mode 100644 wai/live-conformance/ledger/vectors.json create mode 100644 wai/live-conformance/receipt_ledger_verify.py create mode 100644 wai/wai-rs/src/bin/wai_receipt_ledger_vectors.rs create mode 100644 wai/wai-rs/src/receipt_ledger.rs create mode 100644 wai/wai-rs/src/receipt_ledger/corpus.rs create mode 100644 wai/wai-rs/tests/receipt_ledger.rs diff --git a/wai/extensions/receipt-ledger.md b/wai/extensions/receipt-ledger.md new file mode 100644 index 000000000..b869d1b67 --- /dev/null +++ b/wai/extensions/receipt-ledger.md @@ -0,0 +1,544 @@ +# WAI Extension: Receipt Ledger + +> Status: Draft. Registers the record `wai.delivery.ledger`: invoice lines a customer can +> recompute from the signed records WAI already defines. A seller and a buyer both sign a +> **tariff**, `WLT1`: one currency, and one price for each unit those records state, the +> units of a room session ([live-metering](live-metering.md) §4.4), the acknowledged bytes of +> each object class ([settlement](settlement.md) §3.3), the packages, entries and stored bytes +> of a package receipt ([packaging](packaging.md) §9) and the counted impressions of a +> `wai.video.ssai` receipt of the break form ([live-ads](live-ads.md) §8, +> [ad-presentation](ad-presentation.md) §9.2); and the **scope** it covers, the rooms, the +> delivering parties, the packagers and the ad deciders whose records are billed under it. The +> ledger verifies each **source** by the extension that defines it, and the seller signs an +> **invoice**, `WLI1`, for a period: the records it was computed from, one line per source, +> unit and price, each line citing the records it came from, and the joules those records +> state, per scope and per acquisition class with their largest declared uncertainty, never +> summed across classes or scopes. Invoices under one tariff form a chain, and no record is +> billed in two of them. A verifier given the tariff, the invoice, the invoice before it and +> the sources recomputes every line and refuses an invoice that does not recompute. Beside the +> invoice, the ledger states two **reports**: the ad decisions reconciled against the players' +> presentation claims, and the delivery of each party under the one tariff with the bytes two +> parties were acknowledged for twice. No new transport, no payment and no tax: an invoice is +> the recomputable figures a payment rests on. The reference implementation is the +> `receipt_ledger` module of `wai-rs` (feature `receipt_ledger`) and the service +> `wai/wai-ledger`; the corpus is [`live-conformance/ledger/`](../live-conformance/ledger/), +> and its second implementation is `live-conformance/receipt_ledger_verify.py`. +> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. +> +> Texts read for this revision: the WAI texts this one cites; ISO 4217 (the alphabetic +> currency code); RFC 8032; RFC 8259; FIPS 180-4. + +## 1. Scope + +In scope: how one tariff prices rooms, packages, HTTP and relayed delivery and ads together +(§3); the tariff, its two signatures and its scope (§4); the sources a ledger is given and what +each contributes (§5); the invoice, its records, lines, citations and energy, its signature and +its chain (§6); verification (§7); the two reports (§8); and the reference service (§9). + +Out of scope: a transport; payment, a payment rail, tax and any contact with a party outside the +records; how a seller chooses its prices; how a party measures its energy, which +[energy-measurement](energy-measurement.md) governs; and the records themselves, which their own +extensions define, verify and leave as they are. The live-metering tariff and the settlement +statement's tariff keep their meaning: this extension reads them, and changes neither. + +Relationship to the core spec: `wai.delivery.ledger` is a **record** ([SPEC](../SPEC.md) §5): +a signed record a WAI tool emits and verifies. It names no payload format and is not +dispatchable. + +## 2. Terms + +- **Seller**, **buyer**: Ed25519 keys (RFC 8032). The seller operates the ledger and signs its + invoices; the buyer is the customer billed. +- **Time**: microseconds since 1970-01-01T00:00:00Z, the unit of a room's `t0` (live-rooms §3.1). + A **period** is a span of time from its `start` (included) to its `end` (excluded). +- **Minor unit**: a currency's smallest unit (ISO 4217); an **amount** is an integer of + millionths of one, as settlement §7.3's amounts are. +- **Source**: one record a ledger bills from, with what its own verifier is given (§5). +- **Record identity**: the 32 bytes a record of a kind is named by (§5.2). +- *u64*, *u32*, *u16*, *u8*: little-endian unsigned integers of that many bits. A **digest** is + SHA-256 (FIPS 180-4), 32 bytes; **zero** of a key or digest field is 32 zero bytes. +- **Class**, **tag**, **unmetered marker**: an acquisition class of energy-measurement §2.7, its + tag byte (2 `ModelBased`, 3 `Estimator`, 4 `OnChipCounter`, 5 `CalibratedInstrument`), and a + figure of 0 with no class. + +## 3. One tariff across rooms, packages and HTTP delivery + +The records a deployment bills from state their units in their own terms. A room-session receipt +states microseconds and events (live-metering §4.4), and may state a `tariff` of its own, which +prices the session's grants against their funds ceilings (§4.6 there). A settlement statement +states acknowledged bytes by object class, whatever carried them: a relay over MOQT, or a cache +over HTTP whose deliveries were acknowledged as settlement §9.1 maps them; it may state a tariff +too, which prices its own lines (§7.3 there). A package receipt states entries and bytes +(packaging §9.1) and no tariff. An ad decision's receipt states impressions (live-ads §8.2) and +no tariff of the seller's. + +A tariff of this extension prices every one of those units in one currency, so one invoice bills +a buyer's rooms, deliveries, packages and ads together. Three rules keep every figure in it +consistent with what the records say: + +1. **A record that states a tariff states this one.** A room-session receipt of a source either + states no tariff (currency zero) or the tariff's currency and its thirteen room prices, in + live-metering §4.1's order; a settlement statement either states no tariff or the tariff's + currency and its four delivery prices, in settlement §7.1's order. A record under another + tariff is refused `tariff_conflict` (§7.2 step 5): the funds its grants were held to, or the + amount its party stated, were computed at other prices, and an invoice at these prices would + contradict it. +2. **One rounding, the same everywhere.** Every amount is a quantity times a price, divided by + the unit's divisor and rounded down once per invoice line (§6.4), as settlement §7.3 rounds a + statement's line. A statement's own amount rounds once per payer; the invoice rounds once per + party and class, so the two can differ by less than one millionth of a minor unit per payer + line, and the delivery report (§8.2) states both. +3. **Units are never converted.** A room is billed in its own units, a delivery in bytes, a + package in packages, entries and stored bytes, an ad in impressions. No line turns one unit + into another, and no line is priced from joules: the energy of an invoice is stated beside + its lines (§6.6), per class, and is not an amount. + +So the tariff is the one place prices live: a room receipt's funds verdicts, a statement's +amount and an invoice's lines all follow from it, and a buyer who holds the tariff and the +records recomputes all three. + +## 4. The tariff + +### 4.1 Layout + +| field | size | meaning | +|---|---|---| +| `magic` | 4 | `WLT1` (ASCII) | +| `version` | 1 | 1 in this revision | +| `seller` | 32 | the seller's key, which signs the tariff and every invoice under it | +| `buyer` | 32 | the buyer's key, which accepts the tariff by signing it | +| `start`, `end` | 8 each | u64: the period the tariff is good for | +| `currency` | 3 | three capitals `A`–`Z` (ISO 4217) | +| `room` | 52 | thirteen u32: millionths of a minor unit per second of `publisher_us`, of `subscriber_us` for each layer class 0 to 8, of `composition_us` and of `recorded_us`, and per event sent (live-metering §4.1's order) | +| `delivery` | 32 | four u64: millionths of a minor unit per 10^9 acknowledged bytes of each object class, in class order (settlement §7.1) | +| `package` | 24 | three u64: millionths of a minor unit per package receipt, per entry, and per 10^9 stored bytes | +| `ads` | 16 | two u64: millionths of a minor unit per presented impression, and per billable impression | +| `scope` | 2 + 33 × *n* | a u16 count *n*, then each entry: `kind` (u8) and `id` (32), in strictly ascending order of (`kind`, `id`) | +| `seller_sig` | 64 | §4.2 | +| `buyer_sig` | 64 | §4.2 | + +### 4.2 Digest and signatures + +The **digest** of a tariff is SHA-256 over its bytes before `seller_sig`, the **body**. Its +`seller_sig` is Ed25519 by `seller` over the 18 bytes `wai:ledger-tariff` (ASCII) and 0x01, +followed by the body; its `buyer_sig` is Ed25519 by `buyer` over the 18 bytes `wai:ledger-accept` +(ASCII) and 0x01, followed by the body. Both are verified strictly, as [SPEC](../SPEC.md) §7 +*Signatures* requires: a key that fails step 1 or 2 is `bad_key`, and a signature that fails a +later step is `bad_signature`. The buyer's signature is what binds the buyer to the prices: a +tariff the buyer did not sign bills nobody. + +### 4.3 Reading and rules + +A reader reads exactly the bytes it is given as one tariff, in the order of §4.1, and refuses at +the first of these that holds: fewer than 4 bytes, `truncated`; a magic other than `WLT1`, +`malformed`; no fifth byte, `truncated`; a version other than 1, `unknown_version`; the bytes end +before a field the layout requires, `truncated`; bytes after `buyer_sig`, `malformed`. It then +refuses as `tariff_invalid` a tariff that breaks any of these: + +- `start` is below `end`; `currency` is three capitals `A`–`Z`; +- `seller` and `buyer` are not zero, and not the same key; +- `scope` has at least one entry, in strictly ascending order of (`kind`, `id`), each `kind` 1 to + 4 and each `id` not zero. + +Then the seller's signature, then the buyer's: `bad_key`, `bad_signature`. + +### 4.4 Scope + +A scope entry names whose records the tariff bills: + +| kind | `id` | a source is in scope when | +|---|---|---| +| 1 | a room id | a room-session receipt's `room` is the `id` | +| 2 | a party's key | a settlement statement's `party` is the `id` | +| 3 | a packager's key | a package receipt's `signer_pubkey` is the `id` | +| 4 | an ad decider's key | every receipt of an ads source has `signer_pubkey` the `id` (live-ads §3.4) | + +The scope is what the buyer agreed to pay for: a record signed by a party the tariff does not name +is refused `outside_scope` (§7.2 step 5), whoever submits it. + +## 5. Sources + +### 5.1 Kinds and their documents + +A source is a JSON object (RFC 8259) whose `kind` names its extension, holding exactly what that +extension's verifier is given. Bytes are lowercase hexadecimal strings. + +| `kind` | members | verified as | +|---|---|---| +| `room` | `room` (`descriptor`, `grants`, `key_log_head` (a string or `null`), `chains`, a list of lists of records), `receipt`, `hops` (each `record` and `ledger`), `descriptors`, `compositions`, `takes`, `logs` (a string or `null`) | live-metering §5.1, its receipt being `receipt` | +| `delivery` | `statement`, `records` (each `record` and its `items`, one item list per entry), `proofs`, `hops`, `rooms` (each `descriptor` and `grants`) | settlement §8.2 | +| `package` | `receipt` (the text of `package.receipt.json`), `indexes` (the index files in the receipt's component order) | packaging §9.1, a receipt of version 2 against its indexes | +| `ads` | `descriptor`, `grants`, `key_log`, `chains`, `policy`, `pt0_ms`, `receipts` (form-2 receipts in their JSON carriage), `pods`, `claims`, `creatives`, `issuers` | live-ads §9, with ad-presentation §9 under a policy that requires claims | + +A source is accepted when its extension's verifier accepts it; for `ads`, when the outcome is +**accept** (live-ads §9.4), a **gap** being `source_gap`. A source that is not such an object, +whose `kind` is none of these, or whose members are not exactly its kind's, each of its form, is +refused `malformed`. + +### 5.2 What a source contributes + +Each source contributes **records**, each named by a kind and an identity: + +| record kind | record | identity | contributed by | +|---|---|---|---| +| 1 | a room-session receipt, `WMR1` | its digest (live-metering §4.2) | a `room` source: its receipt | +| 2 | a per-hop record, `WMH1` | its digest (live-metering §3.5) | a `room` source: each record its receipt names; a `delivery` source: each record its statement names in `hops` | +| 3 | a settlement statement, `WSS1` | its digest (settlement §7.4) | a `delivery` source: its statement | +| 4 | an acknowledgement or declaration, `WDA1` | its digest (settlement §3.5) | a `delivery` source: each record its statement names in `records` | +| 5 | a package receipt | its `receipt_hash` (packaging §9.1) | a `package` source | +| 6 | a `wai.video.ssai` receipt of the break form | its identity (live-ads §8.4) | an `ads` source: each receipt | +| 7 | a presentation claim, `WPC1` | its digest (ad-presentation §5.3) | an `ads` source: each claim that reads | + +A source's **head** is its receipt, statement or package receipt; an `ads` source's heads are its +receipts. A head of one source that another source of the invoice also contributes is refused +`record_duplicate`; a per-hop record, which a room receipt and a statement can both name, is +contributed once. + +### 5.3 Placement in time + +A source is **in** a period when: + +- `room`: the receipt's session, placed by its room's `t0`, lies within it: `t0` + `start` ≥ the + period's `start` and `t0` + `end` ≤ its `end`; +- `delivery`: the statement's period lies within it; +- `ads`: the `t` of the break each receipt names, placed by the room's `t0`, is within it; +- `package`: always: a package receipt states no time, and the chain (§6.9) bills each once. + +Two room receipts of one room whose sessions overlap, two statements of one party whose periods +overlap, and two receipts of the invoice's ads sources naming one break and one session, bill one +thing twice: the later is refused `record_overlap`. + +## 6. The invoice + +### 6.1 Layout + +| field | size | meaning | +|---|---|---| +| `magic` | 4 | `WLI1` (ASCII) | +| `version` | 1 | 1 in this revision | +| `seller`, `buyer` | 32 each | the tariff's | +| `tariff` | 32 | the tariff's digest | +| `prev` | 32 | the digest of the invoice before it in its chain (§6.9); zero for the first | +| `start`, `end` | 8 each | u64: the period it bills | +| `currency` | 3 | the tariff's | +| `records` | 4 + 33 × *r* | a u32 count, then each record (§5.2): `kind` (u8) and `id` (32), in strictly ascending order of (`kind`, `id`) | +| `lines` | 2 + 94 × *l* | a u16 count, then each line (§6.3), in strictly ascending order of (`source_kind`, `source`, `unit`) | +| `energy` | 1 + 18 × *e* | a u8 count, then each row (§6.6): `scope` (u8), `tag` (u8), `figures` (u32), `joules` (u64, µJ), `max_ppm` (u32), in strictly ascending order of (`scope`, `tag`) | +| `unmetered` | 16 | four u32: the unmetered figures of scopes 1 to 4 (§6.6) | +| `amount` | 8 | u64: the sum of the lines' amounts | +| `sig` | 64 | §6.7 | + +### 6.2 Records + +`records` lists every record the sources contribute (§5.2), each once. It is what the invoice was +computed from, and what the next invoice of the chain may not bill again. + +### 6.3 Lines + +A line is 94 bytes: + +| field | size | meaning | +|---|---|---| +| `source_kind` | 1 | 1 room, 2 delivery, 3 package, 4 ads | +| `source` | 32 | the room id, the delivering party, the packager or the ad decider | +| `unit` | 1 | the unit (below) | +| `quantity` | 8 | u64: the quantity of the unit the records state | +| `price` | 8 | u64: the tariff's price for the unit | +| `amount` | 8 | u64: §6.4 | +| `cited` | 4 | u32: the records the line cites (§6.5) | +| `cites` | 32 | their **citation digest** (§6.5) | + +The units, the quantity each sums over the sources of its `source`, and the price and divisor of +each: + +| `source_kind` | `unit` | name | quantity | price | divisor | +|---|---|---|---|---|---| +| 1 | 1 | `publisher_us` | the receipts' `publisher_us` | `room` 0 | 10^6 | +| 1 | 2–10 | `subscriber_us_0`–`subscriber_us_8` | their `subscriber_us` of layer class `unit` − 2 | `room` 1–9 | 10^6 | +| 1 | 11 | `composition_us` | their `composition_us` | `room` 10 | 10^6 | +| 1 | 12 | `recorded_us` | their `recorded_us` | `room` 11 | 10^6 | +| 1 | 13 | `events_sent` | their `events_sent` | `room` 12 | 1 | +| 2 | 1–4 | `live`, `fetch`, `fill`, `ancillary` | the bytes of the statement's delivery lines of that class that are not free (settlement §7.2) | `delivery` `unit` − 1 | 10^9 | +| 3 | 1 | `packages` | the number of package receipts | `package` 0 | 1 | +| 3 | 2 | `entries` | their `total_entries` | `package` 1 | 1 | +| 3 | 3 | `stored_bytes` | their `stored_bytes` | `package` 2 | 10^9 | +| 4 | 1 | `presented_impressions` | under a policy that requires claims, the impressions that count (ad-presentation §9.2) | `ads` 0 | 1 | +| 4 | 2 | `billable_impressions` | under a policy that does not, the billable impressions (quartile at least 1) | `ads` 1 | 1 | + +There is one line for each source and unit whose quantity is above 0, a price of 0 included, and +none for a quantity of 0. A delivery line's bytes are those its party's downstream parties signed +for (settlement §3.7): a declared byte and a free retrieval are never billed, and the delivery +report states both (§8.2). An ad line counts impressions under one rule or the other, never both: +a policy that requires claims bills only what the players' own claims show presented, and one that +does not bills on the decider's beacons, under a unit of its own. + +### 6.4 Amounts + +A line's **amount** is ⌊`quantity` × `price` / divisor⌋, computed in integers wide enough not to +overflow, and the invoice's `amount` is the sum of its lines' amounts. A line amount or a sum past +what a u64 holds cannot be stated, and a ledger refuses to compute such an invoice. + +### 6.5 Citations + +Each line cites the records it came from: + +- a room line: the receipts of its room whose figure of the unit is above 0; +- a delivery line: the statements of its party with a delivery line of the class that is not free, + and each acknowledgement (mode 1) they name with an entry of the class that is not free (a + retrieval class with a zero `grant` is free); +- a package line: the package receipts of its packager whose figure of the unit is above 0; +- an ad line: the receipts of its decider with at least one impression counted under the unit, and, + for `presented_impressions`, each counted impression's own claim. + +`cited` is their number, and `cites` is SHA-256 over the 17 bytes `wai:ledger-cites` (ASCII) and +0x01, followed by each cited record's `kind` and identity, in ascending order of (`kind`, +identity). Every cited record is in `records`. A buyer recomputes a line from the records it +cites alone, and shows one line to an auditor with its records and nothing else. + +### 6.6 Energy + +Each record that states an energy figure states it once, in one **scope**: + +| scope | figures | +|---|---| +| 1 `room_hops` | the per-hop records named by the room receipts (each restated by its receipt, live-metering §4.5) | +| 2 `delivery_hops` | the per-hop records named by the statements and by no room receipt of the invoice | +| 3 `packaging` | the package receipts' `joules_micro` and class | +| 4 `ad_decisions` | the ad receipts' `joules_micro` and class | + +For each scope, `unmetered` counts its figures that are the unmetered marker, which are in no row +and **MUST NOT** be presented as zero joules. For each scope and each tag among its metered +figures there is one **row**: `figures`, their number; `joules`, their sum; and `max_ppm`, the +largest `relative_ppm` among them for tags 4 and 5, which carry a declared uncertainty, and 0 for +tags 2 and 3, which do not. When each figure's error is within its declared relative uncertainty, +the sum's is within `max_ppm` of the sum. A row never sums figures of two classes, and no row sums +two scopes: a relay's counter reading, a packager's model and a decider's estimate are three rows, +since a sum of a model and a counter reading is neither. A row whose `tag` is 0 is refused +`classes_summed`, and so is a row whose `joules` is the sum of the figures of its class and of at +least one other class of its scope, while not the sum of its own (live-metering §4.5's rule). A +scope's energy is **whole** when its `unmetered` is 0 and it has a row, **partial** when it has an +unmetered figure, and **none** when it has no figure; a partial total **MUST NOT** be presented as +the scope's energy. + +### 6.7 Digest and signature + +The **digest** of an invoice is SHA-256 over its bytes before `sig`. Its signature is Ed25519 by +`seller` over the 19 bytes `wai:ledger-invoice` (ASCII) and 0x01, followed by the bytes the digest +covers, verified strictly (SPEC §7 *Signatures*). + +### 6.8 Reading and rules + +A reader reads exactly the bytes it is given as one invoice, as §4.3 reads a tariff with `WLI1` +for `WLT1` (`truncated`, `malformed`, `unknown_version`), and refuses as `classes_summed` an +invoice with a row of tag 0. It refuses as `invoice_invalid` one that breaks any of these: + +- `start` is below `end`; `currency` is three capitals `A`–`Z`; +- each list is in the strictly ascending order §6.1 gives it; each record's `kind` is 1 to 7; +- each line's `source_kind` is 1 to 4, its `unit` a unit of its kind (§6.3), its `quantity` and + `cited` above 0; +- each row's `scope` is 1 to 4, its `tag` 2 to 5 and its `figures` above 0. + +Then the signature (§6.7): `bad_key`, `bad_signature`. + +### 6.9 Chains + +The invoices of one tariff form a **chain**: each names in `prev` the invoice before it, the first +names zero, and their periods do not overlap. A ledger keeps one chain per tariff. No record is in +the `records` of two invoices of a chain: a record billed once is billed never again, whatever +period a later invoice states, and a package receipt, which states no time, is billed in the +invoice that first holds it. + +## 7. Verification + +### 7.1 Inputs + +A verifier is given the **tariff**, the **invoice**, the invoice its `prev` names (the +**previous**, or none), and the **sources**, a list. The ledger's verification document holds them +as one JSON object: `tariff` and `invoice` (hexadecimal), `previous` (hexadecimal or `null`) and +`sources` (§5.1). + +### 7.2 Steps + +It runs these steps and stops at the first refusal, which it reports with the reason and where: + +1. **The tariff** (§4.3), at `tariff`, or `tariff` and `seller` or `buyer` for a signature: + `truncated`, `malformed`, `unknown_version`, `tariff_invalid`, `bad_key`, `bad_signature`. +2. **The invoice** (§6.8), at `invoice`: `truncated`, `malformed`, `unknown_version`, + `classes_summed`, `invoice_invalid`, `bad_key`, `bad_signature`. +3. **The binding**, at `invoice`: its `tariff` is the tariff's digest, and its `seller`, `buyer` + and `currency` the tariff's: otherwise `tariff_mismatch`; its period lies within the tariff's: + otherwise `outside_tariff`. +4. **The previous**, at `previous`, when `prev` is not zero: an invoice whose digest is `prev` is + given (`record_missing`); it reads and its signature verifies (§6.8, with those reasons); its + `tariff`, `seller` and `buyer` are the invoice's (`tariff_mismatch`); and its `end` is at most + the invoice's `start` (`invoice_overlap`). +5. **Each source**, in order, at `source` and its index: it is verified as §5.1 says, a refusal + being that extension's reason with that extension's place after the index (an `ads` gap is + `source_gap`, and a source that does not read `malformed`); then it is in the scope (§4.4, `outside_scope`); a room receipt or a statement + that states a tariff states this one (§3, `tariff_conflict`); and it is in the invoice's period + (§5.3, `outside_period`). +6. **Across sources**, at `source` and the index of the later: a head contributed twice + (`record_duplicate`); a receipt, statement or ad receipt overlapping an earlier one (§5.3, + `record_overlap`); and a record that is in the previous invoice's `records` + (`record_rebilled`). +7. **The figures**, at `invoice`, recomputed from the sources as §6.2 to §6.6 compute them and + compared in this order: + 1. a line whose source and unit no source gives, or that states a larger quantity than the + sources give: `quantity_not_recorded`, a quantity no signed record covers; + 2. a row that §6.6 calls summed across classes: `classes_summed`; + 3. a line whose `cited` or `cites` is not the records it came from: `citation_mismatch`; + 4. any other difference (the records, a line missing, a smaller quantity, a price, an amount, a + row, `unmetered`, the `amount`): `invoice_mismatch`. + +A figure that cannot be held in its field is a difference. + +### 7.3 Outcome + +- **refuse**: the reason and where. +- **accept**: the invoice's digest; the tariff's digest; the currency and the period; the number of + records; each line with its source kind, source, unit name, quantity, price, amount and its cited + records; each energy row by scope and class label with its figures, joules and `max_ppm`; each + scope's unmetered figures and coverage; the amount; and the reports of §8. + +## 8. Reports + +The reports are computed from the verified sources, as the lines are, and are stated with every +accepted invoice. They are not signed: anyone holding the sources computes them, and a verifier +compares them. + +### 8.1 Ad decisions against presentation claims + +For each `ads` source, in order: its room, whether its policy requires claims, and each receipt in +its list's order, with its identity, decider, `cue` and `session`, and each impression's `slot`, +`ad_id`, `price_micros` and `quartiles_reached`, whether it is **billable** (the decider's beacons: +a quartile of at least 1), whether it **counts**, and its **verdict**: under a policy that requires +claims, `presented` or the reason its own claim did not count, `claim_absent` among them +(ad-presentation §9.2); under one that does not, `beacons`. Then the receipt's billable impressions +and their spend, `budget_spent_micros`, the seller's figure; and its counted impressions and their +spend, the presented spend (ad-presentation §9.3), the buyer's. Then, over the source: the +billable and the counted impressions, the billable and the counted spend, and, for each verdict +other than `presented`, the billable impressions that did not count with it. + +So a seller's receipts and a buyer's claims are reconciled from the same signed bytes: what the +seller billed, what the players presented, and why each impression that was billed and did not +count did not. + +### 8.2 Delivery by party + +For the `delivery` sources together, each party in ascending order of key: the digests of its +statements; their `currency` (the stated one, or `null`); the acknowledged objects and bytes of +each class; the free retrieval bytes; the declared declarations, objects and bytes (settlement §4), +never billed; the sum of its statements' own `amount`s; the sum of the invoice's lines of the party; +and its energy rows and unmetered figures over its per-hop records. + +Then the **overlaps**: for each pair of acknowledgements (mode 1) of two different parties, signed +by one downstream key, whose windows overlap, and each content hash that an item of each names with +byte ranges that share bytes, one overlap. It states the content hash, the downstream key, the bytes +shared, summed over every such pair of items, and the two parties with their acknowledgements' +digests, the lower party first. The overlaps are in ascending order of (content hash, downstream, +first party, first record, second party, second record). An overlap is billed by both parties, since each was acknowledged; it is what a buyer +using several delivering parties disputes with them, and the report names the signatures it rests +on. + +## 9. The reference ledger service + +`wai/wai-ledger` runs the ledger as an HTTP service over a directory it owns: + +| request | does | +|---|---| +| `GET /v1` | the service, its seller key, its limits | +| `GET /v1/health` | `ok` | +| `POST /v1/tariffs` | `{"tariff": hex}`: a tariff whose seller is the service's key, both signatures verified (§4), kept by its digest | +| `POST /v1/sources` | `{"tariff": digest, "source": {...}}`: the source verified (§7.2 steps 5 and 6 against what the tariff's chain holds), kept by its heads | +| `POST /v1/invoices` | `{"tariff": digest, "start", "end"}`: the invoice of the period over the sources held and not yet billed, chained, signed and kept, with its outcome (§7.3) | +| `GET /v1/invoices/{digest}` | the invoice, its outcome and its verification document (§7.1) | +| `POST /v1/verify` | a verification document: its outcome | + +Each refusal names its reason and place. The service writes every file whole (to a temporary name, +synced, then renamed), holds a lock on its directory so that one process writes it, and recovers +each tariff's chain and held sources from the directory when it starts. A buyer fetches an +invoice's verification document and checks it with any verifier of this extension, the service's +own not among them. + +## 10. Reasons + +The codes this extension adds, each with where it is raised: + +| reason | raised at | +|---|---| +| `tariff_invalid` | §4.3, §7.2 step 1 | +| `invoice_invalid` | §6.8, §7.2 step 2 | +| `tariff_mismatch` | §7.2 steps 3 and 4 | +| `outside_tariff` | §7.2 step 3 | +| `invoice_overlap` | §6.9, §7.2 step 4 | +| `source_gap` | §5.1, §7.2 step 5 | +| `outside_scope` | §4.4, §7.2 step 5 | +| `tariff_conflict` | §3, §7.2 step 5 | +| `outside_period` | §5.3, §7.2 step 5 | +| `record_duplicate` | §5.2, §7.2 step 6 | +| `record_overlap` | §5.3, §7.2 step 6 | +| `record_rebilled` | §6.9, §7.2 step 6 | +| `quantity_not_recorded` | §7.2 step 7 | +| `citation_mismatch` | §6.5, §7.2 step 7 | +| `invoice_mismatch` | §7.2 step 7 | + +A verifier of this extension also raises `truncated`, `malformed` and `unknown_version` for a +tariff or an invoice that does not read, and `malformed` for a source that does not read (§5.1); `bad_key` and `bad_signature` for their signatures; +`record_missing` (live-metering §9) for a previous invoice not given; `classes_summed` +(live-metering §9) for a row over classes; and, at a source, every code its extension's verifier +raises: live-metering §9 and the codes it lists, settlement §11 and the codes it lists, packaging +§12, and live-ads §10 with ad-presentation §11. + +## 11. Conformance + +[`live-conformance/ledger/`](../live-conformance/ledger/) holds the corpus, in one file, +`vectors.json`, written by `wai_receipt_ledger_vectors gen` from fixed seeds and from sources taken +from the corpora of the extensions it reads (`live-conformance/metering/`, +`live-conformance/settlement/`, `live-conformance/ads/`, `live-conformance/presentation/` and +`pack-conformance/packs/`), with delivery statements of two parties signed for it. The sources are +held once, by name, and each vector is a verification document (§7.1) naming its sources, with the +outcome §7.3 gives it: + +- **Accepted**: an invoice of one day billing a lesson room, two delivering parties and a viewer + that forwarded sealed objects under no tariff of its own, a package, ads under a policy that + requires claims and under one that does not, with both reports, one viewer acknowledging one + object from both parties among the overlaps; the next invoice of its chain, billing a relay's + session whose energy is unmetered, and a second package; and an invoice whose room hops are of + three classes, one row each. +- **Refused**: each reason of §10 at least once, each vector a control of one defect, and a refusal + of each source kind by its own extension. + +`wai_receipt_ledger_vectors verify` and the test `tests/receipt_ledger.rs` check that the file +regenerates byte for byte, that `wai-rs` gives every vector its outcome, that every reason of §10 +refuses a vector, and that §10 lists exactly the reference's codes. +`live-conformance/receipt_ledger_verify.py` is the second implementation, written from this text: +it shares no code with `wai-rs`, verifies signatures with WAI's strict rule written from RFC 8032 +(`claim-conformance/_ed25519.py`), verifies each source with that corpus's own second +implementation (`live_metering_verify.py`, `settlement_verify.py`, `live_ads_verify.py` with +`ad_presentation_verify.py`, and `pack-conformance/pack_verify.py`), recomputes every line, +citation, row and report itself, gives every vector the same outcome, and requires every mutated +copy of the corpus to fail. + +## 12. Security and privacy considerations + +- **Who signs what.** The buyer signs the prices and the scope; each record's own party signs its + figures; the seller signs the invoice. A seller cannot bill a unit at a price the buyer did not + sign, a record of a party the buyer did not name, or a quantity no record states: the verifier + recomputes every line. +- **Double billing.** A head is billed once in an invoice (`record_duplicate`), one session, one + statement period and one viewer's break once (`record_overlap`), and a record once in a chain + (`record_rebilled`). A verifier given one invoice checks it against the one before it; a buyer + holding the chain checks every link. +- **What the figures rest on.** An invoice is as true as its records: a delivery line rests on + acknowledgements, so a party and a downstream key that collude can bill bytes that never moved, + as settlement §13 says; a room line rests on per-hop records and the room's events; an ad line + under claims rests on players' session keys. The reports name the signatures each figure rests + on, so a dispute is about records, not about logs. +- **Energy.** A row states figures as their parties signed them, with their class: it is + attributable and its sum recomputable, not true (energy-measurement §5). `max_ppm` bounds the + declared part of a sum's uncertainty and says nothing of a class's undeclared systematic error. +- **What an invoice reveals.** Its records list names every record by identity, and its lines name + rooms, parties, packagers and deciders. A verification document holds the records themselves: + acknowledgements name downstream keys, and ad receipts name sessions. They travel between the + seller, the buyer and an auditor they choose; the service serves an invoice's document by its + digest and lists nothing. diff --git a/wai/live-conformance/ledger/README.md b/wai/live-conformance/ledger/README.md new file mode 100644 index 000000000..74317b259 --- /dev/null +++ b/wai/live-conformance/ledger/README.md @@ -0,0 +1,61 @@ +# live-conformance/ledger — the receipt ledger + +The corpus of [`extensions/receipt-ledger.md`](../../extensions/receipt-ledger.md) §11, in one +file, `vectors.json`: + +- `sources`: the sources the vectors bill from, by name, each a source document of §5.1; +- `vectors`: verification documents (§7.1), each a `tariff`, an `invoice`, the `previous` + invoice or `null`, and the names of its `sources` in order, with the `outcome` §7.3 gives it; +- `keys`: the seller's, the buyers' and the two delivering parties' public keys. + +**Ledger equivalence.** For each vector, a conforming verifier given its tariff, its invoice, its +previous invoice and its sources, resolved by name, reaches the outcome the vector states: + +- `refuse`: the `reason` and where, `at`: `["tariff"]`, `["tariff", "seller"]` or `["tariff", + "buyer"]`, `["invoice"]`, `["previous"]`, `["source", k]` for source `k`, or `["source", k, …]` + followed by the place the source's own extension gives (live-metering §5.1, settlement §8.2, + packaging §9.1, live-ads §9.2); +- `accept`: the invoice's `digest`, its `tariff`, `currency`, `start` and `end`, the number of + `records`, each line (its source kind, source, unit, quantity, price, amount and the records it + `cites`, each `[kind, identity]`), each energy row (scope, class, figures, joules and + `max_ppm`), the `unmetered` figures and `coverage` of each scope, the `amount`, and the + `reports` of §8: `ads`, one per ads source, and `delivery`, the parties and their overlaps. + +The accepted vectors: + +- `a_day`: one day's invoice over a lesson room, two delivering parties (one viewer acknowledges + part of one object from both, in windows that overlap; one party serves a fetch free; the other + declares delivery to viewers with no key), a viewer that forwarded sealed objects and states no + tariff of its own, a package, ads under a policy that requires presentation claims and under one + that does not; +- `the_next_invoice`: the next invoice of the chain, over a relay's session whose hop is + unmetered (its room energy is partial) and a second package; +- `room_hops_of_three_classes`: a room whose hops are a model, a counter and an estimate, under a + tariff of its own: one row each. + +Every other vector is refused, a control of one defect: each reason of §10, the reused codes, and a +refusal of each source kind by its own extension. + +**Provenance.** Generated here: `wai_receipt_ledger_vectors gen` writes `vectors.json` from fixed +seeds (the seller, the buyers, the two delivering parties, their viewers and objects) and from +sources taken unchanged from the corpora of the extensions the ledger reads: rooms and their +receipts from [`metering/`](../metering/) (`lesson`, `mixed_classes`, `receipt_bad_signature`, +`relay_recorded`), the forwarding viewer's statement and the room its hop records name from +[`settlement/`](../settlement/), ad rooms from [`ads/`](../ads/) (`live_break`, `gap`) and +[`presentation/`](../presentation/) (`video_rule`, `ceiling_exceeded_by_presented`), and the +packages `video-base-01` and `video-staged-01` of +[`pack-conformance/packs/`](../../pack-conformance/packs/). When one of those corpora is +regenerated, so is this one. Nothing here was fetched from outside the repository. + +Regenerate and check: + + cd wai/wai-rs + cargo run -q --no-default-features --features receipt_ledger --bin wai_receipt_ledger_vectors -- gen ../live-conformance/ledger + cargo run -q --no-default-features --features receipt_ledger --bin wai_receipt_ledger_vectors -- verify ../live-conformance/ledger + cargo test -q --no-default-features --features receipt_ledger --test receipt_ledger + python3 -I ../live-conformance/receipt_ledger_verify.py ../live-conformance/ledger + +The second implementation, `receipt_ledger_verify.py`, shares no code with `wai-rs`. It verifies +each source with the second implementation of that source's own corpus, recomputes every line, +citation, row and report, requires every mutated copy of the corpus to fail, and requires the corpus +to catch it with each of its own checks dropped. diff --git a/wai/live-conformance/ledger/vectors.json b/wai/live-conformance/ledger/vectors.json new file mode 100644 index 000000000..e207bfc35 --- /dev/null +++ b/wai/live-conformance/ledger/vectors.json @@ -0,0 +1,3165 @@ +{ + "corpus": "wai.delivery.ledger", + "extension": "extensions/receipt-ledger.md", + "keys": { + "seller": "c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c6", + "buyer": "63fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61", + "buyer_m": "98f25ea3a2c247f29e94b9280ebf10b50c1a7412b49a0b559a2c131c0b87ba01", + "cdn_a": "b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83", + "cdn_b": "123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3" + }, + "sources": { + "ads_gap": { + "kind": "ads", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101", + "pt0_ms": 0, + "receipts": [], + "pods": [], + "claims": [], + "creatives": [], + "issuers": [] + }, + "ads_live_break": { + "kind": "ads", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000809698000000000000030a0008006b69636b2d6f66668227a302dafb2d67f492bc8c59c4c8be3730992537f7aa3a3593be4f67e0c8115c4191b1797316e68347a85dfe26df2369399eb791fdf4d9fc0e3a5b9fc40409", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000dbfd9310641cf0dab6702574cd3ce6d0f9b22de2cecc880a9c7c7e16c199d369008793030000000000020a00016500000000000000002110cd2b55e39805a9ef77c10010df2b73c5af40395d20e98b38da55f8f5503752dafb262366db255fbdafca2142559f8cc27535563da2ada9e5ac771d07aa01", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90200000000000000d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0804a5d050000000000020a00026500000030750000004dbb2cf538b422db35d234237f444df08054ecd2acf5b82c39dc9501e5f6790aa1ceb6d46cb8f5e77aba1c0d7cfb7e74d6a06c6f23644e8443f2cef9981d3504" + ] + ], + "policy": "57415031010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b41602370230750000a00f000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732fe82893ff962214057b32844a6bc521b023510372b049919c5cfce297eaab5fa18a8e0d4e0529b3b883416b5d576ebebd59d31636d3668bcce973cd800372d101", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "bbff84c09aac7224f8ccf1a1d285523a3c8c95619cb72f83fefa925d8ccf4e45", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 3 + } + ], + "merkle_root": "59bc8fe1a61b55fe0880a07fe2613a70bb3ec2eacf622790b67e5285d145152c", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 1200, + "energy_provenance": "ModelBased", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "33e44911a03df215447afca25b44ff24d6fa2682905aab4178355269694fcd7591e676001be8d95e2befd704f51c6fc1e957b42ef560d9cd7bb5d5bc80603c0e" + }, + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "0f041639e63167e41cc74c151102c01b", + "start_offset_ms": 12000, + "format": 2, + "asset_list_sha256": "0c1b410693e1a2fb79b2c2d8c0bb3ec16baa450f8a4936464675dc2c1ac768a7", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 1930000, + "pod_hash": "937792d49f963c64dd84eed6c4fe3ba3fd2c748f0e4d20669d12162ce685c9e2", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + } + ], + "merkle_root": "70c4ff5e864102c0a890a33c5e4b340cbb9f4d2f1e3d41a870bf36a6b1968d09", + "budget_spent_micros": 40000, + "work_decide_ops": 3, + "joules_micro": 900, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 30000, + "window_us": 2000000 + }, + "energy_counter_filtering": "Unknown", + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "506ea6b499543a9e55efb78bd7f5f60595dc49f7bfc066b504f59989cd2cfeeca26efcb4a1b809ec2fd17a11bd0a43b5d0b31a701ec9e1c72e363a928e4dc602" + } + ], + "pods": [ + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ], + [ + { + "ad_id": "ad-a", + "creative_hash": "8881822d8d0d16d1758b8126aae6d6ac86f261421cd1bfcc93a2b34b71dcfc60", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-b", + "creative_hash": "ce4b66aed535d3670ebe49d5f82c49652698fc3cb398102571367a797e48d814", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-c", + "creative_hash": "a93cd1b5d65711becc86a2bd625830b6ac463ac40e9a32bc67a40177df1c4906", + "duration_ms": 20000, + "price_micros": 25000 + } + ] + ], + "claims": [], + "creatives": [], + "issuers": [] + }, + "ads_past_ceiling": { + "kind": "ads", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff01000000000000004555520000000042d93313a0f5f3922a482385164cf6113495f0c4f36a984e1bcb89aa993c92da33b7fcb1736b6fc20e987e706e5378ca83d48417a52f98eafd2d56be6ab1200d" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000000e27070000000000020a0004150000003075000000aa98625a6b3df58a538960d1a551c6f4f317841226a577f62f86c0242ba4e5d31565d9c76b65ff9ca3e4c59c9a5a6b3d77c40979a011cfb2f93baddc9dfe5706", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa000e270700000000000126005741423101e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa01527597e53dd1c817047d50eef49343e4757ae9f75c18b0f81f53176abd2a2caa66f6ad365e0b6937b20f77080aff5cb52ebe439bca83f5c5b07d4657912c8308" + ] + ], + "policy": "57415031020cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc11362735de4a5208fe9859cc9a0253cf0bbe757c1d3abaee1de54272857fde9f02000000000000000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f010100000000b03ddf9a5063ad40716b05e8f79979bf5291fd9b5980c3b92bc5cb0bf68ddd1b2ba616fa5b46fc703d495bf3b8cad76aaf41104ff4145cc2848f3db6693c9908", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa", + "session": "9857427973585b59349ee607c2b3d426", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "0be5a087ca49212e28c35b36c55b4a0d71dc64e7b2098e5a8dbbd1ed1faba2d1", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 1000000, + "pod_hash": "d9775473b182dce786ae602bd492a5954fc13a3ed48abc6cb1d99dfcad7db188", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-w", + "creative_hash": "f567fd7b6ecdddbdca42eb10f6c5f344b7e0851efdcb8afcdad1532677f55f88", + "duration_ms": 15000, + "price_micros": 600000, + "quartiles_reached": 5 + } + ], + "merkle_root": "61774a76eb8a8724fa50422ed01ed974b795fe97d176ff5af3951cc3474c4d6d", + "budget_spent_micros": 600000, + "work_decide_ops": 1, + "joules_micro": 0, + "energy_provenance": null, + "grant": { + "grant_hash": "11362735de4a5208fe9859cc9a0253cf0bbe757c1d3abaee1de54272857fde9f", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 1 + }, + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "91e50a2bdcd6b16ffa39128b335bb8e19f3849d4e5035938532bb567dde7514a21d2b47169c303af3bfc3aca4fbedd5dd7648918edbae456ff4a4026b4418004" + }, + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa", + "session": "e7832a57261de786e36cd8b57cc023ee", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "0be5a087ca49212e28c35b36c55b4a0d71dc64e7b2098e5a8dbbd1ed1faba2d1", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 1000000, + "pod_hash": "d9775473b182dce786ae602bd492a5954fc13a3ed48abc6cb1d99dfcad7db188", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-w", + "creative_hash": "f567fd7b6ecdddbdca42eb10f6c5f344b7e0851efdcb8afcdad1532677f55f88", + "duration_ms": 15000, + "price_micros": 600000, + "quartiles_reached": 5 + } + ], + "merkle_root": "61774a76eb8a8724fa50422ed01ed974b795fe97d176ff5af3951cc3474c4d6d", + "budget_spent_micros": 600000, + "work_decide_ops": 1, + "joules_micro": 0, + "energy_provenance": null, + "grant": { + "grant_hash": "11362735de4a5208fe9859cc9a0253cf0bbe757c1d3abaee1de54272857fde9f", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 1 + }, + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "674661164a9b4e5eda3303638a3fc52071f07da3ba1c0bf40bc131eb475dfb8b0bc8adf60389523c3dc84cf83553b8b5196f6b0b9363377c70120b4065582700" + } + ], + "pods": [ + [ + { + "ad_id": "ad-w", + "creative_hash": "f567fd7b6ecdddbdca42eb10f6c5f344b7e0851efdcb8afcdad1532677f55f88", + "duration_ms": 15000, + "price_micros": 600000 + } + ], + [ + { + "ad_id": "ad-w", + "creative_hash": "f567fd7b6ecdddbdca42eb10f6c5f344b7e0851efdcb8afcdad1532677f55f88", + "duration_ms": 15000, + "price_micros": 600000 + } + ] + ], + "claims": [ + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa9857427973585b59349ee607c2b3d42600f567fd7b6ecdddbdca42eb10f6c5f344b7e0851efdcb8afcdad1532677f55f887d4a22c530e172ad6d94fb1293e6265d3787a6283bccbeb16aea8c335eedc85301ffffffff0500b66f42322e52718d08a81552e72aca223d88bf8445137586d93f168ee63db4d347fc7e7df25682a8cd9b085c73337203dc01bbdb31391101bb48294ac55a6afb8cf1a5469dc5caf745695f22864330f3508d7526e49b72cf9c85ab5e267fcab402cd6f8e2c2e4529f918cc17308c7a78e63313823823f3ddeb22c96dcb560727395db5a3af9985ef25a181153dfe9063d8d23b7d1ca7b0f2d917d1cfd4306c83010000000000983a0000102701000041521389361e01b16635a847ee0413ec979aa357a4842776a18f93c9054777a581546090299738a6ca7e12b51533337d26f763cad9277fb95b65169dbf904401", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aae7832a57261de786e36cd8b57cc023ee00f567fd7b6ecdddbdca42eb10f6c5f344b7e0851efdcb8afcdad1532677f55f88101162dc0f6bc5d2507e05ad346bf9c73f0001229a8157d464eec9ae3740c45f01ffffffff0500b66f42322e52718d08a81552e72aca223d88bf8445137586d93f168ee63db4d347fc7e7df25682a8cd9b085c73337203dc01bbdb31391101bb48294ac55a6afb8cf1a5469dc5caf745695f22864330f3508d7526e49b72cf9c85ab5e267fcab402cd6f8e2c2e4529f918cc17308c7a78e63313823823f3ddeb22c96dcb560727395db5a3af9985ef25a181153dfe9063d8d23b7d1ca7b0f2d917d1cfd4306c83010000000000983a000010270100003e7b4ca5ffcf733c8d707dfdff81c07b2fc485a70035cf1b7ca52e79e87206683086baeb7e1f189d5d9c034d6b23833ba9e4113710827e12773a88aa3ffaa60b" + ], + "creatives": [ + { + "objects": [ + { + "content_hash": "a56293a5e2f4c58b299e04eccaeece2fd64a520fcf764d0391afce3ad503dd2e", + "duration_ms": 4000 + }, + { + "content_hash": "e85fbd87b131aa49cc8e8f1ddc3668eb291a7506e0838c7e4ae41384088c3f70", + "duration_ms": 4000 + }, + { + "content_hash": "4ccccf8ccd1aeb72cf044e0d718c1d2fbb6fdc0c6f1c6e0b060d58555ebc8043", + "duration_ms": 4000 + }, + { + "content_hash": "5a696e3581b9457f7b306f8e8d035e1c97e14a99ed69f5fa2dd8786e1115e20f", + "duration_ms": 3000 + } + ] + }, + { + "objects": [ + { + "content_hash": "13ea9b7e608b702a3e4fab9d28a1fa2837fbd518c005e396cbfa052fcadf7cd1", + "duration_ms": 5000 + }, + { + "content_hash": "2945e92a5c34a9348b2c143ec673a6d67d00189b9aa996cd3eb30aa1055cf777", + "duration_ms": 5000 + }, + { + "content_hash": "c827da95ea0d3661ff5a6802a9df61e784c8c493cdb7b0bcc33548806a020298", + "duration_ms": 5000 + } + ] + }, + { + "objects": [ + { + "content_hash": "e1c6857dbe1fbd3f09e790af8e7e68c13dc012fdef9f5cb4c3bed0efef37fb22", + "duration_ms": 5000 + }, + { + "content_hash": "003f859ca4d04e316a0f77f4cddf5a8f7612ce85771225188938e6f7d8a2fd45", + "duration_ms": 5000 + } + ] + }, + { + "objects": [ + { + "content_hash": "b66f42322e52718d08a81552e72aca223d88bf8445137586d93f168ee63db4d3", + "duration_ms": 3000 + }, + { + "content_hash": "47fc7e7df25682a8cd9b085c73337203dc01bbdb31391101bb48294ac55a6afb", + "duration_ms": 3000 + }, + { + "content_hash": "8cf1a5469dc5caf745695f22864330f3508d7526e49b72cf9c85ab5e267fcab4", + "duration_ms": 3000 + }, + { + "content_hash": "02cd6f8e2c2e4529f918cc17308c7a78e63313823823f3ddeb22c96dcb560727", + "duration_ms": 3000 + }, + { + "content_hash": "395db5a3af9985ef25a181153dfe9063d8d23b7d1ca7b0f2d917d1cfd4306c83", + "duration_ms": 3000 + } + ] + } + ], + "issuers": [ + { + "name": "issuer.example", + "token_type": 2, + "keys": [ + "30820152303d06092a864886f70d01010a3030a00d300b0609608648016503040202a11a301806092a864886f70d010108300b0609608648016503040202a2030201300382010f003082010a0282010100cb1aed6b6a95f5b1ce013a4cfcab25b94b2e64a23034e4250a7eab43c0df3a8c12993af12b111908d4b471bec31d4b6c9ad9cdda90612a2ee903523e6de5a224d6b02f09e5c374d0cfe01d8f529c500a78a2f67908fa682b5a2b430c81eaf1af72d7b5e794fc98a3139276879757ce453b526ef9bf6ceb99979b8423b90f4461a22af37aab0cf5733f7597abe44d31c732db68a181c6cbbe607d8c0e52e0655fd9996dc584eca0be87afbcd78a337d17b1dba9e828bbd81e291317144e7ff89f55619709b096cbb9ea474cead264c2073fe49740c01f00e109106066983d21e5f83f086e2e823c879cd43cef700d2a352a9babd612d03cad02db134b7e225a5f0203010001" + ] + } + ] + }, + "ads_presented": { + "kind": "ads", + "descriptor": "575244310198925abbec382eaa9716af19533e2da733f16f0c5e4211da533f86235b901ed496a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900406cd8e95d060040420f00000000000000100000000400000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000030870726f6475636572000101000e000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100080000000000000000000000000000000000000000000000000000000000000007646563696465720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b446b681c3acb0a952b495f6413b201a39ea15ac40bc16ef9f1fd0f5652046b64f3a198f43f5ab31d47561523c8703db71b7cb34fcc000493a220abc407180d", + "grants": [ + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000001010000000000000000000000000000000000000000000000000000000000000000000057455031010e000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000000000043ef80f50c308a0dbbbe257a608bd4cb2621e48a3a643601e4ca941c848f3d7f1efc39cd7684c498110da78b3e77d18ae45d26047bd89743b835c54583e7a100", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc5bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bf00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310108000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000000000002dbe370ef573d370c9094a346a461cbf226953b1da26196a856ebbf65858c5c4e4381fbb4fd6a2f4fe63d881f388453f6f7707b204eabfbebfaef77bf906d605", + "57524731010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcdd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200000000000000000000000000000000000000000000000000000000000000000000000000574550310100000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff020000000000000045555200000000633a9c6151bdd3a6731598ff60969d3221e79341705b24bce0aad94f1d77f940c503382d394978c630169e9531f553e06e5e382d819c7b0b1e1f1280d922180a" + ], + "key_log": null, + "chains": [ + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f9900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000ebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e915257cf0af27250e92899911ce8d7406737413b8fb3ea07fe018cb13a181b321f80f31d2d11ba7e5adea2bce42b1d928b373d5cf77bbf6ea3294dc9f096f0faaed436bd052adb36b7abb3ccdc808d12d1c23566b33ddd66e0c828ca228aa650c", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc96a3c369a83402e79ea110b2bc1f9026cd10be235e0bc7e7ebc0c8b2141b7f990100000000000000dee4d8f77b286f5d5938a0cfac6ddd652d7e080f3f14c96be89726590905651a0000000000000000001040005bf53717790addd310fcb1716ea58ff76cb693db9d9b09118167ddd0fc9098bfe55d4127973ec5ca3829b5af51616fef6c8dbbe07600e1b1cc5f7fbbcd9ffe32bdf328188dd7c6d5c3f1e1aff9d69e0354fecfd4ef30a89552e7c85e7d149d1a6c3e992702aec2bb5d9327169d00ba4ba8a5095c181cf775741e0c6a7b997b04" + ], + [ + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e900000000000000000000000000000000000000000000000000000000000000000000000000000000000e27070000000000020a0004150000003075000000aa98625a6b3df58a538960d1a551c6f4f317841226a577f62f86c0242ba4e5d31565d9c76b65ff9ca3e4c59c9a5a6b3d77c40979a011cfb2f93baddc9dfe5706", + "57455631010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddcebd931f28bc8d5f3a74572b36618b4c6ae2166b28c44789a34bcf005572020e90100000000000000e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa000e270700000000000126005741423101e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa01527597e53dd1c817047d50eef49343e4757ae9f75c18b0f81f53176abd2a2caa66f6ad365e0b6937b20f77080aff5cb52ebe439bca83f5c5b07d4657912c8308" + ] + ], + "policy": "57415031020cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b416023702000000000000000000ffff001f0068747470733a2f2f6164732e726f6f6d732e6578616d706c652f736761692f240068747470733a2f2f63646e2e726f6f6d732e6578616d706c652f6372656174697665732f010100000000090c1237e74d6f9c60a7d3c6420d55d3e1c4cc1e108d0f6ee34f14cea7bd9565dfa8002eff9a08c862749107f94672321f33909c7ec33f0840db72d78913700e", + "pt0_ms": 0, + "receipts": [ + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa", + "session": "0df27375ac69306db3c554c1316c28fa", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "33ceedc30b2797d3c8a10423893aa82fb866c4bf7ccb1e1720b0217059cb0a20", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "c4b76a31ac436d11b321bb17c1d6816363432133b14b17b0a9627ade69b0f67c", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-x", + "creative_hash": "a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-y", + "creative_hash": "9f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee3", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 4 + } + ], + "merkle_root": "d4b4f7bef7f8ded970866d3f932b55029814b09cb900682574084b10fcd2acac", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 0, + "energy_provenance": null, + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "186a6ac3a7da34c53f3ffd3c8cff10fdb139ea0d767ad20442e8f160dd7812ff11893c5ed6a5c104469273685253bbe098eb38710a0537129a646c0b9b21020b" + }, + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa", + "session": "0eb873fe6fd3d9ab69d21ec031ce36df", + "start_offset_ms": 0, + "format": 2, + "asset_list_sha256": "87f4ffb5f7346243d7cc01c7f8509da9bd0f5a0087cec3ef55afe3a416cac673", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "c4b76a31ac436d11b321bb17c1d6816363432133b14b17b0a9627ade69b0f67c", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-x", + "creative_hash": "a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-y", + "creative_hash": "9f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee3", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 2 + } + ], + "merkle_root": "45bd5bbdc3fd9e320ea185d335a7a3602792f29027ca2f41dc8bb74b4e22bd09", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 0, + "energy_provenance": null, + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "420b7cb16d8d40e7e8a21fb5b9da5f313609382419d768d94e52294772bee7a0fa25e536db09001ba9bd1c9735f8b9882e47817ad5d63dcd34bbf8b5503bd10b" + }, + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa", + "session": "f723833b66cc049001d83ca85d3e9ea3", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "33ceedc30b2797d3c8a10423893aa82fb866c4bf7ccb1e1720b0217059cb0a20", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "c4b76a31ac436d11b321bb17c1d6816363432133b14b17b0a9627ade69b0f67c", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-x", + "creative_hash": "a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-y", + "creative_hash": "9f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee3", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 5 + } + ], + "merkle_root": "6e00260218f5bbc72a0f012686eb1ad6e101c05355dfb711e214b964966fc967", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 0, + "energy_provenance": null, + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "7230bc0528392b224f821acf501094703fe43e611dbc73ed9bd02572bbe3382dbdaf243ef53d6f9092a92b04606ebdf6e48431884ec7e1383be997957d19b009" + }, + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa", + "session": "d67c9c9715765cd08d686b0b418f8f4f", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "33ceedc30b2797d3c8a10423893aa82fb866c4bf7ccb1e1720b0217059cb0a20", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "c4b76a31ac436d11b321bb17c1d6816363432133b14b17b0a9627ade69b0f67c", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-x", + "creative_hash": "a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-y", + "creative_hash": "9f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee3", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 5 + } + ], + "merkle_root": "6e00260218f5bbc72a0f012686eb1ad6e101c05355dfb711e214b964966fc967", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 0, + "energy_provenance": null, + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "227de1e0f703ca5d664882aae25c76a421064092b7de486ef31f9d9aa96b51cc2c38eda128c236d3e4c2825ef8eba3d401877ca8e23264e012fe3ba8ae9e7600" + }, + { + "form": 2, + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "cue": "e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa", + "session": "28d08ac9bb09ab45c22ff9a59e54ad9d", + "start_offset_ms": 0, + "format": 1, + "asset_list_sha256": "33ceedc30b2797d3c8a10423893aa82fb866c4bf7ccb1e1720b0217059cb0a20", + "avail_ms": 30000, + "slots": 2, + "funds_remaining_micros": 2000000, + "pod_hash": "c4b76a31ac436d11b321bb17c1d6816363432133b14b17b0a9627ade69b0f67c", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-x", + "creative_hash": "a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d", + "duration_ms": 15000, + "price_micros": 40000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-y", + "creative_hash": "9f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee3", + "duration_ms": 15000, + "price_micros": 30000, + "quartiles_reached": 5 + } + ], + "merkle_root": "6e00260218f5bbc72a0f012686eb1ad6e101c05355dfb711e214b964966fc967", + "budget_spent_micros": 70000, + "work_decide_ops": 3, + "joules_micro": 0, + "energy_provenance": null, + "grant": { + "grant_hash": "76d868a117b51d032432e7068e5a366118be713477e95c28dcd649a4b4160237", + "capability": "wai.live.room", + "joule_ceiling_micro": 18446744073709551615, + "funds_ceiling": 2 + }, + "signer_pubkey": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "signer_id": "did:key:z6MkuN7U3zjRLZbdbmWCwVY3N2tj5s9j2pAyyxLBPBJPhw5v", + "sig": "84c23ab9b71403f515515b153baf16e79ed24b8b5a95ab5994e5a5d8751467dc6d939aa52ed68f2af90628150d0ad2a31e1e96081748ddd477265a52eaeb5d00" + } + ], + "pods": [ + [ + { + "ad_id": "ad-x", + "creative_hash": "a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-y", + "creative_hash": "9f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee3", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-z", + "creative_hash": "720d603e084faee4914d5d63564d25c26d8255990f1d5dd533b0002db7f377cf", + "duration_ms": 10000, + "price_micros": 25000 + } + ], + [ + { + "ad_id": "ad-x", + "creative_hash": "a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-y", + "creative_hash": "9f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee3", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-z", + "creative_hash": "720d603e084faee4914d5d63564d25c26d8255990f1d5dd533b0002db7f377cf", + "duration_ms": 10000, + "price_micros": 25000 + } + ], + [ + { + "ad_id": "ad-x", + "creative_hash": "a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-y", + "creative_hash": "9f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee3", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-z", + "creative_hash": "720d603e084faee4914d5d63564d25c26d8255990f1d5dd533b0002db7f377cf", + "duration_ms": 10000, + "price_micros": 25000 + } + ], + [ + { + "ad_id": "ad-x", + "creative_hash": "a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-y", + "creative_hash": "9f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee3", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-z", + "creative_hash": "720d603e084faee4914d5d63564d25c26d8255990f1d5dd533b0002db7f377cf", + "duration_ms": 10000, + "price_micros": 25000 + } + ], + [ + { + "ad_id": "ad-x", + "creative_hash": "a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d", + "duration_ms": 15000, + "price_micros": 40000 + }, + { + "ad_id": "ad-y", + "creative_hash": "9f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee3", + "duration_ms": 15000, + "price_micros": 30000 + }, + { + "ad_id": "ad-z", + "creative_hash": "720d603e084faee4914d5d63564d25c26d8255990f1d5dd533b0002db7f377cf", + "duration_ms": 10000, + "price_micros": 25000 + } + ] + ], + "claims": [ + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa0df27375ac69306db3c554c1316c28fa00a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5df67d521ed37b174448cb376c7b390acf158c00ac41211e282a79db9bbcb4021e01ffffffff0400a56293a5e2f4c58b299e04eccaeece2fd64a520fcf764d0391afce3ad503dd2ee85fbd87b131aa49cc8e8f1ddc3668eb291a7506e0838c7e4ae41384088c3f704ccccf8ccd1aeb72cf044e0d718c1d2fbb6fdc0c6f1c6e0b060d58555ebc80435a696e3581b9457f7b306f8e8d035e1c97e14a99ed69f5fa2dd8786e1115e20f010000000000983a00001027010000793a60ef660c17af1c900c9cbb3ea971e9c0270d076fad8fcdf5c35b0182e33330bfb9845c6a2fb3143429dcd7cdb4ff4b34c027f1d77e8ac3e74fa2b821040a", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa0df27375ac69306db3c554c1316c28fa019f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee3f67d521ed37b174448cb376c7b390acf158c00ac41211e282a79db9bbcb4021e01ffffffff030013ea9b7e608b702a3e4fab9d28a1fa2837fbd518c005e396cbfa052fcadf7cd12945e92a5c34a9348b2c143ec673a6d67d00189b9aa996cd3eb30aa1055cf777c827da95ea0d3661ff5a6802a9df61e784c8c493cdb7b0bcc33548806a020298030000000000a00f0000b80b01a00f0000b80b0000701701581b000088130000d007010000d650d007cac82000d6da177bbbcda7598fa4bf8fab89f143f644a15d5601652416f30eb2ae16c2c03572177fa488e3a6b518a051b06d01669ee5731da4e53a08", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa0eb873fe6fd3d9ab69d21ec031ce36df00a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d76e6b877adff9a10d2cca759cf2949071cd7f3ac92b614d8b1a46877da56b23a01ffffffff0400a56293a5e2f4c58b299e04eccaeece2fd64a520fcf764d0391afce3ad503dd2ee85fbd87b131aa49cc8e8f1ddc3668eb291a7506e0838c7e4ae41384088c3f704ccccf8ccd1aeb72cf044e0d718c1d2fbb6fdc0c6f1c6e0b060d58555ebc80435a696e3581b9457f7b306f8e8d035e1c97e14a99ed69f5fa2dd8786e1115e20f010000000000983a0000a00f010000ba67773647ff55a933eed9a79918f65e20c04760525eb54261eae1f5e0c8bfeafd088de19183ffd4debbbaeba9dc91ac4e7c9e0f6b2c1dc21433abadb86e1c00", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa0eb873fe6fd3d9ab69d21ec031ce36df019f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee376e6b877adff9a10d2cca759cf2949071cd7f3ac92b614d8b1a46877da56b23a01ffffffff010013ea9b7e608b702a3e4fab9d28a1fa2837fbd518c005e396cbfa052fcadf7cd1030000000000dc050000102701dc050000dc050000102703b80b0000e8030000e8030100000baa8b8e5b1435f3a5a5260e9aa16a330773ddd40b7b4a187f466422ad80ea06c0728951152a80a1732654794c5ad823c4095038411a2435b5f2c1e260dfb501", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aaf723833b66cc049001d83ca85d3e9ea300a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d2ff09f78cd3c4f9506aecd1cdb170cf0dbc635d7a97e0ea5c9cba1625b15b27b01ffffffff0400a56293a5e2f4c58b299e04eccaeece2fd64a520fcf764d0391afce3ad503dd2ee85fbd87b131aa49cc8e8f1ddc3668eb291a7506e0838c7e4ae41384088c3f704ccccf8ccd1aeb72cf044e0d718c1d2fbb6fdc0c6f1c6e0b060d58555ebc80435a696e3581b9457f7b306f8e8d035e1c97e14a99ed69f5fa2dd8786e1115e20f010000000000983a000010270100008123bcd4256cd79cd084154b30839a4a9b0430a5e783628dc8dc58ab8b77eb41e708c8326f366ffbeddb2a2aecb51e2318dfc891df0cba299d5599ec74a93a0e", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aaf723833b66cc049001d83ca85d3e9ea300a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d2ff09f78cd3c4f9506aecd1cdb170cf0dbc635d7a97e0ea5c9cba1625b15b27b01ffffffff0400a56293a5e2f4c58b299e04eccaeece2fd64a520fcf764d0391afce3ad503dd2ee85fbd87b131aa49cc8e8f1ddc3668eb291a7506e0838c7e4ae41384088c3f704ccccf8ccd1aeb72cf044e0d718c1d2fbb6fdc0c6f1c6e0b060d58555ebc80435a696e3581b9457f7b306f8e8d035e1c97e14a99ed69f5fa2dd8786e1115e20f010000000000983a000010270100008123bcd4256cd79cd084154b30839a4a9b0430a5e783628dc8dc58ab8b77eb41e708c8326f366ffbeddb2a2aecb51e2318dfc891df0cba299d5599ec74a93a0e", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aaf723833b66cc049001d83ca85d3e9ea3019f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee34ba5127cfca111cbe3237afa2d83d2aadb55b475b23e1743bff9cc102c636e9501ffffffff030013ea9b7e608b702a3e4fab9d28a1fa2837fbd518c005e396cbfa052fcadf7cd12945e92a5c34a9348b2c143ec673a6d67d00189b9aa996cd3eb30aa1055cf777c827da95ea0d3661ff5a6802a9df61e784c8c493cdb7b0bcc33548806a020298010000000000983a00001027010000f1c3f0b23ecf62bfca90641c49e6c2c2529635bdee07dd8cb12a1cf17e794f3712452b97375b8ad5757bac1ce89ccc59ecbb3a2ff7a970138662430a12d80207", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aaf723833b66cc049001d83ca85d3e9ea3019f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee32ff09f78cd3c4f9506aecd1cdb170cf0dbc635d7a97e0ea5c9cba1625b15b27b01ffffffff030013ea9b7e608b702a3e4fab9d28a1fa2837fbd518c005e396cbfa052fcadf7cd1771808a22abdf38cc8944c4019f15c93913e7b595c585a607e21fcd830f4dc7dc827da95ea0d3661ff5a6802a9df61e784c8c493cdb7b0bcc33548806a020298010000000000983a00001027010000f942c30a21aff85cde278f9ac8b6b7e9abb56bf24ebf854d9306dd025150b3acbda582fe002eedfaeb359ae1324491b744125138776ec694359df7e204130709", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aad67c9c9715765cd08d686b0b418f8f4f00a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d40caacc1e30b23e3628917a50e7d71172caf9974aa98539b416b9772b286d32801ffffffff0200a56293a5e2f4c58b299e04eccaeece2fd64a520fcf764d0391afce3ad503dd2ee85fbd87b131aa49cc8e8f1ddc3668eb291a7506e0838c7e4ae41384088c3f70010000000000401f0000102701000019c57219b1cd94a8232de8f4b0765a45b227ed0f61c4b18b6527f77787a25895c70ca40e5a822ff1ee7ceae9dfc8c07a7a3dbb03c68fe407153beca0f5846d06", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aad67c9c9715765cd08d686b0b418f8f4f019f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee340caacc1e30b23e3628917a50e7d71172caf9974aa98539b416b9772b286d32801ffffffff030013ea9b7e608b702a3e4fab9d28a1fa2837fbd518c005e396cbfa052fcadf7cd12945e92a5c34a9348b2c143ec673a6d67d00189b9aa996cd3eb30aa1055cf777c827da95ea0d3661ff5a6802a9df61e784c8c493cdb7b0bcc33548806a020298010000000000983a000010270100005d0350644ab1f45e307b858bc0d6172debc31a355ef298e44c846f4a52020d88372fd867edbaba1c0710d1d6c28def9dbe96a134b5dd3ef1dcdfca7c7d499308", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aad67c9c9715765cd08d686b0b418f8f4f019f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee340caacc1e30b23e3628917a50e7d71172caf9974aa98539b416b9772b286d32801ffffffff030013ea9b7e608b702a3e4fab9d28a1fa2837fbd518c005e396cbfa052fcadf7cd12945e92a5c34a9348b2c143ec673a6d67d00189b9aa996cd3eb30aa1055cf777c827da95ea0d3661ff5a6802a9df61e784c8c493cdb7b0bcc33548806a020298020000000000a00f0000102701401f0000581b00001027010000fb2a7d521a2c1d6e7ff97d35642f27fcdc3462aedcfcecb7988e041d301c9cffa40fe039fdaefd2b036bb04c58a49caa8ad73c288f7f5130a69a574ffd35f10b", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aad60faab986417f8eb4c7665128a7a0c800a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d134ff5aa797eda39e08db347ecbb913720755d5d9fdfa7192b4333651ff3b8bb01ffffffff0400a56293a5e2f4c58b299e04eccaeece2fd64a520fcf764d0391afce3ad503dd2ee85fbd87b131aa49cc8e8f1ddc3668eb291a7506e0838c7e4ae41384088c3f704ccccf8ccd1aeb72cf044e0d718c1d2fbb6fdc0c6f1c6e0b060d58555ebc80435a696e3581b9457f7b306f8e8d035e1c97e14a99ed69f5fa2dd8786e1115e20f010000000000983a00001027010000743333bd8698d5f0c53ff121e87b4f6a31fb206ef46658e08c40759ac35270fdda66cd8d5e61b516af47f1e83de829b7f425863d1fb931b16e6f459e5c57f405", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aad60faab986417f8eb4c7665128a7a0c800a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d134ff5aa797eda39e08db347ecbb913720755d5d9fdfa7192b4333651ff3b8bb01ffffffff0400a56293a5e2f4c58b299e04eccaeece2fd64a520fcf764d0391afce3ad503dd2ee85fbd87b131aa49cc8e8f1ddc3668eb291a7506e0838c7e4ae41384088c3f704ccccf8ccd1aeb72cf044e0d718c1d2fbb6fdc0c6f1c6e0b060d58555ebc80435a696e3581b9457f7b306f8e8d035e1c97e14a99ed69f5fa2dd8786e1115e20f010000000000983a00001027010000743333bd8698d5f0c53ff121e87b4f6a31fb206ef46658e08c40759ac35270fdda66cd8d5e61b516af47f1e83de829b7f425863d1fb931b16e6f459e5c57f4", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa28d08ac9bb09ab45c22ff9a59e54ad9d00a056e7de984fa323c5e751f55f39ad8c95d2bc6ffa9eb044ba1e3ce7986a7f5d7ca33fadaa0654f9da41d85a263d6e589b0c38b8ee6cff5d3196433d3c51a4e702ffffffff0400a56293a5e2f4c58b299e04eccaeece2fd64a520fcf764d0391afce3ad503dd2ee85fbd87b131aa49cc8e8f1ddc3668eb291a7506e0838c7e4ae41384088c3f704ccccf8ccd1aeb72cf044e0d718c1d2fbb6fdc0c6f1c6e0b060d58555ebc80435a696e3581b9457f7b306f8e8d035e1c97e14a99ed69f5fa2dd8786e1115e20f010000000000983a00001027010000304f9e03a20ee09e687d0486f36e42a4c87a23fb194d7e5d682c50a3054d9a3b631ba7229531f629e1077d1b4f02597daca352d2d12aabaf4a87db942d3e440a", + "57504331010cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddce9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa28d08ac9bb09ab45c22ff9a59e54ad9d019f3a53877e3f6331996e6759c117258756f1c083736908d54368f87683eeeee37ca33fadaa0654f9da41d85a263d6e589b0c38b8ee6cff5d3196433d3c51a4e703c2010000030013ea9b7e608b702a3e4fab9d28a1fa2837fbd518c005e396cbfa052fcadf7cd12945e92a5c34a9348b2c143ec673a6d67d00189b9aa996cd3eb30aa1055cf777c827da95ea0d3661ff5a6802a9df61e784c8c493cdb7b0bcc33548806a020298010000000000983a000070170100004ea3818f20da18ec8aa90cea98cc797b90496d53b70bf80f1278887b4bf0590c02e74a89638bc82addc123e3bfebfcecf508e9492e44a1ce623875769579fc0e" + ], + "creatives": [ + { + "objects": [ + { + "content_hash": "a56293a5e2f4c58b299e04eccaeece2fd64a520fcf764d0391afce3ad503dd2e", + "duration_ms": 4000 + }, + { + "content_hash": "e85fbd87b131aa49cc8e8f1ddc3668eb291a7506e0838c7e4ae41384088c3f70", + "duration_ms": 4000 + }, + { + "content_hash": "4ccccf8ccd1aeb72cf044e0d718c1d2fbb6fdc0c6f1c6e0b060d58555ebc8043", + "duration_ms": 4000 + }, + { + "content_hash": "5a696e3581b9457f7b306f8e8d035e1c97e14a99ed69f5fa2dd8786e1115e20f", + "duration_ms": 3000 + } + ] + }, + { + "objects": [ + { + "content_hash": "13ea9b7e608b702a3e4fab9d28a1fa2837fbd518c005e396cbfa052fcadf7cd1", + "duration_ms": 5000 + }, + { + "content_hash": "2945e92a5c34a9348b2c143ec673a6d67d00189b9aa996cd3eb30aa1055cf777", + "duration_ms": 5000 + }, + { + "content_hash": "c827da95ea0d3661ff5a6802a9df61e784c8c493cdb7b0bcc33548806a020298", + "duration_ms": 5000 + } + ] + }, + { + "objects": [ + { + "content_hash": "e1c6857dbe1fbd3f09e790af8e7e68c13dc012fdef9f5cb4c3bed0efef37fb22", + "duration_ms": 5000 + }, + { + "content_hash": "003f859ca4d04e316a0f77f4cddf5a8f7612ce85771225188938e6f7d8a2fd45", + "duration_ms": 5000 + } + ] + }, + { + "objects": [ + { + "content_hash": "b66f42322e52718d08a81552e72aca223d88bf8445137586d93f168ee63db4d3", + "duration_ms": 3000 + }, + { + "content_hash": "47fc7e7df25682a8cd9b085c73337203dc01bbdb31391101bb48294ac55a6afb", + "duration_ms": 3000 + }, + { + "content_hash": "8cf1a5469dc5caf745695f22864330f3508d7526e49b72cf9c85ab5e267fcab4", + "duration_ms": 3000 + }, + { + "content_hash": "02cd6f8e2c2e4529f918cc17308c7a78e63313823823f3ddeb22c96dcb560727", + "duration_ms": 3000 + }, + { + "content_hash": "395db5a3af9985ef25a181153dfe9063d8d23b7d1ca7b0f2d917d1cfd4306c83", + "duration_ms": 3000 + } + ] + } + ], + "issuers": [ + { + "name": "issuer.example", + "token_type": 2, + "keys": [ + "30820152303d06092a864886f70d01010a3030a00d300b0609608648016503040202a11a301806092a864886f70d010108300b0609608648016503040202a2030201300382010f003082010a0282010100cb1aed6b6a95f5b1ce013a4cfcab25b94b2e64a23034e4250a7eab43c0df3a8c12993af12b111908d4b471bec31d4b6c9ad9cdda90612a2ee903523e6de5a224d6b02f09e5c374d0cfe01d8f529c500a78a2f67908fa682b5a2b430c81eaf1af72d7b5e794fc98a3139276879757ce453b526ef9bf6ceb99979b8423b90f4461a22af37aab0cf5733f7597abe44d31c732db68a181c6cbbe607d8c0e52e0655fd9996dc584eca0be87afbcd78a337d17b1dba9e828bbd81e291317144e7ff89f55619709b096cbb9ea474cead264c2073fe49740c01f00e109106066983d21e5f83f086e2e823c879cd43cef700d2a352a9babd612d03cad02db134b7e225a5f0203010001" + ] + } + ] + }, + "cdn_a": { + "kind": "delivery", + "statement": "5753533101b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8300e4ffaeea5d0600006b93b2ea5d0600030009f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b63935a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d50d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990100e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad455552002d31010000000080f0fa020000000000b4c40400000000404b4c00000000000500000000000000a4380000000000000100000000000000100e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104010000004054890000000000030001b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83015c7e9427189195ef433885dba1d68c6138b787c2ab8d8f55d8e3fb3ab6f479750200000000000000ec13000000000000660000000000000001b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83015f249b030e483d644e8967defbeb94416f11064a38db04f2cc4f7232857b2dc10300000000000000b824000000000000bc0000000000000001b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f830200000000000000000000000000000000000000000000000000000000000000000100000000000000100e00000000000000000000000000002201000000000000001faf4d6212338c121b41e475d2574e1db542fb22a953f6e065baff84beed76acb8bed3fa0e21805051b4d92d1110316862c87aead0619fe225c2aa9473cb0f", + "records": [ + { + "record": "57444131010102b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f835f249b030e483d644e8967defbeb94416f11064a38db04f2cc4f7232857b2dc100e4ffaeea5d0600001131b0ea5d060000000000000000000000000000000000000000000000000000000000000000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000ec13000000000000516f48bbd36964fe0303e3cc5168613ba1bc04248987bcc62bae91263f3c69839a28ba2e0142e8011cda10e78da6ea1ef1e318fc88a11eba547c6b79909a639ab9ee22145e045192fc345ff32401a51831bbf0882de6c5dde049e46de4f6f804c92bfbc522bf800bf546192362cc0cee883000f75223f48a98641ffd49ba5401", + "items": [ + "de1fd9f932bdc0a1f660fcf9c7d312f08d367a1f75014cbd3be36a7fb7391cb200000000000000009808000000000000b1500fd3fb93a7acbf8de1822fd3b4c37517e322c004ee778a36ab8384f207680000000000000000540b000000000000" + ] + }, + { + "record": "57444131010102b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f835f249b030e483d644e8967defbeb94416f11064a38db04f2cc4f7232857b2dc1001131b0ea5d0600003e62b1ea5d060009f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b6390100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000cc10000000000000050fff384ff7943e7ce4f0bee432e9ecba125ac4d1a27e9b7c80fc88332516e72ca1d1e8d907c66b4b32b73fb9184f0d7261adcb7e54a1c0e1de748afc8eeb965f31cfe87a7544d3e1b5e1d930fb9fe11309ced5d327afe902ccc0cdbaf0497e273924e034548a7329a3e56c22aefb12546b4e6a0e55d2a6487a086b34753105", + "items": [ + "c07efe4cc9861dbc2255e2b9fcaa9e337d0ae64aba24863c4d1e46387d5f5e1a0000000000000000cc10000000000000" + ] + }, + { + "record": "57444131010102b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f835c7e9427189195ef433885dba1d68c6138b787c2ab8d8f55d8e3fb3ab6f4797500e4ffaeea5d0600001131b0ea5d060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000ec13000000000000516f48bbd36964fe0303e3cc5168613ba1bc04248987bcc62bae91263f3c69839a28ba2e0142e8011cda10e78da6ea1ef1e318fc88a11eba547c6b79909a639a020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000100e0000000000006a16872ffbeba5bfbc96bb918fd5749a1d9b58132ecd1da3e2fd19619e89e9f592650f8ed7832db21b8c2a4cd5c8da1ab4730df9f6a84822866453e603dcde862b11b1318d107500957bad204740ab22754c5748e04591d6d30343f04615ef58fa8b1519a7524c14f7b9c391c8fe41f1d3345cc8d5954b6fce10315f52dce502", + "items": [ + "de1fd9f932bdc0a1f660fcf9c7d312f08d367a1f75014cbd3be36a7fb7391cb200000000000000009808000000000000b1500fd3fb93a7acbf8de1822fd3b4c37517e322c004ee778a36ab8384f207680000000000000000540b000000000000", + "997efa05dddb22d8a58186651b41b23f036f2bdd13757826e362af6fc4b29bf00000000000000000100e000000000000" + ] + } + ], + "proofs": [], + "hops": [ + "574d48310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3eb7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f830100a493d600000000002b27da00000000405489000000000004000061a8000000000393870000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000092f50229bac008db15b4d0931d02a38a20bcd2731737fe8781bad16ae16ba246f34cb73980fe00b59c0dadfc7b9aa0365c16f37b9e2f680867d6760b53cffa03" + ], + "rooms": [ + { + "descriptor": "57524431018aa39cf8fdbd28c94250d9926290666554dcce05547d3c408401b2cf7bd7123e36949fe7258b8cb93dfde9ec7f0c53799342b7a3eba707864791d97e2a91b3f500406cd8e95d060040420f00000000000000400000000200000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000020d61756469656e63655f7365656b0001011018000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000067071c2ddc0d15a2fa3f76e5e818fb944e306291454f5301064b05e6276500313a8d4348cd589067f4dea032990479f79ce5a286e39e4a04e3d6e035c60d470c", + "grants": [ + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ea62b62ee4f376eee845a252ae553bf75403ff1a6e5f0645dc08dd0f6cadca90e00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000000010110000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000006c84b5ab5450807a4aaec12271aafb2bc633ea0da3a88d66abaaebed40963e91385cf501763f4ac70708123e101cadbb0b32a22d66075845f80012341669d0e", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3e9a91016e6f2c08c0a4bbfbdd97af879e8fb2eaf68cc260d7d79f9a3a1452cb0d00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c650000aea74949d335058ededf60f8200e9f3408e1cf5c3f06557681682a0e5c4129bac7f69934a60a34afc2c072415457edfcf948a34b2fd7fae5db649f8156d2730b", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ead1ad87c109cc52fe1a119a7a88976df697aacf5c98685c465891ff4e0d128c900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000084eaf23f0a802312e582ad3da14b1130fce3e1866b6c3f7017b2eea15e6271b36d604a536c149d25887bee3deb8d975157f58ca8ef3c7dc1f8350ca4273cc005", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3e9a1cbbe400b8a3ec50999255146da878c1e40338d518df86e64db94af877e96000000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c6500006e4c1edc6fe54e2adfe1a768cbbf3739b5c2a1b9d36eac037f2399b3b78ab255e10b89305b0dbe26e52fd32beafa6a32ed21814df05cb69bf6e37f3ab0161308", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ea62b62ee4f376eee845a252ae553bf75403ff1a6e5f0645dc08dd0f6cadca90e00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a000000000f69d16a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000054de9d3f3b650ce0aee274c2bbb454afde21d4bdc85eb37186713bdca98dc574ec30bc98a7702a9a791c3d0c8b6ea311aa5ee646172ef6149de7c503df6a1009" + ] + } + ] + }, + "cdn_a_amount": { + "kind": "delivery", + "statement": "5753533101b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8300e4ffaeea5d0600006b93b2ea5d0600030009f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b63935a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d50d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990100e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad455552002d31010000000080f0fa020000000000b4c40400000000404b4c00000000000500000000000000a4380000000000000100000000000000100e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104010000004054890000000000030001b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83015c7e9427189195ef433885dba1d68c6138b787c2ab8d8f55d8e3fb3ab6f479750200000000000000ec13000000000000660000000000000001b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83015f249b030e483d644e8967defbeb94416f11064a38db04f2cc4f7232857b2dc10300000000000000b824000000000000bc0000000000000001b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f830200000000000000000000000000000000000000000000000000000000000000000100000000000000100e000000000000000000000000000023010000000000006d2879b297cc89b3fcc01b70b317d8a3cf2effd692d90ab4d3940981c1491a5997ced34df5f3de7c56d1bef49c53b9868e44999137f8fb921a1890bd6da84f0a", + "records": [ + { + "record": "57444131010102b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f835f249b030e483d644e8967defbeb94416f11064a38db04f2cc4f7232857b2dc100e4ffaeea5d0600001131b0ea5d060000000000000000000000000000000000000000000000000000000000000000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000ec13000000000000516f48bbd36964fe0303e3cc5168613ba1bc04248987bcc62bae91263f3c69839a28ba2e0142e8011cda10e78da6ea1ef1e318fc88a11eba547c6b79909a639ab9ee22145e045192fc345ff32401a51831bbf0882de6c5dde049e46de4f6f804c92bfbc522bf800bf546192362cc0cee883000f75223f48a98641ffd49ba5401", + "items": [ + "de1fd9f932bdc0a1f660fcf9c7d312f08d367a1f75014cbd3be36a7fb7391cb200000000000000009808000000000000b1500fd3fb93a7acbf8de1822fd3b4c37517e322c004ee778a36ab8384f207680000000000000000540b000000000000" + ] + }, + { + "record": "57444131010102b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f835f249b030e483d644e8967defbeb94416f11064a38db04f2cc4f7232857b2dc1001131b0ea5d0600003e62b1ea5d060009f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b6390100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000cc10000000000000050fff384ff7943e7ce4f0bee432e9ecba125ac4d1a27e9b7c80fc88332516e72ca1d1e8d907c66b4b32b73fb9184f0d7261adcb7e54a1c0e1de748afc8eeb965f31cfe87a7544d3e1b5e1d930fb9fe11309ced5d327afe902ccc0cdbaf0497e273924e034548a7329a3e56c22aefb12546b4e6a0e55d2a6487a086b34753105", + "items": [ + "c07efe4cc9861dbc2255e2b9fcaa9e337d0ae64aba24863c4d1e46387d5f5e1a0000000000000000cc10000000000000" + ] + }, + { + "record": "57444131010102b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f835c7e9427189195ef433885dba1d68c6138b787c2ab8d8f55d8e3fb3ab6f4797500e4ffaeea5d0600001131b0ea5d060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000ec13000000000000516f48bbd36964fe0303e3cc5168613ba1bc04248987bcc62bae91263f3c69839a28ba2e0142e8011cda10e78da6ea1ef1e318fc88a11eba547c6b79909a639a020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000100e0000000000006a16872ffbeba5bfbc96bb918fd5749a1d9b58132ecd1da3e2fd19619e89e9f592650f8ed7832db21b8c2a4cd5c8da1ab4730df9f6a84822866453e603dcde862b11b1318d107500957bad204740ab22754c5748e04591d6d30343f04615ef58fa8b1519a7524c14f7b9c391c8fe41f1d3345cc8d5954b6fce10315f52dce502", + "items": [ + "de1fd9f932bdc0a1f660fcf9c7d312f08d367a1f75014cbd3be36a7fb7391cb200000000000000009808000000000000b1500fd3fb93a7acbf8de1822fd3b4c37517e322c004ee778a36ab8384f207680000000000000000540b000000000000", + "997efa05dddb22d8a58186651b41b23f036f2bdd13757826e362af6fc4b29bf00000000000000000100e000000000000" + ] + } + ], + "proofs": [], + "hops": [ + "574d48310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3eb7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f830100a493d600000000002b27da00000000405489000000000004000061a8000000000393870000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000092f50229bac008db15b4d0931d02a38a20bcd2731737fe8781bad16ae16ba246f34cb73980fe00b59c0dadfc7b9aa0365c16f37b9e2f680867d6760b53cffa03" + ], + "rooms": [ + { + "descriptor": "57524431018aa39cf8fdbd28c94250d9926290666554dcce05547d3c408401b2cf7bd7123e36949fe7258b8cb93dfde9ec7f0c53799342b7a3eba707864791d97e2a91b3f500406cd8e95d060040420f00000000000000400000000200000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000020d61756469656e63655f7365656b0001011018000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000067071c2ddc0d15a2fa3f76e5e818fb944e306291454f5301064b05e6276500313a8d4348cd589067f4dea032990479f79ce5a286e39e4a04e3d6e035c60d470c", + "grants": [ + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ea62b62ee4f376eee845a252ae553bf75403ff1a6e5f0645dc08dd0f6cadca90e00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000000010110000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000006c84b5ab5450807a4aaec12271aafb2bc633ea0da3a88d66abaaebed40963e91385cf501763f4ac70708123e101cadbb0b32a22d66075845f80012341669d0e", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3e9a91016e6f2c08c0a4bbfbdd97af879e8fb2eaf68cc260d7d79f9a3a1452cb0d00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c650000aea74949d335058ededf60f8200e9f3408e1cf5c3f06557681682a0e5c4129bac7f69934a60a34afc2c072415457edfcf948a34b2fd7fae5db649f8156d2730b", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ead1ad87c109cc52fe1a119a7a88976df697aacf5c98685c465891ff4e0d128c900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000084eaf23f0a802312e582ad3da14b1130fce3e1866b6c3f7017b2eea15e6271b36d604a536c149d25887bee3deb8d975157f58ca8ef3c7dc1f8350ca4273cc005", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3e9a1cbbe400b8a3ec50999255146da878c1e40338d518df86e64db94af877e96000000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c6500006e4c1edc6fe54e2adfe1a768cbbf3739b5c2a1b9d36eac037f2399b3b78ab255e10b89305b0dbe26e52fd32beafa6a32ed21814df05cb69bf6e37f3ab0161308", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ea62b62ee4f376eee845a252ae553bf75403ff1a6e5f0645dc08dd0f6cadca90e00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a000000000f69d16a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000054de9d3f3b650ce0aee274c2bbb454afde21d4bdc85eb37186713bdca98dc574ec30bc98a7702a9a791c3d0c8b6ea311aa5ee646172ef6149de7c503df6a1009" + ] + } + ] + }, + "cdn_a_usd": { + "kind": "delivery", + "statement": "5753533101b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8300e4ffaeea5d0600006b93b2ea5d0600030009f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b63935a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d50d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990100e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad555344002d31010000000080f0fa020000000000b4c40400000000404b4c00000000000500000000000000a4380000000000000100000000000000100e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104010000004054890000000000030001b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83015c7e9427189195ef433885dba1d68c6138b787c2ab8d8f55d8e3fb3ab6f479750200000000000000ec13000000000000660000000000000001b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83015f249b030e483d644e8967defbeb94416f11064a38db04f2cc4f7232857b2dc10300000000000000b824000000000000bc0000000000000001b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f830200000000000000000000000000000000000000000000000000000000000000000100000000000000100e000000000000000000000000000022010000000000008c14c8614b57b8576e270f2e619e57e90105c0d51d7e10f0709aeea9b7bfcdd00317ee1451b1586567c2aa021351ccd9c961dbfb5abec2214d2ddf8d6d41f50d", + "records": [ + { + "record": "57444131010102b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f835f249b030e483d644e8967defbeb94416f11064a38db04f2cc4f7232857b2dc100e4ffaeea5d0600001131b0ea5d060000000000000000000000000000000000000000000000000000000000000000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000ec13000000000000516f48bbd36964fe0303e3cc5168613ba1bc04248987bcc62bae91263f3c69839a28ba2e0142e8011cda10e78da6ea1ef1e318fc88a11eba547c6b79909a639ab9ee22145e045192fc345ff32401a51831bbf0882de6c5dde049e46de4f6f804c92bfbc522bf800bf546192362cc0cee883000f75223f48a98641ffd49ba5401", + "items": [ + "de1fd9f932bdc0a1f660fcf9c7d312f08d367a1f75014cbd3be36a7fb7391cb200000000000000009808000000000000b1500fd3fb93a7acbf8de1822fd3b4c37517e322c004ee778a36ab8384f207680000000000000000540b000000000000" + ] + }, + { + "record": "57444131010102b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f835f249b030e483d644e8967defbeb94416f11064a38db04f2cc4f7232857b2dc1001131b0ea5d0600003e62b1ea5d060009f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b6390100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000cc10000000000000050fff384ff7943e7ce4f0bee432e9ecba125ac4d1a27e9b7c80fc88332516e72ca1d1e8d907c66b4b32b73fb9184f0d7261adcb7e54a1c0e1de748afc8eeb965f31cfe87a7544d3e1b5e1d930fb9fe11309ced5d327afe902ccc0cdbaf0497e273924e034548a7329a3e56c22aefb12546b4e6a0e55d2a6487a086b34753105", + "items": [ + "c07efe4cc9861dbc2255e2b9fcaa9e337d0ae64aba24863c4d1e46387d5f5e1a0000000000000000cc10000000000000" + ] + }, + { + "record": "57444131010102b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f835c7e9427189195ef433885dba1d68c6138b787c2ab8d8f55d8e3fb3ab6f4797500e4ffaeea5d0600001131b0ea5d060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000ec13000000000000516f48bbd36964fe0303e3cc5168613ba1bc04248987bcc62bae91263f3c69839a28ba2e0142e8011cda10e78da6ea1ef1e318fc88a11eba547c6b79909a639a020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000100e0000000000006a16872ffbeba5bfbc96bb918fd5749a1d9b58132ecd1da3e2fd19619e89e9f592650f8ed7832db21b8c2a4cd5c8da1ab4730df9f6a84822866453e603dcde862b11b1318d107500957bad204740ab22754c5748e04591d6d30343f04615ef58fa8b1519a7524c14f7b9c391c8fe41f1d3345cc8d5954b6fce10315f52dce502", + "items": [ + "de1fd9f932bdc0a1f660fcf9c7d312f08d367a1f75014cbd3be36a7fb7391cb200000000000000009808000000000000b1500fd3fb93a7acbf8de1822fd3b4c37517e322c004ee778a36ab8384f207680000000000000000540b000000000000", + "997efa05dddb22d8a58186651b41b23f036f2bdd13757826e362af6fc4b29bf00000000000000000100e000000000000" + ] + } + ], + "proofs": [], + "hops": [ + "574d48310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3eb7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f830100a493d600000000002b27da00000000405489000000000004000061a8000000000393870000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000092f50229bac008db15b4d0931d02a38a20bcd2731737fe8781bad16ae16ba246f34cb73980fe00b59c0dadfc7b9aa0365c16f37b9e2f680867d6760b53cffa03" + ], + "rooms": [ + { + "descriptor": "57524431018aa39cf8fdbd28c94250d9926290666554dcce05547d3c408401b2cf7bd7123e36949fe7258b8cb93dfde9ec7f0c53799342b7a3eba707864791d97e2a91b3f500406cd8e95d060040420f00000000000000400000000200000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000020d61756469656e63655f7365656b0001011018000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000067071c2ddc0d15a2fa3f76e5e818fb944e306291454f5301064b05e6276500313a8d4348cd589067f4dea032990479f79ce5a286e39e4a04e3d6e035c60d470c", + "grants": [ + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ea62b62ee4f376eee845a252ae553bf75403ff1a6e5f0645dc08dd0f6cadca90e00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000000010110000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000006c84b5ab5450807a4aaec12271aafb2bc633ea0da3a88d66abaaebed40963e91385cf501763f4ac70708123e101cadbb0b32a22d66075845f80012341669d0e", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3e9a91016e6f2c08c0a4bbfbdd97af879e8fb2eaf68cc260d7d79f9a3a1452cb0d00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c650000aea74949d335058ededf60f8200e9f3408e1cf5c3f06557681682a0e5c4129bac7f69934a60a34afc2c072415457edfcf948a34b2fd7fae5db649f8156d2730b", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ead1ad87c109cc52fe1a119a7a88976df697aacf5c98685c465891ff4e0d128c900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000084eaf23f0a802312e582ad3da14b1130fce3e1866b6c3f7017b2eea15e6271b36d604a536c149d25887bee3deb8d975157f58ca8ef3c7dc1f8350ca4273cc005", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3e9a1cbbe400b8a3ec50999255146da878c1e40338d518df86e64db94af877e96000000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c6500006e4c1edc6fe54e2adfe1a768cbbf3739b5c2a1b9d36eac037f2399b3b78ab255e10b89305b0dbe26e52fd32beafa6a32ed21814df05cb69bf6e37f3ab0161308", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ea62b62ee4f376eee845a252ae553bf75403ff1a6e5f0645dc08dd0f6cadca90e00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a000000000f69d16a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000054de9d3f3b650ce0aee274c2bbb454afde21d4bdc85eb37186713bdca98dc574ec30bc98a7702a9a791c3d0c8b6ea311aa5ee646172ef6149de7c503df6a1009" + ] + } + ] + }, + "cdn_b": { + "kind": "delivery", + "statement": "5753533101123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be300e4ffaeea5d0600006b93b2ea5d0600030006ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e11c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d0c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea1100100acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c455552002d31010000000080f0fa020000000000b4c40400000000404b4c00000000000400000000000000442f000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000008813000000000000010000000200000000000000ec13000000000000000001020100000020bcbe0000000000030001123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3014d40b319e41553a210e99d08dc34b167bbff0c0155a69eaa2d529f046a9c3bdb0200000000000000201c000000000000900000000000000001123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3015c7e9427189195ef433885dba1d68c6138b787c2ab8d8f55d8e3fb3ab6f4797502000000000000002413000000000000620000000000000001123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3044d40b319e41553a210e99d08dc34b167bbff0c0155a69eaa2d529f046a9c3bdb0100000000000000881300000000000019000000000000000b010000000000001838be4fe82cee0762764e68606baa4a3c6a56294fc625b9e7de0c80f9b2632a960c4f66cfb9c10ac58bc0d55665151b23f952338f2482b638341905550d8f00", + "records": [ + { + "record": "57444131010202123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3000000000000000000000000000000000000000000000000000000000000000000e4ffaeea5d0600006b93b2ea5d060000000000000000000000000000000000000000000000000000000000000000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000ec13000000000000516f48bbd36964fe0303e3cc5168613ba1bc04248987bcc62bae91263f3c69839a28ba2e0142e8011cda10e78da6ea1ef1e318fc88a11eba547c6b79909a639a2f3e05878dcc8618b04e4d23c95323879020e0984f5d281d42887505647958cf3d151bab6a49c78ad4b3f67227fc893a75b6c0351521267a1ab806ff736a3804", + "items": [ + "de1fd9f932bdc0a1f660fcf9c7d312f08d367a1f75014cbd3be36a7fb7391cb200000000000000009808000000000000b1500fd3fb93a7acbf8de1822fd3b4c37517e322c004ee778a36ab8384f207680000000000000000540b000000000000" + ] + }, + { + "record": "57444131010102123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be34d40b319e41553a210e99d08dc34b167bbff0c0155a69eaa2d529f046a9c3bdb00e4ffaeea5d060080a7c9b0ea5d060000000000000000000000000000000000000000000000000000000000000000000200010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000201c0000000000004efdf42495a983ca7caff104064b28165084544e3a8aacedea97aafc437091c8922a55792a0cd833a5f2c7e0749df358e22183d37301de099d35989915b288db0400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000008813000000000000e757edb8c6ed9cb118a2a082830e8333c87751a8ad4d7ce73bd3fa8279dbc8e00cf116678c983c01fff8069d69b8376956cdcd0b9af12538c84679b7620eaa687632bd6d78c7c50d1b5f5df287a18e7605490ee28d8c07d5ae26e756499ad1c5a27db9cf0562a43bdf9abb8bcf11645a53c640ebe81a41bc4d3b9e820844560d", + "items": [ + "b1500fd3fb93a7acbf8de1822fd3b4c37517e322c004ee778a36ab8384f207680000000000000000540b000000000000c07efe4cc9861dbc2255e2b9fcaa9e337d0ae64aba24863c4d1e46387d5f5e1a0000000000000000cc10000000000000", + "439d4f11ee53990000fcdeb295c5ca120a0f12ab15d0d033bbb39d54716d368d00000000000000008813000000000000" + ] + }, + { + "record": "57444131010102123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be35c7e9427189195ef433885dba1d68c6138b787c2ab8d8f55d8e3fb3ab6f47975807a98afea5d060080a7c9b0ea5d0600000000000000000000000000000000000000000000000000000000000000000001000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000002413000000000000ff8c580b218f124e10e4e5193d3cd7b9e60f2d3e2a95700d1070176906f217295c24823bb9b5f73994876ca184b8efaa11e9115659ff59f3757f6756a8d69c7c5d9523098ece71dc289b06d8a54bcc80f1388596c9aee04c486681b6d037fe790c8d2b9b1ef2682f5afeb952c5a8f1786090586bbf459f29cb9703ab86e89108", + "items": [ + "de1fd9f932bdc0a1f660fcf9c7d312f08d367a1f75014cbd3be36a7fb7391cb200000000000000005802000000000000c07efe4cc9861dbc2255e2b9fcaa9e337d0ae64aba24863c4d1e46387d5f5e1a0000000000000000cc10000000000000" + ] + } + ], + "proofs": [], + "hops": [ + "574d48310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3e123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be30100a493d600000000002b27da0000000020bcbe000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000048301bb0a47754f2d1f05b715c2f0faa2235ec6ef3c7c65c50bfcfd35e8c7456e0f0a0ba5a7243387e3f09dde674ebd1868d87547ba3a9ab3584c09dd0c4fe0d" + ], + "rooms": [ + { + "descriptor": "57524431018aa39cf8fdbd28c94250d9926290666554dcce05547d3c408401b2cf7bd7123e36949fe7258b8cb93dfde9ec7f0c53799342b7a3eba707864791d97e2a91b3f500406cd8e95d060040420f00000000000000400000000200000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000020d61756469656e63655f7365656b0001011018000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000067071c2ddc0d15a2fa3f76e5e818fb944e306291454f5301064b05e6276500313a8d4348cd589067f4dea032990479f79ce5a286e39e4a04e3d6e035c60d470c", + "grants": [ + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ea62b62ee4f376eee845a252ae553bf75403ff1a6e5f0645dc08dd0f6cadca90e00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000000010110000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000006c84b5ab5450807a4aaec12271aafb2bc633ea0da3a88d66abaaebed40963e91385cf501763f4ac70708123e101cadbb0b32a22d66075845f80012341669d0e", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3e9a91016e6f2c08c0a4bbfbdd97af879e8fb2eaf68cc260d7d79f9a3a1452cb0d00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c650000aea74949d335058ededf60f8200e9f3408e1cf5c3f06557681682a0e5c4129bac7f69934a60a34afc2c072415457edfcf948a34b2fd7fae5db649f8156d2730b", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ead1ad87c109cc52fe1a119a7a88976df697aacf5c98685c465891ff4e0d128c900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000084eaf23f0a802312e582ad3da14b1130fce3e1866b6c3f7017b2eea15e6271b36d604a536c149d25887bee3deb8d975157f58ca8ef3c7dc1f8350ca4273cc005", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3e9a1cbbe400b8a3ec50999255146da878c1e40338d518df86e64db94af877e96000000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c6500006e4c1edc6fe54e2adfe1a768cbbf3739b5c2a1b9d36eac037f2399b3b78ab255e10b89305b0dbe26e52fd32beafa6a32ed21814df05cb69bf6e37f3ab0161308", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ea62b62ee4f376eee845a252ae553bf75403ff1a6e5f0645dc08dd0f6cadca90e00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a000000000f69d16a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000054de9d3f3b650ce0aee274c2bbb454afde21d4bdc85eb37186713bdca98dc574ec30bc98a7702a9a791c3d0c8b6ea311aa5ee646172ef6149de7c503df6a1009" + ] + } + ] + }, + "forwarder": { + "kind": "delivery", + "statement": "5753533101a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb00406cd8e95d060000c7ffdbe95d06000100cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000070490000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010001a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb01ad1ad87c109cc52fe1a119a7a88976df697aacf5c98685c465891ff4e0d128c90300000000000000704900000000000000000000000000000000000000000000208ccd96f61f23592794d78559ef3fdaac099eaee482e618ef034d16d5676c4769b93d6af6a7af4699528a2a7965bf2dee5422237403d0798af3fc447c934c01", + "records": [ + { + "record": "57444131010102a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bcebad1ad87c109cc52fe1a119a7a88976df697aacf5c98685c465891ff4e0d128c900406cd8e95d060000c7ffdbe95d06000000000000000000000000000000000000000000000000000000000000000000010001e59c70a1a524f86dbe40f3281b23c991401f21b3665ddb233d920ce593a711420000000000000000000000000000000000000000000000000000000000000000030000007049000000000000ca85c104eeeeabcac9308cd34c0bd4ae4400ea51d63dfb7963bdb5242210082d8417d84eeb3f67fe1698a5724cea17081d84277809875e82cfb8b53216376491c6a8807ce6232ebe8623698964cb9a2a7c636fb165bc4bfe7851290137c54a928836e8d16587d3a0cfbbd9b70fe099974e3c0f9d718cb99842eea9b3ba596a0b", + "items": [ + "5ec50f73b466d64695f7ce893b2cc3250efc2adff0b3d6f138cbbf7f121a33cc00000000000000000807000000000000aa5b6a2188fab7f53575e0e6091ede4c8c25d2f080df5e532181ba93d263e64600000000000000008813000000000000589c8ee2bc1586f966be6e484bcc638d3ea7532998e159699dea88ab679e1ee60000000000000000e02e000000000000" + ] + } + ], + "proofs": [], + "hops": [], + "rooms": [ + { + "descriptor": "57524431018aa39cf8fdbd28c94250d9926290666554dcce05547d3c408401b2cf7bd7123e36949fe7258b8cb93dfde9ec7f0c53799342b7a3eba707864791d97e2a91b3f500406cd8e95d060040420f00000000000000400000000200000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000020d61756469656e63655f7365656b0001011018000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000067071c2ddc0d15a2fa3f76e5e818fb944e306291454f5301064b05e6276500313a8d4348cd589067f4dea032990479f79ce5a286e39e4a04e3d6e035c60d470c", + "grants": [ + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ea62b62ee4f376eee845a252ae553bf75403ff1a6e5f0645dc08dd0f6cadca90e00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000000010110000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000006c84b5ab5450807a4aaec12271aafb2bc633ea0da3a88d66abaaebed40963e91385cf501763f4ac70708123e101cadbb0b32a22d66075845f80012341669d0e", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3e9a91016e6f2c08c0a4bbfbdd97af879e8fb2eaf68cc260d7d79f9a3a1452cb0d00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c650000aea74949d335058ededf60f8200e9f3408e1cf5c3f06557681682a0e5c4129bac7f69934a60a34afc2c072415457edfcf948a34b2fd7fae5db649f8156d2730b", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ead1ad87c109cc52fe1a119a7a88976df697aacf5c98685c465891ff4e0d128c900000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000084eaf23f0a802312e582ad3da14b1130fce3e1866b6c3f7017b2eea15e6271b36d604a536c149d25887bee3deb8d975157f58ca8ef3c7dc1f8350ca4273cc005", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3e9a1cbbe400b8a3ec50999255146da878c1e40338d518df86e64db94af877e96000000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c6500006e4c1edc6fe54e2adfe1a768cbbf3739b5c2a1b9d36eac037f2399b3b78ab255e10b89305b0dbe26e52fd32beafa6a32ed21814df05cb69bf6e37f3ab0161308", + "575247310159db1b835418262fcaebe6621da3324c7346c3999dc2b8ca6680e35434625e3ea62b62ee4f376eee845a252ae553bf75403ff1a6e5f0645dc08dd0f6cadca90e00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a000000000f69d16a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011468747470733a2f2f656467652e6578616d706c65000054de9d3f3b650ce0aee274c2bbb454afde21d4bdc85eb37186713bdca98dc574ec30bc98a7702a9a791c3d0c8b6ea311aa5ee646172ef6149de7c503df6a1009" + ] + } + ] + }, + "lesson": { + "kind": "room", + "room": { + "descriptor": "5752443101d6b5a6849f61a8bbd9d4db526a9fc1c73ef949590cdcd9b0b65ce6375262d9bd0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7200406cd8e95d060040420f0000000000170068747470733a2f2f6c6573736f6e732e6578616d706c651000000004002c0100005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f000000000004086469726563746f720101020b1a0f00000000000000000000000000000000000000000000000000000000000009706572666f726d65720301020018110000000000000000000000000000000000000000000000000000000000000861756469656e63650001010018000000000000000000000000000000000000000000000000000000000000000a636f6d706f7369746f720201020402000000000000000000000000000000000000000000000000000000000000000004067370656563680101046f707573000001000000000000100000004000000080bb0000010f056d757369630101046f70757300000100000000000080000000fe01000080bb000002000b63616d6572612d3130383002020d617630312e302e30384d2e30380b617663312e363430303238010003800268011e0096000000f40100000005d0021e00f4010000dc050000800738041e00dc050000a00f00000000000000000a63616d6572612d37323002020d617630312e302e30354d2e30380b617663312e363430303146010002800268011e0096000000f40100000005d0021e00f4010000dc05000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011e00d0070000f401000002444546527d2038c499b62ca152978601e0dfa4f2e1c5b15e1ef68449ca33b1d6dcd497037e6ce23ed0aa7b849df375b2e89955d6119b4a878fd20be449be1312c6534501", + "grants": [ + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c5200000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000101020b00000000000000000000000000000000000000000000000000000000000000000057455031011a0f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000100011568747470733a2f2f72656c61792e6578616d706c650000bea5ab08f1545cb8dc513b29a89599253fa6e205f706abf0464328c151580465de8533f866bf72b43df493570544a0255c57f275f4cab2f63c86ad7521b3e600", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000103010200000000000000000000000000000000000000000000000000000000000000000000574550310118110000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000020102011568747470733a2f2f72656c61792e6578616d706c655900086665617475726564040074727565077375626a656374400035393432353436353030343530303033326436383933663938306137646164343833616637306234653131366131306237636161346532303532353933363939e8c84e771a717476baa11cf942325fe291a1df45c01de5500df09f929cfe4afda793a413095db8ef85c0834bd20532b4e665684a44d311ada6cb0c887942fd06", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000103010200000000000000000000000000000000000000000000000000000000000000000000574550310118110000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000101011568747470733a2f2f72656c61792e6578616d706c654a00077375626a656374400032623935366261393932646264326433366438316663346134613737316237303438623733326132653336636565613266353734343832313665663536366336cfbe463d94a52e508797cf955379ea84c17812512bd8ce814c994de9acbb1b3549879f17e06a51d786bc13c4fa644f39eb0e8afc6720e5157710a4eeb338df00", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c100000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000302010204000000000000000000000000000000000000000000000000000000000000000000574550310102000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000103011568747470733a2f2f72656c61792e6578616d706c6500003869c45831770faf0563fbece44f7cbcef4cd3fbf19d2a59aa34ee7b95676c956edc96c72ed89e2a11bae6c2ac04e150e899269f76ddc766018d439764d01004", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa78a50426ccfca7ce958e1c52db484fc712687799e1ff84bc4cdd4182aad891c600000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c6500006269a123cbac9dd31be7210d27fb2ee2fdd1f062db4f8546fc4d004435da090bdf022e6e8fad6d2e1220582094847a1c7607d330d6056ae1798ec2960c6b2b0c", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa764a37268db038e6a6700c23f1e1b6ded3fd817e2faea48b1e62cf20cc65cd000000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f00000000008096980000000000ffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c650000c4cbf1a57fe4987d85f84b0d28b1a8261efce7cd6cefabbc2881e56696f83f04c65b12a6398643b5cf09c71774cea97564acd3adc6e3ce5576aa431db40de90b", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa2d8bca2005c2ba7300d28cda48ba74c2217bc1990aa7a2b25931bcc89ca580c00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff640000000000000045555200011568747470733a2f2f72656c61792e6578616d706c6500008a8b973614ae2b0d1e85469d8cffe0e9d207fbbaf0a27e1af25583ba2926f1cc38f5cd8cc3d3cf4ec191227b9242ae798b2c281b684b71f2f7c59deaf981dc03" + ], + "key_log_head": "{\"grant_max_s\":86400,\"issued_at\":\"2026-10-15T00:00:00Z\",\"keys\":[{\"compromised_since\":null,\"id\":\"room-authority\",\"key\":\"d6b5a6849f61a8bbd9d4db526a9fc1c73ef949590cdcd9b0b65ce6375262d9bd\",\"may_sign\":[\"live/room\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"take-store\",\"key\":\"0f222415114ff3e773663df797c88d6e11b094c4ffc6694581d843dbfbe8ba13\",\"may_sign\":[\"capture/store\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"relay\",\"key\":\"0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72\",\"may_sign\":[\"live/relay\",\"energy/OnChipCounter\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"compositor\",\"key\":\"a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c1\",\"may_sign\":[\"live/compositor\",\"energy/OnChipCounter\",\"energy/ModelBased\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"ingest\",\"key\":\"3549cf93c7fb47a5cee15993419720433466b527ad7a7423ccc78de33baf2cc9\",\"may_sign\":[\"live/ingest\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"meter\",\"key\":\"8be2039bc5d06e7cf9d0188c3f22c659f1687b08b69b97f50b134552c46da783\",\"may_sign\":[\"live/meter\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":\"2026-10-14T00:00:00Z\",\"id\":\"meter-old\",\"key\":\"1c5430e51de83d2adf16caf0c8ab6ffaeed421d6569611674a685117eb385180\",\"may_sign\":[\"live/meter\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"revoked\",\"until\":null},{\"compromised_since\":\"2026-10-14T00:00:00Z\",\"id\":\"relay-old\",\"key\":\"e079d6f85a4fb3fecc7f4a94cde29c757313f84e0bd726c9c9dfe640a5745931\",\"may_sign\":[\"live/relay\",\"energy/OnChipCounter\"],\"status\":\"revoked\",\"until\":null}],\"kind\":\"wai-key-set\",\"next_root\":\"fa2ec0c92dcc45c59d8ede47e5f6cbaa0f89e36186a25c7007ec718415288a8d\",\"origin\":\"https://lessons.example\",\"prev\":null,\"revoked_modules\":[],\"root\":\"93bd7020e78d0ced730d16b3df0385340bb6ad05773df662e19ec3d4735c5e5c\",\"seq\":0,\"sig\":\"412da30d6f3f35c71d6c8075a78713129497b1a28c2887990c99cc16dd26dc9c33593f894286ced846c36b597013b39e583d83ce42cb1ffa75812e9222bad104\",\"status_max_age_s\":3600}", + "chains": [ + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000d927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c52d436e9616073a533831cfbec646020b5d4dcc9d1df4b792ccaff6e7e23ee686ad90f34467ecd1ae1beee4715b6ad56a1066885f45de845b5e1254f022b9504b191eee829d5007a0391a30af95d9efc41cb6e7b9fe1bb4591e7f587b35fc12103", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720100000000000000427fb576b2ce3ae19b7f085dc335864687bcc94f64e8d47e8d7a0baab7acffc2000000000000000000104000e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5d1ee853e5c092553edc99a0cc2468a7307afd83533a3a34f401b0695da3433140315765b5e69cf9cda0aa975fff4ff243328d62ddd0d53798472165bba87b9921d60cadec42afa900b559ae469f7a3bb5870d6a180c30d368daca0aca4f43d05", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72020000000000000095ea09d688d1c33f265428db98aa2a268f391f6ffd908ab6c39843c8e591177c000000000000000000104000a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c1b9091845dcf9a7fc8e2a12e1e1b2f986888b33fb22f1b42b384cdf47ecfc33a0e053886f0bc7ed1fc288c3b8dbe6e2e009998f204f921e1ec247fc870003f99598f8216ff5cbda966841da96c98ae2a727607db2d9b08542e2fb9fce4f4bfa04", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720300000000000000ba5015a517cd3fdebc9cd865fc10df47e2e7c97a444dff51c5cfc647434a3ef6000000000000000000122400a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c100020301e449ecf9f6773169161edefbfdb034114849028534b79602cdd2538371a6b21f18b273b29fe0678f781bbf72c3d75bf17d03adc10a531d788d7e693f3858620a", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720400000000000000fc5a8acc446b029d3d479f4b66d09f53e968f32792aee1f4e278ccab6a195a4c20a10700000000000010400078a50426ccfca7ce958e1c52db484fc712687799e1ff84bc4cdd4182aad891c60c2a9846621d91f9309170b29b95e158e278002ce4cc03a2d097917836a391c028cea7fedad7bfdb6b7402c13da6e6b8c1077aa5290e1954f13ac0082da7cdc9d6ff62a798afe163a44575ce65b558f4266798d75e116d34c58f3c3ea8900907", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720500000000000000044bf302c9f47024ab231a640905353115ce057e85a04c509808e7fe9b21b254c027090000000000001040007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eeecf4a5892c8a1bef2afba730a4ec868d0830f9355d10f540b757205945c588cb507ef6ba4e6cecea09d1714d6dd41c963ca58cca289c9839eaf74eba473fc28eb2f5b6190e4da8b5fe796eca8212e2c915ec1459aa081a97de67ec5926f450608", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72060000000000000010ca0251df4b17967f3704e4d3bd4a308ad12fd640aa970e13908ee015aa985660ae0a000000000000104000a764a37268db038e6a6700c23f1e1b6ded3fd817e2faea48b1e62cf20cc65cd06dde44606fd8377d9fa0809b2b36b708de3e6119d8c1c3d1f0d675917c28c619f660a54c57820b95480c0840836278a125593043e58895bdb8e00fed6e0c1308731455a732b1baec43e8e68ee585fc2e272d3c5124e08fdb14c7183eacf33005", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7207000000000000004b49d8a56fe5947607740aef1061d356e7f6e3c83a21701e432405315b10cc5400350c000000000000104000a2d8bca2005c2ba7300d28cda48ba74c2217bc1990aa7a2b25931bcc89ca580ce433cf0201e9b443d808e4406925b6a4a90e9963c6235c742a0ecec87a59e4783ee48164e163b5b78d764f4380a58e1596c7a0b06a78cc26bbc130382bca7e5c863bf635a1f4b4b42cdcc6e788cd6a26e3f89e56af5e83b43b1d12b013282206", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720800000000000000f0719456a072c6baab714eaddccad1b7cd93fa97fe10ecf990edf381e0c1230a40420f000000000000122400e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5000101011fe5381fc3b2acb103f880c2fa9ad52b41f7e792f6dc84b320804531020b477c1dc21fb2afc6b440e441a462e0cb714eda89c7782b0e6c7f9c26f392bb57c307", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720900000000000000062e7b16ad9bfc59a985b0f3e5e68d0c6e28c73355d6be2e11cf8f945ae463ce40420f000000000000122400e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5000202032dbd24bcab531bb61e3759b44e48aa90009652aecc1ac2d66af737af13b577f47adf927eb619d29f59395164eac2f2d3a363c0e4d1847527e8d3b40da9c5ae02", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720a00000000000000fdab89363cafb8a44d66c7bd763a25da8917783ffcf4a97bfd36c70455c2307d40420f000000000000122400d927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520001000103d7b211d87104be28d03b83d15a94431fd9253524acd08eaea84a500ad9883c12cef58ca5016c10185fded407b57717ef13c5a600ed11818502271390122609", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720b00000000000000b2915db782fe866d5dbe91dca3b09c7e9d7d677045feb73cfa3e5a2881edc11b40420f0000000000001224007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee000101010e426f3883cc38a43b114bd429ecb3059f7d86df5569989b55cc45f54a44cd9e3a4a459961bb0a1af8030633d23fcbcb7d5fa39c0352ac0c760999e35b2d6908", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720c000000000000002b70c96549302cc9e63155fab6889ca46f91223f5690a2c21ab7081331acdc73002d31010000000000132200e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50002da3a459c92f7c05ffc10bfa3d65ad4387e2deb2b0152056937992ed445b4acd95eb5b93826b09f3bd959da3d8c87a7bbcd4d6f461bebbf8917e5991fb7367b0a", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720d00000000000000041812e956c700e749826b39425905b8034c109e6deda9b07dc4cc106e419b1a002d31010000000000132200e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50001cfebd12a777c322de9cbd0f5f7f47aa305d741a1887ca8eb9d5c5e88fbee77e52301a011319877a391756aef65f5fcce986d7956539612a97653fec894c4750e", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720e000000000000004a9d94ee96ec81db8f434cbf8d46940217d1ec0849ad66769beecda538455b34002d31010000000000132200d927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520001f9ecc31703ca5a1f00b6cdf9196e371c49a1cb0ab5bd5125835798e52017cf7501e0341746a3e9e81ff559f805b0de751bfea43b60edf33a7a1e808b06333b0d", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720f00000000000000e4fab4da5c6f3c166c5ce005bdc60c491c7137298872ca63fb252d8dad6c1925002d310100000000001322007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee000124288a152f94c15021ffa82d68b2c9fc1351e4b8fb4a21a345f6b85dcaa0bb65ec93c2f5d9ac406b54064316618582247c4166345156e9aeb3e390580403c00f", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b721000000000000000e8f976794e72bb3e1ac846a01f930aa7181468e136a25151275e77a34fca1c4ce09466010000000000132200a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c10002416e0b9f31fedb053ead6f7fe918a3542a9f4f715bad8522e9d77cfaa07d04dce798bb36c48ece9e60cc75deafe5a90afca0185ccf00401877717a3a5384e705", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7211000000000000007adb989ef3fdca75b2116a3c1d8ccc32f9726a370bb1fe74a778367b80ef9b13c0286b01000000000011210078a50426ccfca7ce958e1c52db484fc712687799e1ff84bc4cdd4182aad891c60182ff63588f819d513b3adf9234be70f92276eacbf740d9dd6a2887b35d67ae250874c98219fd203dd46380b790fa11523ef3d647af4bf2c29b6f767fd67d440d", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72120000000000000046d9ca416acfb977917660e77d1c025dd21fcab6b9c77a84be1ba34126ae4705c0286b010000000000112100a764a37268db038e6a6700c23f1e1b6ded3fd817e2faea48b1e62cf20cc65cd001961a1543c72a2ed7cef5e60a729057980ae0333d3ca172d0d7765ea3cb53e37869fc96d783d87f199d84e403a6590b9cee396e80d8d4a11ddf2c1ed7e1a09108", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b721300000000000000a5bc35e28e4dc80e1246f65538b5ee2d5ce1e63415726c1ab0424f4b3082ca49c0286b010000000000112100a2d8bca2005c2ba7300d28cda48ba74c2217bc1990aa7a2b25931bcc89ca580c01b85d69bae728a80312847181411cbf1b857c7c904c6e3ce393731ec05a8b902aa943ddc1177eb5a85d89d3b8188b368cbb8420bbafd9e518364260f9f7f83704", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7214000000000000009136003edf701bb862143262e21c22fef7493df56a28db3557c789a1e7cd2c4bc0286b0100000000001121007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee018afe46c99845ba0d7af81f03eb2c8c4067f34c8674cd33106729df8cfd81c48d21ea9df9ebc4662931d865c3a95683c870b3115a1383f2813967b278803c0109", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7215000000000000006f83d7703375750f5cca72fb4c1179c04f2b65202682adbdb3d67b1ea13fc91ac0286b010000000000112100e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f501d906868dc6a495c8188516e2e90a24fddd72396678bb9bf6a699921b0603cfae109e785229a8e9589bc9f8631239e41060170d7b1139b39050dbc85ad4d0ec08", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72160000000000000095a40cc34fa91bfb041448d1ceaa4d3a30716a6fcef532bfd452ff00242dbe72c0286b010000000000112100d927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520101de18404ca29dde16299566d3688d1188d8124540fd37c7df87457a4b87d94236d6ad5ba1bdaa6b861505e004378c094de1aa08a45057209e1f972f4443ee0a", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b721700000000000000ccd8e80adcaacc780695d2f91dd925f4a4e666adce7e40b7aed60d8e9fd41dd0c0286b010000000000112100a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c1017b0e0acca851520158b977db04889ec317c5b3b63399ffad2d8f1fa2a3e16a584163fe83cf09e6fc88bfd8320594a3c6d25f2979120a79eb4205f513158d4c00" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520000000000000000000000000000000000000000000000000000000000000000000000000000000080841e000000000000092500e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f501a00f00002de246b73724b5efe6fed8b8b500d8d4db72cd1a6bf1fe44a0fcbc1390d38c8b7c34efb79c4f52745b9d945ea0e09b83f2d3ea6e2ebc63ee72cc83963b457e0f", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520100000000000000b2c4d5251f98cf34d11f86821c2cd542f6bbf5152a07ad54b271f65b2530901880841e0000000000000925007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee01a00f00001bdab77a3690419feb21de999193a68bf7856807f63c37989d8b1d0d26bab830f17be4ae69e686f15b5f9f58510474fb68a294e32145e972430955bba78f5308", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520200000000000000b7091c801fbc133089d9612f76d94401ffd0199edd399f9db0beaf3e8a611d4540548900000000000008160004000000020e00727573686564207468652072756e001895215217dce05b5b18cc8f8638ea08e01ad7077de8a741852f15db966b6df81d9603c321949de5dfe7c53fbc553afe7a2439bc18def689a61e9afd2858670b", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c52030000000000000078223b1dc7500f31098e2ef4ae4f615e1eaa5a06e5f3a19c0698970771536431405dc600000000000008170004000000010f006c6f76656c79207068726173696e67003dde71eca895d2587649a3888baef3917077bd6d500d9fcef2348c70e6590bcf16d2738a9bf9a49c30b14d22203fd088c8f492169118a20b323c955172daab07", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c52040000000000000091ef0040cd22f692bad52da2aa2ee4ff3495d571373249b21f7488b583368af40024f4000000000000092100e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f502159e513cebb1af6f51733013724825fbd4b3301b94c9c31c10b4011ab35bd7e4590002d82faadb93cb25ffafd19a236c5caed905ed849d7c246177d48908450d", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c5205000000000000007ad53acd82e3e7767b6fe17bf1e5cbbefc07729bcbeeacbc71387e043bd5de8a0024f40000000000000921007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee02feb7e48b31bf62d8b7974e1c15b19f2abfb6190cdf8912b00c9970b386745e81beec63bfc20c7f8046f392aa238f96f0a41a0c8f71095c4e7405c5b21ee55900" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa78a50426ccfca7ce958e1c52db484fc712687799e1ff84bc4cdd4182aad891c600000000000000000000000000000000000000000000000000000000000000000000000000000000a02526000000000000030b000900676f6f64206c75636baba18e36da1e7d557a55c40c03a8f89ceb77a4dfee113e44680f91c25b60bf8220d0cf49ab0a9f4d1b42b793b90e596a22107b80c7db3a6095749cc3e138bf01" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa764a37268db038e6a6700c23f1e1b6ded3fd817e2faea48b1e62cf20cc65cd00000000000000000000000000000000000000000000000000000000000000000000000000000000080a8120100000000000307000500627261766f94766cf3326e298ea016d3784bb748cce1ae77e4e939b4a75c7bdb3a912978c251a3935f53acea5081479ea000604095c3ce85a53df644491cdc5a0ccec3a803" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f500000000000000000000000000000000000000000000000000000000000000000000000000000000c091210000000000000c7e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da00000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000efad5feffffff0039276d6016497f70ab9d2a5f1ff179a1070b1016d8cdbde6791792f00ada0133af241e3b99a3fca2e21369598281e46c89c41553c97855022f565312fc32a10c", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50100000000000000d7b83f99f213619227fbccadf7069c3f6cffca8e86f2aba6ffb8d15212698fa5e0a57e0000000000000c7e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0100000002d7b83f99f213619227fbccadf7069c3f6cffca8e86f2aba6ffb8d15212698fa500000000000000000200000000000000880211aa301be185e15a6c1895c2d43367360f1104ca7e72fa3c576cf0437c1b000efad5feffffff00cff0422ccfafb46431eeeec478c887b9a5609ed42f7b3b79c0b4c20ae6d87c375a6160907635a9fdd4a3990b2a4dcfdcf4ca46123c7e44dddca935c3354baf06", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f502000000000000001be10b08c6e09a1b9314f45c65bf3c9b6bd507cfa5df3f013d50657245ee48c5602a9d0000000000000c9e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da02000000021be10b08c6e09a1b9314f45c65bf3c9b6bd507cfa5df3f013d50657245ee48c502000000000000000400000000000000c6cda45663cd175aa9ba5f30289803a8059baa9924a9f919ef40d91902ca7e00000efad5feffffff01bbf572927af2d03d45aa66c8f630bda9d29429e07e09a3a3d6c9a8d933326244bec776069cf37a98f02833ca20fb02b81e7ccd6dff6d55266db73157ecb1002955cc3aa3b3d1a698be7314dd222b0b61676b727c6e3ed6b93394f4c55dcd100a", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50300000000000000602f2236c9c17b8a1cca6bae144f8f162627d27095a7256e1c5b8bc199c84fb3e0aebb0000000000000c9e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0300000002602f2236c9c17b8a1cca6bae144f8f162627d27095a7256e1c5b8bc199c84fb304000000000000000600000000000000249f88d1dcac0f7d98b53d89fdbeb22c284ffc97c90ff18a1ee27980e3ce88bd000efad5feffffff01eb6ddc57c1c32eeef5f7ff5f2a73f33e16684a4b4249d3cb0b99f72adb199a21bf6c5f49688d465cbdcc26c2dd73ed5b9ec157bf86dba5d2dc0b44e4112193a511a1fe886a482e6876c09119c470ce35e7a046f9d60df4b4085bb7c2093d9309", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50400000000000000d535c9375be68b107355dcfeaab2a78192ba09570642f09de57b7bab1438eadc6033da0000000000000cde00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0400000002d535c9375be68b107355dcfeaab2a78192ba09570642f09de57b7bab1438eadc06000000000000000800000000000000cef53df7a17ba27234381bf4fb47ad712b9f2aab80d4f837db1155d18af26a55000efad5feffffff03eb6ddc57c1c32eeef5f7ff5f2a73f33e16684a4b4249d3cb0b99f72adb199a21d75f76241ecda381c6d8d9232daab10b13803e2fe52ab86871c6bca67199fef3c6cda45663cd175aa9ba5f30289803a8059baa9924a9f919ef40d91902ca7e009d53f633fc2fae20b44ccc38918c8ac026ebd6fe90d3548fbe195fec26564bbdfa8ae5862663306a82c0680fc9ad127d5cf6304e92beae4ec0db27c4b54e8700", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50500000000000000703cab1c2158dee2277fea9a9b909df1f7926a3a56996b7db1bfbaaba40ffa29803efa0000000000000c9e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0500000003703cab1c2158dee2277fea9a9b909df1f7926a3a56996b7db1bfbaaba40ffa2908000000000000000a000000000000008883ea090e9f374f91bacaac0864af7b6dafa57c4f8a3ac0040ec7fc669f1f25000efad5feffffff01b09aef291cbbb9927cefbbb3a26f4c2d4689a03a6b1cc7f938ce9c615890e9dd16de4bf19b3cfced2d0c7afec09ffdbafe2275901f940330ff39faecfadbc6adf3a784f003d44b0153b753eb6afc1cc30c047dd373457fd21f31de384709060b" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee000000000000000000000000000000000000000000000000000000000000000000000000000000006018230000000000000c7e00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf69281000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004039d2ffffffffff00727cd67bbd0a01562d596da5eec92f54c9f9ff4c863b4f7eac3a1d9bf2c762f287a80b347596f5f98eb11463381d7729ee84dbc2afbb52d38d4172e23497d005", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee01000000000000000571c24d887ed7ee13662e1b6b5916b05446674f04a48bb9657a88aa791aa4f7802c800000000000000c7e00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf6928101000000020571c24d887ed7ee13662e1b6b5916b05446674f04a48bb9657a88aa791aa4f700000000000000000100000000000000ea1a9c44b721ca2a583fad19bca966298c4958034505c8143c6779e0b73bbd9e4039d2ffffffffff003171796be71e8f39fb36708f2ea8a5c0f9290bb7e2f029298d6df465b6b6f7f89715239ea8eb94b4b5e1747c48c82b01ea80c183879adca17efcaf3a45146204", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee02000000000000008c7c1730862c2541afc6faf68f8f1a4b34c99f6c9ea331b8be3a60e97734ac8100b19e0000000000000cbe00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf6928102000000028c7c1730862c2541afc6faf68f8f1a4b34c99f6c9ea331b8be3a60e97734ac810100000000000000030000000000000075ea44294bf1e20d670345b52e104bd360523168f8b0046b23a6bae059558c5c4039d2ffffffffff02e1a39c21b47bc7d6c78d0678749d84614817a2fb9b0a7dcbf19e940ff564df98a0a547193b22c6cc66acb6f6c926bbb61155ada07da896581acf52eaab6c2112b0c76a9550457dd487ed407b529c2f5e6df599da73b966801709654bd5991bd3ad6a930521ff1b4c63561f13a285c964df44cf4eea7f2ff5c013070e652ae001", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee03000000000000004bcc3352c9690a6b19b9aaa4301286ce746571ec9a1dfd777ce3cdb1ab47ca1e8035bd0000000000000cfe00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf6928103000000024bcc3352c9690a6b19b9aaa4301286ce746571ec9a1dfd777ce3cdb1ab47ca1e030000000000000005000000000000003935b6c803043cd12ec5bd53c252b49dfc9ca51a329fbc669de2c40288a414934039d2ffffffffff04a0a547193b22c6cc66acb6f6c926bbb61155ada07da896581acf52eaab6c21123ef26fe17f9c983fbd0f910c1b02459555aa96cf6bcb0b9b4ee374c6cbbb4890903e2f87b64d80ebb638e313ac851980a6667905bb6fca955b2786600ea9cc45c3213641a266ecdba0b17573dba5aa10ad9d831686d7e4e4248174f065e0c696a3a31821a93f6e49030d4cf063b8fe4cdf87bdb4e06b59ede349228bbb2d6c3ff83d087fcbb4e8ce4f98a341842c838b0aa852b70587bf0bb1934ee98cb0ed00", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee0400000000000000a6e6c44a5d482193b91fac6a4b86aae708a8fe967079b4dc31dbe6e7da840cab00badb0000000000000cde00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf692810400000002a6e6c44a5d482193b91fac6a4b86aae708a8fe967079b4dc31dbe6e7da840cab050000000000000006000000000000009a192de50070c39f440893ca66028c0fde3db238081fadf11e676d5cfd6e16bd4039d2ffffffffff03c3213641a266ecdba0b17573dba5aa10ad9d831686d7e4e4248174f065e0c696de40ac758d31d7dcaeff289171af8d3adc07c59cab6f3faaaae3ab769b9d9ce8c40d49c1602bd6fb0f81d107ddfcc04e9e08ba6bb91af36c94d2a7157e8b7a32b1e1ee7336f42352bf73ecbe68563bf291473c749aea18045e1f1f886c28c1cbe4e4c236a411f76e71fb0917b6e2c4ecdb404934f1dfb4af040bcf2c4dac2d01", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee05000000000000000b041b0865ddff65ae4f592bac31b3e3627cb44ed3bfd4678f33dfcdd03c9e6820c5fb0000000000000cde00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf6928105000000030b041b0865ddff65ae4f592bac31b3e3627cb44ed3bfd4678f33dfcdd03c9e68060000000000000008000000000000005b8ad24298b11d4afe4acabf254c55d99e1badc4d20a3f293fef3c8e9870e6eb4039d2ffffffffff03d3b72f96114a9bdb8b730b48a4075a66d0b5c8641ad0c2e05e35a52e0baa7cb2b946fd25fcd2fff7393541ee8d0fd387535cd9280821cdabfe528936c6784e88c40d49c1602bd6fb0f81d107ddfcc04e9e08ba6bb91af36c94d2a7157e8b7a32acef80c4845b7fb19f6fd811a561f427d385f5066516b6ffdc70555d940e0f9f70edee43ed38e24e8d6bf93e37bfcea45df6423cf20d46b0a4bbe0ec33232501" + ] + ] + }, + "receipt": "574d5231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa8be2039bc5d06e7cf9d0188c3f22c659f1687b08b69b97f50b134552c46da783000000000000000000366e0100000000028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a21a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c10280de8002000000000400003a9800000000016e360001c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720100c31801000000000400004e2000000000016e3600010265b94ec2ec2932104a7583c08050f61547332fe434464d598cad6464025b3040b598d58ceff895b484641dfdea9bf329f40b464895eb31b2271a83af17cce7fb0a000000d2e2268edd1bfc8a0dbebf1f2287b93e200bc8affd02c3e92ac872b3200731a103000000d290530d77935fe173d0d5dcbb2588170f730b423f93543b4f3ceaee06d4e78e2055cc0400000000c0f5220400000000801b680100000000801b680100000000000000000000000000000000000000000000000000000000000000000000000000000000000000001856620200000000070000000000000040548900000000002c00000000000000f000000000000000002d3101000000000001040200000080a1990300000000455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000080000000d3a5365cdd4a6acda275ad18cbec48f1fae9e6f3af5513449d166bffeee689500000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fa41f1621e42cb654d5c55f5595ae7bd41187f8d68105386547106cd5f721c48ff0002f83b2e3048500879d460ef33ffd2603259b365a30846cc8399d2d3d0a", + "hops": [ + { + "record": "574d4831012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7201000000000000000000366e010000000000c31801000000000400004e2000000000016e360001110001025980e26a212cf4c098b0f8a1a36a15333c6975497e60c073b9cd8242943b4c01b603000000000000c0510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001025980e26a212cf4c098b0f8a1a36a15333c6975497e60c073b9cd8242943b4c02d80e00000000000000470900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f5939013a02000000000000d80b0d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f5939023a02000000000000d80b0d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001313ea2ca301a5e17a28cb2d4b11014eaed6c09c8bb27b777993c556d712aa84d01b603000000000000c0510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001313ea2ca301a5e17a28cb2d4b11014eaed6c09c8bb27b777993c556d712aa84d02d80e00000000000000470900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f011700000000000000e0040700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0242000000000000004024140000000000030000000000000000000000000000000000000000000000000000000000000003000000000000000195ab21fa90d692e9fe90bade8cd132b0d17d75f25efa1fab73239d188617366501b6030000000000007094000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000195ab21fa90d692e9fe90bade8cd132b0d17d75f25efa1fab73239d188617366502d80e000000000000c05102000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000019d7a22d6b2f60ddeaf9f0b43a147004709d988a73527b65d08e0ea6372f910db013a0200000000000040ca22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000019d7a22d6b2f60ddeaf9f0b43a147004709d988a73527b65d08e0ea6372f910db023a0200000000000040ca2200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c2c18a1d4c5f9a784336c16e2755ec99f12ba6990cacb81cb23f94aa048d6034013a02000000000000485904000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000011600000000000000781e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000002cd000000000000003cf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000001720000000000000098850000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000002580000000000000020670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007000000c0f5220400000000801b680100000000801b680100000000000000000000000000000000000000000000000000000000000000000000000000000000000000001856620200000000f00000000000000008000000a6c37ecf5177e5e8d350fcc7c97ae32f9046bf15337e07d97453cd8dc79f4aa36d8d6332a0f2a20836f88a057863237aabd71abb0b5f41b8fed92fbbf04d851f567827e521b3ea570cdb126bc2c44e7940eb33690b8244e00abee459e82a8302", + "ledger": "0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffff00000002000000000000000000000018600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018566202000000000c2a9846621d91f9309170b29b95e158e278002ce4cc03a2d097917836a391c0ffffffffffffffffffffffffffffffff000000010000000000000000000000d0870c0000000000a087630100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006dde44606fd8377d9fa0809b2b36b708de3e6119d8c1c3d1f0d675917c28c6198096980000000000ffffffffffffffff000000010000000000000000000000709d0b0000000000607a60010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b9091845dcf9a7fc8e2a12e1e1b2f986888b33fb22f1b42b384cdf47ecfc33a0ffffffffffffffffffffffffffffffff00000001000000e004070000000000c8431200000000000000000000000000801b6801000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf4a5892c8a1bef2afba730a4ec868d0830f9355d10f540b757205945c588cb5ffffffffffffffffffffffffffffffff000000010000002c720200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d1ee853e5c092553edc99a0cc2468a7307afd83533a3a34f401b0695da343314ffffffffffffffffffffffffffffffff0000000100000020e53600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d436e9616073a533831cfbec646020b5d4dcc9d1df4b792ccaff6e7e23ee686affffffffffffffffffffffffffffffff0000000100000014b400000000000084e022000000000000000000000000000000000000000000801b680100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e433cf0201e9b443d808e4406925b6a4a90e9963c6235c742a0ecec87a59e478ffffffffffffffff6400000000000000455552010000000000000000000000d0870c0000000000c0f35e010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + { + "record": "574d4831012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c1020000000000000000801b68010000000080de8002000000000400003a9800000000016e360001050001025980e26a212cf4c098b0f8a1a36a15333c6975497e60c073b9cd8242943b4c01b603000000000000c0510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f5939013a02000000000000d80b0d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001313ea2ca301a5e17a28cb2d4b11014eaed6c09c8bb27b777993c556d712aa84d01b603000000000000c0510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f021700000000000000e004070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000195ab21fa90d692e9fe90bade8cd132b0d17d75f25efa1fab73239d188617366501b60300000000000070940000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000b2083f6a8d3e1c901b93bfb87f5ffe83344214430e91d08f4e422c5ac7f0c0869f25a4e826f34f770f8f63bcea36ec697bbc398f81c06aa8ad11bb1f7a33323de1911cf31bd046195483ef8a8b22a5778adb66cfde02d9e8f8e46360a5577b06", + "ledger": "b9091845dcf9a7fc8e2a12e1e1b2f986888b33fb22f1b42b384cdf47ecfc33a0ffffffffffffffffffffffffffffffff00000001000000c843120000000000e004070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + ], + "descriptors": [ + "57434431012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad6b5a6849f61a8bbd9d4db526a9fc1c73ef949590cdcd9b0b65ce6375262d9bd0000000000000000a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c10005d00201101010010108000100086665617475726564056f72646572000000000000000000000280bb0000046761696e0370616e0c0c13afc5df1dffed9f80c566f13be392cc90ad1ceba5db69473a7993f8a7ff2e64cb5d094b2fdf651392e4a2d4f317cc6a436ab9b0cd5afe626493549677937002" + ], + "compositions": [ + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f000127a00000000008a826f7851f7a89f5ecf581141292c7f69e9d004ed08b01d25dd3c66e30f43f602540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f08000000000000000000000000000000c264cd78485650e03db23e16774c0566ac52d21be1b9edacaad39fa34dc3100c01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f593908000000000000000000000000000000e43f0568538eef406fb5ed7901b131c0b789d9c0fdb04d61d590af9bec169a1dd2ed35929ea1d408e2da06ee1c840bacab38929016f1a7ba820a80472485c9c6159b98672266a380458e260007c2baec97749e4941eb6ac370d1660fb0e5f900", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f04054890000000000ee27588426463e6a765b4b8b5791670a7ad61be12ef1995102f9220928d6038d02540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f090000000000000000000000000000005e05448cb224cb6aa2726e4c0fe448b127e032169bd2ffea55b4693d53ef487f01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f593909000000000000000000000000000000f993d88a6b1a587e0980017103aa4fcdbdf1d7b1c948f95a8bfa36a26493bb1e89b7c717020a8a296d3688d7988d8f2e70e54228d60c58514564a12937b2bb9421199fdee27ccf49acb52a32fa0f698b3eb5b28c35c47a797ae5e7d531f7db0a", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f08096980000000000d8bb5ef530c1fe8d1a0478d5ae231aac802d9f99dff906a66aa111420948048a02540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0a000000000000000000000000000000539e2bb145135d7de5b00ebf1b1178f83ef60d1b5ceedabf98bbb424a1d0a96301e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390a00000000000000000000000000000031a3cdcd31f70593af11aaf7bc4d22cc1ec5c18ee0d918fb2edd5a9c98b5a4758e74da34c2fb00e235278a3010c44dac99131fa49acc910e04f3929e7a6014833b553fad2cd31d127ad862b76999324556e76388192020c8022db9db6cafb803", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0c0d8a70000000000c65182e1c2868d42d9c1a244545f3c3416b14e481ade5c42b1979419f360b3e502540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0b000000000000000000000000000000491fe4bc5c4bafa0066ec9c2baf4b4287fcd09a778e36d5996a97ab827b7eaae01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390b000000000000000000000000000000414a7eb6f2353358646d5b861979c533b344cc01fb9dafdb9d6518c4ad7e3f6f01a03c915891dac04736b7858fdf34c4137e10f593ab5cb3a64900914b360f6b242ee3ead8c5499f325411942dbe827bd51176f2b049c4453a654c49dc701b09", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0001bb70000000000381618c2dfecc2dea3881cb1d4ed69e43dec01f07ebda92e08cd08ff935ed44502540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0c0000000000000000000000000000001915289342627f7f5a8bf4ccaf04c0418735ae65e41ca8692ebd3fca27ea764b01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390c000000000000000000000000000000194d05dbb7218051137b7537fcb320653e7f67071a19ae363969aee47cf08bede3b8b1012f1fc03a132f8793e0eae4389c23da524a7f6482a1d5cdf26ff04249cb81d4f19d8b164ec8eb381c66ba2704c57c7bb6f2389114f14bbfd632199302", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0405dc600000000007629d9fb8d6ac9e846d7ebdbe0a3a64b29ad51f7e4db40be5f96e447abcf6f0202540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0d000000000000000000000000000000f98b3489983bd0476673dc05db4f09c092411512ed2da6578e9c254eee0d72f501e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390d000000000000000000000000000000bc87dab2315f8ea4f21a4b5adcb838b8d89f6d67f93bbdf5bf175069ad81fa5d123e5e3721d1b781cfcc4650a2d571df1142f2160b377be26b692636e885cd037553b9041ac8f13b1f2064568957993ff69556b8673f0ad6e6caafd8b4887c07", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0809fd50000000000d32fbadccdb9742ffff99344fe07069e2e89b1d7f2d99859ad48336fadacd7c202540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0e00000000000000000000000000000053c09229456148744a940ba828e839eca0cf46d7c15e2c3533b4c65dcb0b4ff701e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390e000000000000000000000000000000b5702d26fbc4708196f576c1d46b7fc92471154fe81689707069bcb43173ecedc36859b37258c17a4294d56d36befaa9fbd975ce62e5deb542372aef4c9240b43ba584fc98e7c5a5066e1ba4060e96ca4e50daa116366c3aca9dfccef285f405", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0c0e1e40000000000fe93ba9f3d79ff2d3aeb01726ac4728adf60676494fbd0e596f5972be5f4639102540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0f000000000000000000000000000000341b9ec871ad41102a671b153ccd014f32cfe167bf5cca3989470a574196d0a401e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390f0000000000000000000000000000001eafd27ec73cf8676b02ebb4fa82a5bc67b2298a442322797db8cd996f8866d7bfdca1dadbfc723c27a3b6963a74c78570a41403a9cb454643631157e8bddbda8ccd54062aeec216b94406595aeb140b59afd5e697733f45980831357c0bc804", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f00024f4000000000092a195ef7c5e97512b5eed1dd778db0bd1f1c75cf8cf25b61ec800296e459e6302540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f10000000000000000000000000000000c693ed9bd4639c29ceb962b071e0531512929a139ebed7c5e12989d28b316ddc01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f593910000000000000000000000000000000c5043e90fe94a06a8b47242bb084c7dc5f8e46aebbe9b2c5a8db30d44d1a8b55ab981922c453fc2c78b58f7ec56293bb26d02c335079fdd1e246e33607ef473f513bd939cfe20da8b94af0963156c7218be49ffd1efbc9c14429fddf91bf5006", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f04066030100000000d00e25bb474153c30c4bf88f0fb890a0dd10342388c0be87aa93399d54bbaf5502540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f110000000000000000000000000000005f50d92c07b2f57953a701633e9d59575664bfe9b13644d5db11c5d04629b08c01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f593911000000000000000000000000000000339aa736ed16c43427079481a1b58bb9d370f104bbcba7a9d915ba1528b1a75da2dabf39b4d5d2518a8bef4340b8c5e7b32250ac222cc3e8648a42730b81eeac78da15f7fa7f42f7bb131311eb22aa7f89ada8940b2dd41c18c113a694a50808" + ], + "takes": [ + "57544d31012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f559425465004500032d6893f980a7dad483af70b4e116a10b7caa4e2052593699b2c4d5251f98cf34d11f86821c2cd542f6bbf5152a07ad54b271f65b25309018a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0f222415114ff3e773663df797c88d6e11b094c4ffc6694581d843dbfbe8ba1307020a0000000000000000000000807f612a01000000c0c1702a01000000300000000000000037090d56c333ad6c4392ffbb8f80b7854c7ae3d562e182d6b76685f99db2ec1f0100000000000000c0c1702a010000000004802a0100000055000000000000003b9b3da8c3121692dbe9b75ff7b37a77773cf7d71d8b7c885253d5b3855c53b902000000000000000004802a0100000040468f2a010000003a0000000000000097f5ee9829746c16c29946a44489846e4a385eb6d62804ba22a18a99a92642ab030000000000000040468f2a0100000080889e2a010000005f000000000000004bb0df0483b6370199dc9196f695ec65b029d8ffb09dfd7ec24bf5d5c59a72f3040000000000000080889e2a01000000c0caad2a010000004400000000000000733b22d5d80b9a80443b60a14334000a8ab43f139364fa9cd3a547e8a7c523640500000000000000c0caad2a01000000000dbd2a0100000069000000000000009395a39fcaaab116f14a5e4f1e51abe694d712e0f912827045471c6283730a480600000000000000000dbd2a01000000404fcc2a010000004e000000000000002b869dfefc2fe0873e06d23c09f1629470a788c6b8f2aa8e6aafb1298cd73fe60700000000000000404fcc2a010000008091db2a0100000033000000000000009f10cb7a3feccf63f5c0c398375eaf998628d314dc8159d15845fe5a0f8a006b08000000000000008091db2a01000000c0d3ea2a0100000058000000000000008b03281ec96d96d0e76e48ef184b7a60d4856eddf77629af961fe54746404ef60900000000000000c0d3ea2a010000000016fa2a010000003d000000000000001fb42eb87b4ea4dc72d02c9e6ea82aab91be4bbef122892b8921e2f552a462678883ea090e9f374f91bacaac0864af7b6dafa57c4f8a3ac0040ec7fc669f1f2506000000d7b83f99f213619227fbccadf7069c3f6cffca8e86f2aba6ffb8d15212698fa51be10b08c6e09a1b9314f45c65bf3c9b6bd507cfa5df3f013d50657245ee48c5602f2236c9c17b8a1cca6bae144f8f162627d27095a7256e1c5b8bc199c84fb3d535c9375be68b107355dcfeaab2a78192ba09570642f09de57b7bab1438eadc703cab1c2158dee2277fea9a9b909df1f7926a3a56996b7db1bfbaaba40ffa2970412cc3dc8306ba8f88774f1a622d1bf21b9ef51fb85b990d279f66fed977340159425465004500032d6893f980a7dad483af70b4e116a10b7caa4e205259369900000000000000000000000000000000000000000000000000000000000000001f250bafd7daed705bd36f8138b7419700ffe4c20991a2a46993dfd25b592a3643977ddbb511d2b51471498d51135c1a724a069a507032f2a3ff93aaa9787f0d", + "57544d31012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee2b956ba992dbd2d36d81fc4a4a771b7048b732a2e36ceea2f57448216ef566c6b7091c801fbc133089d9612f76d94401ffd0199edd399f9db0beaf3e8a611d45859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf692810f222415114ff3e773663df797c88d6e11b094c4ffc6694581d843dbfbe8ba130702080000000000000000000000405489000000000010679c000000000030000000000000008fae533ea900dd435a27c7ac6bbe43a78ca53f4ac8bf916accd92a8d374cba94010000000000000010679c0000000000e079af000000000055000000000000006cb63b81a4b11a270dfd91c37eaaed324b1b55999789174218e9990aa186455f0200000000000000e079af0000000000b08cc200000000003a00000000000000e515ec9e690d2bf9be7ce9bd8547b84406445c82682346f3cf84ccb305cf4ae50300000000000000b08cc20000000000809fd500000000005f000000000000008ab19bbbd6173bd1339419c327fadbf9337f7d77aff9e7082ee68da5132756c80400000000000000809fd5000000000050b2e80000000000440000000000000017fca124fd9c32581a3a6f032bc985481cef9852f5f07ba3120fd851511d20ba050000000000000050b2e8000000000020c5fb00000000006900000000000000be2b55d6011931c758ef4700f002f24a947e8e95428049653bc0fc44055b5563060000000000000020c5fb0000000000f0d70e01000000004e0000000000000083da93c67f107a2933b1befbdaaf558a44b2adf41cd3e085f1d48767e8dc3fde0700000000000000f0d70e0100000000c0ea2101000000003300000000000000989f78372ee677d01a1a45c0f4e33989e1e30935ef1f70e0426de38e2cb5ae8a5b8ad24298b11d4afe4acabf254c55d99e1badc4d20a3f293fef3c8e9870e6eb060000000571c24d887ed7ee13662e1b6b5916b05446674f04a48bb9657a88aa791aa4f78c7c1730862c2541afc6faf68f8f1a4b34c99f6c9ea331b8be3a60e97734ac814bcc3352c9690a6b19b9aaa4301286ce746571ec9a1dfd777ce3cdb1ab47ca1ea6e6c44a5d482193b91fac6a4b86aae708a8fe967079b4dc31dbe6e7da840cab0b041b0865ddff65ae4f592bac31b3e3627cb44ed3bfd4678f33dfcdd03c9e684fa04199a174e37753164a4ac950986de9a4abcc3c2cdce827249a9f063289b8012b956ba992dbd2d36d81fc4a4a771b7048b732a2e36ceea2f57448216ef566c600000000000000000000000000000000000000000000000000000000000000008fdd3b426a73d6a2e5ba04781aafc491c5950f29be46baca5bc57f7e54ba95b22dee29ffae444fb5eeee0e941d74e4e71f8dffbe4f45979a1e8c49aebc023c0c" + ], + "logs": "[[{\"v\":1,\"sid\":\"aecd92c612a3b9704723441772d4704e\",\"sn\":0,\"ts_ms\":1792108800500,\"event\":\"start\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"aecd92c612a3b9704723441772d4704e\",\"sn\":1,\"ts_ms\":1792108801100,\"event\":\"present\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":0,\"decode_us\":900},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"aecd92c612a3b9704723441772d4704e\",\"sn\":2,\"ts_ms\":1792108822500,\"event\":\"end\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}],[{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":0,\"ts_ms\":1792108800510,\"event\":\"start\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":1,\"ts_ms\":1792108801147,\"event\":\"present\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":0,\"decode_us\":900},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":2,\"ts_ms\":1792108805510,\"event\":\"stall-start\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":3,\"ts_ms\":1792108805910,\"event\":\"stall-end\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":4,\"ts_ms\":1792108822510,\"event\":\"end\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}],[{\"v\":1,\"sid\":\"59b8375b353f73cd469c9ecaf790f7bb\",\"sn\":0,\"ts_ms\":1792108800520,\"event\":\"start\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"59b8375b353f73cd469c9ecaf790f7bb\",\"sn\":1,\"ts_ms\":1792108801194,\"event\":\"present\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":0,\"decode_us\":900},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"59b8375b353f73cd469c9ecaf790f7bb\",\"sn\":2,\"ts_ms\":1792108822520,\"event\":\"end\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]" + }, + "lesson_bad_signature": { + "kind": "room", + "room": { + "descriptor": "5752443101d6b5a6849f61a8bbd9d4db526a9fc1c73ef949590cdcd9b0b65ce6375262d9bd0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7200406cd8e95d060040420f0000000000170068747470733a2f2f6c6573736f6e732e6578616d706c651000000004002c0100005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f000000000004086469726563746f720101020b1a0f00000000000000000000000000000000000000000000000000000000000009706572666f726d65720301020018110000000000000000000000000000000000000000000000000000000000000861756469656e63650001010018000000000000000000000000000000000000000000000000000000000000000a636f6d706f7369746f720201020402000000000000000000000000000000000000000000000000000000000000000004067370656563680101046f707573000001000000000000100000004000000080bb0000010f056d757369630101046f70757300000100000000000080000000fe01000080bb000002000b63616d6572612d3130383002020d617630312e302e30384d2e30380b617663312e363430303238010003800268011e0096000000f40100000005d0021e00f4010000dc050000800738041e00dc050000a00f00000000000000000a63616d6572612d37323002020d617630312e302e30354d2e30380b617663312e363430303146010002800268011e0096000000f40100000005d0021e00f4010000dc05000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011e00d0070000f401000002444546527d2038c499b62ca152978601e0dfa4f2e1c5b15e1ef68449ca33b1d6dcd497037e6ce23ed0aa7b849df375b2e89955d6119b4a878fd20be449be1312c6534501", + "grants": [ + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c5200000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000101020b00000000000000000000000000000000000000000000000000000000000000000057455031011a0f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000100011568747470733a2f2f72656c61792e6578616d706c650000bea5ab08f1545cb8dc513b29a89599253fa6e205f706abf0464328c151580465de8533f866bf72b43df493570544a0255c57f275f4cab2f63c86ad7521b3e600", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000103010200000000000000000000000000000000000000000000000000000000000000000000574550310118110000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000020102011568747470733a2f2f72656c61792e6578616d706c655900086665617475726564040074727565077375626a656374400035393432353436353030343530303033326436383933663938306137646164343833616637306234653131366131306237636161346532303532353933363939e8c84e771a717476baa11cf942325fe291a1df45c01de5500df09f929cfe4afda793a413095db8ef85c0834bd20532b4e665684a44d311ada6cb0c887942fd06", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000103010200000000000000000000000000000000000000000000000000000000000000000000574550310118110000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000101011568747470733a2f2f72656c61792e6578616d706c654a00077375626a656374400032623935366261393932646264326433366438316663346134613737316237303438623733326132653336636565613266353734343832313665663536366336cfbe463d94a52e508797cf955379ea84c17812512bd8ce814c994de9acbb1b3549879f17e06a51d786bc13c4fa644f39eb0e8afc6720e5157710a4eeb338df00", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c100000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000302010204000000000000000000000000000000000000000000000000000000000000000000574550310102000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000103011568747470733a2f2f72656c61792e6578616d706c6500003869c45831770faf0563fbece44f7cbcef4cd3fbf19d2a59aa34ee7b95676c956edc96c72ed89e2a11bae6c2ac04e150e899269f76ddc766018d439764d01004", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa78a50426ccfca7ce958e1c52db484fc712687799e1ff84bc4cdd4182aad891c600000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c6500006269a123cbac9dd31be7210d27fb2ee2fdd1f062db4f8546fc4d004435da090bdf022e6e8fad6d2e1220582094847a1c7607d330d6056ae1798ec2960c6b2b0c", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa764a37268db038e6a6700c23f1e1b6ded3fd817e2faea48b1e62cf20cc65cd000000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f00000000008096980000000000ffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c650000c4cbf1a57fe4987d85f84b0d28b1a8261efce7cd6cefabbc2881e56696f83f04c65b12a6398643b5cf09c71774cea97564acd3adc6e3ce5576aa431db40de90b", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa2d8bca2005c2ba7300d28cda48ba74c2217bc1990aa7a2b25931bcc89ca580c00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff640000000000000045555200011568747470733a2f2f72656c61792e6578616d706c6500008a8b973614ae2b0d1e85469d8cffe0e9d207fbbaf0a27e1af25583ba2926f1cc38f5cd8cc3d3cf4ec191227b9242ae798b2c281b684b71f2f7c59deaf981dc03" + ], + "key_log_head": "{\"grant_max_s\":86400,\"issued_at\":\"2026-10-15T00:00:00Z\",\"keys\":[{\"compromised_since\":null,\"id\":\"room-authority\",\"key\":\"d6b5a6849f61a8bbd9d4db526a9fc1c73ef949590cdcd9b0b65ce6375262d9bd\",\"may_sign\":[\"live/room\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"take-store\",\"key\":\"0f222415114ff3e773663df797c88d6e11b094c4ffc6694581d843dbfbe8ba13\",\"may_sign\":[\"capture/store\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"relay\",\"key\":\"0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72\",\"may_sign\":[\"live/relay\",\"energy/OnChipCounter\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"compositor\",\"key\":\"a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c1\",\"may_sign\":[\"live/compositor\",\"energy/OnChipCounter\",\"energy/ModelBased\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"ingest\",\"key\":\"3549cf93c7fb47a5cee15993419720433466b527ad7a7423ccc78de33baf2cc9\",\"may_sign\":[\"live/ingest\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"meter\",\"key\":\"8be2039bc5d06e7cf9d0188c3f22c659f1687b08b69b97f50b134552c46da783\",\"may_sign\":[\"live/meter\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":\"2026-10-14T00:00:00Z\",\"id\":\"meter-old\",\"key\":\"1c5430e51de83d2adf16caf0c8ab6ffaeed421d6569611674a685117eb385180\",\"may_sign\":[\"live/meter\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"revoked\",\"until\":null},{\"compromised_since\":\"2026-10-14T00:00:00Z\",\"id\":\"relay-old\",\"key\":\"e079d6f85a4fb3fecc7f4a94cde29c757313f84e0bd726c9c9dfe640a5745931\",\"may_sign\":[\"live/relay\",\"energy/OnChipCounter\"],\"status\":\"revoked\",\"until\":null}],\"kind\":\"wai-key-set\",\"next_root\":\"fa2ec0c92dcc45c59d8ede47e5f6cbaa0f89e36186a25c7007ec718415288a8d\",\"origin\":\"https://lessons.example\",\"prev\":null,\"revoked_modules\":[],\"root\":\"93bd7020e78d0ced730d16b3df0385340bb6ad05773df662e19ec3d4735c5e5c\",\"seq\":0,\"sig\":\"412da30d6f3f35c71d6c8075a78713129497b1a28c2887990c99cc16dd26dc9c33593f894286ced846c36b597013b39e583d83ce42cb1ffa75812e9222bad104\",\"status_max_age_s\":3600}", + "chains": [ + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000d927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c52d436e9616073a533831cfbec646020b5d4dcc9d1df4b792ccaff6e7e23ee686ad90f34467ecd1ae1beee4715b6ad56a1066885f45de845b5e1254f022b9504b191eee829d5007a0391a30af95d9efc41cb6e7b9fe1bb4591e7f587b35fc12103", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720100000000000000427fb576b2ce3ae19b7f085dc335864687bcc94f64e8d47e8d7a0baab7acffc2000000000000000000104000e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5d1ee853e5c092553edc99a0cc2468a7307afd83533a3a34f401b0695da3433140315765b5e69cf9cda0aa975fff4ff243328d62ddd0d53798472165bba87b9921d60cadec42afa900b559ae469f7a3bb5870d6a180c30d368daca0aca4f43d05", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72020000000000000095ea09d688d1c33f265428db98aa2a268f391f6ffd908ab6c39843c8e591177c000000000000000000104000a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c1b9091845dcf9a7fc8e2a12e1e1b2f986888b33fb22f1b42b384cdf47ecfc33a0e053886f0bc7ed1fc288c3b8dbe6e2e009998f204f921e1ec247fc870003f99598f8216ff5cbda966841da96c98ae2a727607db2d9b08542e2fb9fce4f4bfa04", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720300000000000000ba5015a517cd3fdebc9cd865fc10df47e2e7c97a444dff51c5cfc647434a3ef6000000000000000000122400a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c100020301e449ecf9f6773169161edefbfdb034114849028534b79602cdd2538371a6b21f18b273b29fe0678f781bbf72c3d75bf17d03adc10a531d788d7e693f3858620a", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720400000000000000fc5a8acc446b029d3d479f4b66d09f53e968f32792aee1f4e278ccab6a195a4c20a10700000000000010400078a50426ccfca7ce958e1c52db484fc712687799e1ff84bc4cdd4182aad891c60c2a9846621d91f9309170b29b95e158e278002ce4cc03a2d097917836a391c028cea7fedad7bfdb6b7402c13da6e6b8c1077aa5290e1954f13ac0082da7cdc9d6ff62a798afe163a44575ce65b558f4266798d75e116d34c58f3c3ea8900907", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720500000000000000044bf302c9f47024ab231a640905353115ce057e85a04c509808e7fe9b21b254c027090000000000001040007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eeecf4a5892c8a1bef2afba730a4ec868d0830f9355d10f540b757205945c588cb507ef6ba4e6cecea09d1714d6dd41c963ca58cca289c9839eaf74eba473fc28eb2f5b6190e4da8b5fe796eca8212e2c915ec1459aa081a97de67ec5926f450608", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72060000000000000010ca0251df4b17967f3704e4d3bd4a308ad12fd640aa970e13908ee015aa985660ae0a000000000000104000a764a37268db038e6a6700c23f1e1b6ded3fd817e2faea48b1e62cf20cc65cd06dde44606fd8377d9fa0809b2b36b708de3e6119d8c1c3d1f0d675917c28c619f660a54c57820b95480c0840836278a125593043e58895bdb8e00fed6e0c1308731455a732b1baec43e8e68ee585fc2e272d3c5124e08fdb14c7183eacf33005", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7207000000000000004b49d8a56fe5947607740aef1061d356e7f6e3c83a21701e432405315b10cc5400350c000000000000104000a2d8bca2005c2ba7300d28cda48ba74c2217bc1990aa7a2b25931bcc89ca580ce433cf0201e9b443d808e4406925b6a4a90e9963c6235c742a0ecec87a59e4783ee48164e163b5b78d764f4380a58e1596c7a0b06a78cc26bbc130382bca7e5c863bf635a1f4b4b42cdcc6e788cd6a26e3f89e56af5e83b43b1d12b013282206", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720800000000000000f0719456a072c6baab714eaddccad1b7cd93fa97fe10ecf990edf381e0c1230a40420f000000000000122400e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5000101011fe5381fc3b2acb103f880c2fa9ad52b41f7e792f6dc84b320804531020b477c1dc21fb2afc6b440e441a462e0cb714eda89c7782b0e6c7f9c26f392bb57c307", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720900000000000000062e7b16ad9bfc59a985b0f3e5e68d0c6e28c73355d6be2e11cf8f945ae463ce40420f000000000000122400e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5000202032dbd24bcab531bb61e3759b44e48aa90009652aecc1ac2d66af737af13b577f47adf927eb619d29f59395164eac2f2d3a363c0e4d1847527e8d3b40da9c5ae02", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720a00000000000000fdab89363cafb8a44d66c7bd763a25da8917783ffcf4a97bfd36c70455c2307d40420f000000000000122400d927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520001000103d7b211d87104be28d03b83d15a94431fd9253524acd08eaea84a500ad9883c12cef58ca5016c10185fded407b57717ef13c5a600ed11818502271390122609", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720b00000000000000b2915db782fe866d5dbe91dca3b09c7e9d7d677045feb73cfa3e5a2881edc11b40420f0000000000001224007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee000101010e426f3883cc38a43b114bd429ecb3059f7d86df5569989b55cc45f54a44cd9e3a4a459961bb0a1af8030633d23fcbcb7d5fa39c0352ac0c760999e35b2d6908", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720c000000000000002b70c96549302cc9e63155fab6889ca46f91223f5690a2c21ab7081331acdc73002d31010000000000132200e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50002da3a459c92f7c05ffc10bfa3d65ad4387e2deb2b0152056937992ed445b4acd95eb5b93826b09f3bd959da3d8c87a7bbcd4d6f461bebbf8917e5991fb7367b0a", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720d00000000000000041812e956c700e749826b39425905b8034c109e6deda9b07dc4cc106e419b1a002d31010000000000132200e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50001cfebd12a777c322de9cbd0f5f7f47aa305d741a1887ca8eb9d5c5e88fbee77e52301a011319877a391756aef65f5fcce986d7956539612a97653fec894c4750e", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720e000000000000004a9d94ee96ec81db8f434cbf8d46940217d1ec0849ad66769beecda538455b34002d31010000000000132200d927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520001f9ecc31703ca5a1f00b6cdf9196e371c49a1cb0ab5bd5125835798e52017cf7501e0341746a3e9e81ff559f805b0de751bfea43b60edf33a7a1e808b06333b0d", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720f00000000000000e4fab4da5c6f3c166c5ce005bdc60c491c7137298872ca63fb252d8dad6c1925002d310100000000001322007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee000124288a152f94c15021ffa82d68b2c9fc1351e4b8fb4a21a345f6b85dcaa0bb65ec93c2f5d9ac406b54064316618582247c4166345156e9aeb3e390580403c00f", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b721000000000000000e8f976794e72bb3e1ac846a01f930aa7181468e136a25151275e77a34fca1c4ce09466010000000000132200a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c10002416e0b9f31fedb053ead6f7fe918a3542a9f4f715bad8522e9d77cfaa07d04dce798bb36c48ece9e60cc75deafe5a90afca0185ccf00401877717a3a5384e705", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7211000000000000007adb989ef3fdca75b2116a3c1d8ccc32f9726a370bb1fe74a778367b80ef9b13c0286b01000000000011210078a50426ccfca7ce958e1c52db484fc712687799e1ff84bc4cdd4182aad891c60182ff63588f819d513b3adf9234be70f92276eacbf740d9dd6a2887b35d67ae250874c98219fd203dd46380b790fa11523ef3d647af4bf2c29b6f767fd67d440d", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72120000000000000046d9ca416acfb977917660e77d1c025dd21fcab6b9c77a84be1ba34126ae4705c0286b010000000000112100a764a37268db038e6a6700c23f1e1b6ded3fd817e2faea48b1e62cf20cc65cd001961a1543c72a2ed7cef5e60a729057980ae0333d3ca172d0d7765ea3cb53e37869fc96d783d87f199d84e403a6590b9cee396e80d8d4a11ddf2c1ed7e1a09108", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b721300000000000000a5bc35e28e4dc80e1246f65538b5ee2d5ce1e63415726c1ab0424f4b3082ca49c0286b010000000000112100a2d8bca2005c2ba7300d28cda48ba74c2217bc1990aa7a2b25931bcc89ca580c01b85d69bae728a80312847181411cbf1b857c7c904c6e3ce393731ec05a8b902aa943ddc1177eb5a85d89d3b8188b368cbb8420bbafd9e518364260f9f7f83704", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7214000000000000009136003edf701bb862143262e21c22fef7493df56a28db3557c789a1e7cd2c4bc0286b0100000000001121007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee018afe46c99845ba0d7af81f03eb2c8c4067f34c8674cd33106729df8cfd81c48d21ea9df9ebc4662931d865c3a95683c870b3115a1383f2813967b278803c0109", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7215000000000000006f83d7703375750f5cca72fb4c1179c04f2b65202682adbdb3d67b1ea13fc91ac0286b010000000000112100e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f501d906868dc6a495c8188516e2e90a24fddd72396678bb9bf6a699921b0603cfae109e785229a8e9589bc9f8631239e41060170d7b1139b39050dbc85ad4d0ec08", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72160000000000000095a40cc34fa91bfb041448d1ceaa4d3a30716a6fcef532bfd452ff00242dbe72c0286b010000000000112100d927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520101de18404ca29dde16299566d3688d1188d8124540fd37c7df87457a4b87d94236d6ad5ba1bdaa6b861505e004378c094de1aa08a45057209e1f972f4443ee0a", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b721700000000000000ccd8e80adcaacc780695d2f91dd925f4a4e666adce7e40b7aed60d8e9fd41dd0c0286b010000000000112100a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c1017b0e0acca851520158b977db04889ec317c5b3b63399ffad2d8f1fa2a3e16a584163fe83cf09e6fc88bfd8320594a3c6d25f2979120a79eb4205f513158d4c00" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520000000000000000000000000000000000000000000000000000000000000000000000000000000080841e000000000000092500e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f501a00f00002de246b73724b5efe6fed8b8b500d8d4db72cd1a6bf1fe44a0fcbc1390d38c8b7c34efb79c4f52745b9d945ea0e09b83f2d3ea6e2ebc63ee72cc83963b457e0f", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520100000000000000b2c4d5251f98cf34d11f86821c2cd542f6bbf5152a07ad54b271f65b2530901880841e0000000000000925007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee01a00f00001bdab77a3690419feb21de999193a68bf7856807f63c37989d8b1d0d26bab830f17be4ae69e686f15b5f9f58510474fb68a294e32145e972430955bba78f5308", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520200000000000000b7091c801fbc133089d9612f76d94401ffd0199edd399f9db0beaf3e8a611d4540548900000000000008160004000000020e00727573686564207468652072756e001895215217dce05b5b18cc8f8638ea08e01ad7077de8a741852f15db966b6df81d9603c321949de5dfe7c53fbc553afe7a2439bc18def689a61e9afd2858670b", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c52030000000000000078223b1dc7500f31098e2ef4ae4f615e1eaa5a06e5f3a19c0698970771536431405dc600000000000008170004000000010f006c6f76656c79207068726173696e67003dde71eca895d2587649a3888baef3917077bd6d500d9fcef2348c70e6590bcf16d2738a9bf9a49c30b14d22203fd088c8f492169118a20b323c955172daab07", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c52040000000000000091ef0040cd22f692bad52da2aa2ee4ff3495d571373249b21f7488b583368af40024f4000000000000092100e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f502159e513cebb1af6f51733013724825fbd4b3301b94c9c31c10b4011ab35bd7e4590002d82faadb93cb25ffafd19a236c5caed905ed849d7c246177d48908450d", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c5205000000000000007ad53acd82e3e7767b6fe17bf1e5cbbefc07729bcbeeacbc71387e043bd5de8a0024f40000000000000921007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee02feb7e48b31bf62d8b7974e1c15b19f2abfb6190cdf8912b00c9970b386745e81beec63bfc20c7f8046f392aa238f96f0a41a0c8f71095c4e7405c5b21ee55900" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa78a50426ccfca7ce958e1c52db484fc712687799e1ff84bc4cdd4182aad891c600000000000000000000000000000000000000000000000000000000000000000000000000000000a02526000000000000030b000900676f6f64206c75636baba18e36da1e7d557a55c40c03a8f89ceb77a4dfee113e44680f91c25b60bf8220d0cf49ab0a9f4d1b42b793b90e596a22107b80c7db3a6095749cc3e138bf01" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa764a37268db038e6a6700c23f1e1b6ded3fd817e2faea48b1e62cf20cc65cd00000000000000000000000000000000000000000000000000000000000000000000000000000000080a8120100000000000307000500627261766f94766cf3326e298ea016d3784bb748cce1ae77e4e939b4a75c7bdb3a912978c251a3935f53acea5081479ea000604095c3ce85a53df644491cdc5a0ccec3a803" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f500000000000000000000000000000000000000000000000000000000000000000000000000000000c091210000000000000c7e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da00000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000efad5feffffff0039276d6016497f70ab9d2a5f1ff179a1070b1016d8cdbde6791792f00ada0133af241e3b99a3fca2e21369598281e46c89c41553c97855022f565312fc32a10c", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50100000000000000d7b83f99f213619227fbccadf7069c3f6cffca8e86f2aba6ffb8d15212698fa5e0a57e0000000000000c7e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0100000002d7b83f99f213619227fbccadf7069c3f6cffca8e86f2aba6ffb8d15212698fa500000000000000000200000000000000880211aa301be185e15a6c1895c2d43367360f1104ca7e72fa3c576cf0437c1b000efad5feffffff00cff0422ccfafb46431eeeec478c887b9a5609ed42f7b3b79c0b4c20ae6d87c375a6160907635a9fdd4a3990b2a4dcfdcf4ca46123c7e44dddca935c3354baf06", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f502000000000000001be10b08c6e09a1b9314f45c65bf3c9b6bd507cfa5df3f013d50657245ee48c5602a9d0000000000000c9e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da02000000021be10b08c6e09a1b9314f45c65bf3c9b6bd507cfa5df3f013d50657245ee48c502000000000000000400000000000000c6cda45663cd175aa9ba5f30289803a8059baa9924a9f919ef40d91902ca7e00000efad5feffffff01bbf572927af2d03d45aa66c8f630bda9d29429e07e09a3a3d6c9a8d933326244bec776069cf37a98f02833ca20fb02b81e7ccd6dff6d55266db73157ecb1002955cc3aa3b3d1a698be7314dd222b0b61676b727c6e3ed6b93394f4c55dcd100a", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50300000000000000602f2236c9c17b8a1cca6bae144f8f162627d27095a7256e1c5b8bc199c84fb3e0aebb0000000000000c9e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0300000002602f2236c9c17b8a1cca6bae144f8f162627d27095a7256e1c5b8bc199c84fb304000000000000000600000000000000249f88d1dcac0f7d98b53d89fdbeb22c284ffc97c90ff18a1ee27980e3ce88bd000efad5feffffff01eb6ddc57c1c32eeef5f7ff5f2a73f33e16684a4b4249d3cb0b99f72adb199a21bf6c5f49688d465cbdcc26c2dd73ed5b9ec157bf86dba5d2dc0b44e4112193a511a1fe886a482e6876c09119c470ce35e7a046f9d60df4b4085bb7c2093d9309", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50400000000000000d535c9375be68b107355dcfeaab2a78192ba09570642f09de57b7bab1438eadc6033da0000000000000cde00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0400000002d535c9375be68b107355dcfeaab2a78192ba09570642f09de57b7bab1438eadc06000000000000000800000000000000cef53df7a17ba27234381bf4fb47ad712b9f2aab80d4f837db1155d18af26a55000efad5feffffff03eb6ddc57c1c32eeef5f7ff5f2a73f33e16684a4b4249d3cb0b99f72adb199a21d75f76241ecda381c6d8d9232daab10b13803e2fe52ab86871c6bca67199fef3c6cda45663cd175aa9ba5f30289803a8059baa9924a9f919ef40d91902ca7e009d53f633fc2fae20b44ccc38918c8ac026ebd6fe90d3548fbe195fec26564bbdfa8ae5862663306a82c0680fc9ad127d5cf6304e92beae4ec0db27c4b54e8700", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50500000000000000703cab1c2158dee2277fea9a9b909df1f7926a3a56996b7db1bfbaaba40ffa29803efa0000000000000c9e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0500000003703cab1c2158dee2277fea9a9b909df1f7926a3a56996b7db1bfbaaba40ffa2908000000000000000a000000000000008883ea090e9f374f91bacaac0864af7b6dafa57c4f8a3ac0040ec7fc669f1f25000efad5feffffff01b09aef291cbbb9927cefbbb3a26f4c2d4689a03a6b1cc7f938ce9c615890e9dd16de4bf19b3cfced2d0c7afec09ffdbafe2275901f940330ff39faecfadbc6adf3a784f003d44b0153b753eb6afc1cc30c047dd373457fd21f31de384709060b" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee000000000000000000000000000000000000000000000000000000000000000000000000000000006018230000000000000c7e00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf69281000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004039d2ffffffffff00727cd67bbd0a01562d596da5eec92f54c9f9ff4c863b4f7eac3a1d9bf2c762f287a80b347596f5f98eb11463381d7729ee84dbc2afbb52d38d4172e23497d005", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee01000000000000000571c24d887ed7ee13662e1b6b5916b05446674f04a48bb9657a88aa791aa4f7802c800000000000000c7e00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf6928101000000020571c24d887ed7ee13662e1b6b5916b05446674f04a48bb9657a88aa791aa4f700000000000000000100000000000000ea1a9c44b721ca2a583fad19bca966298c4958034505c8143c6779e0b73bbd9e4039d2ffffffffff003171796be71e8f39fb36708f2ea8a5c0f9290bb7e2f029298d6df465b6b6f7f89715239ea8eb94b4b5e1747c48c82b01ea80c183879adca17efcaf3a45146204", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee02000000000000008c7c1730862c2541afc6faf68f8f1a4b34c99f6c9ea331b8be3a60e97734ac8100b19e0000000000000cbe00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf6928102000000028c7c1730862c2541afc6faf68f8f1a4b34c99f6c9ea331b8be3a60e97734ac810100000000000000030000000000000075ea44294bf1e20d670345b52e104bd360523168f8b0046b23a6bae059558c5c4039d2ffffffffff02e1a39c21b47bc7d6c78d0678749d84614817a2fb9b0a7dcbf19e940ff564df98a0a547193b22c6cc66acb6f6c926bbb61155ada07da896581acf52eaab6c2112b0c76a9550457dd487ed407b529c2f5e6df599da73b966801709654bd5991bd3ad6a930521ff1b4c63561f13a285c964df44cf4eea7f2ff5c013070e652ae001", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee03000000000000004bcc3352c9690a6b19b9aaa4301286ce746571ec9a1dfd777ce3cdb1ab47ca1e8035bd0000000000000cfe00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf6928103000000024bcc3352c9690a6b19b9aaa4301286ce746571ec9a1dfd777ce3cdb1ab47ca1e030000000000000005000000000000003935b6c803043cd12ec5bd53c252b49dfc9ca51a329fbc669de2c40288a414934039d2ffffffffff04a0a547193b22c6cc66acb6f6c926bbb61155ada07da896581acf52eaab6c21123ef26fe17f9c983fbd0f910c1b02459555aa96cf6bcb0b9b4ee374c6cbbb4890903e2f87b64d80ebb638e313ac851980a6667905bb6fca955b2786600ea9cc45c3213641a266ecdba0b17573dba5aa10ad9d831686d7e4e4248174f065e0c696a3a31821a93f6e49030d4cf063b8fe4cdf87bdb4e06b59ede349228bbb2d6c3ff83d087fcbb4e8ce4f98a341842c838b0aa852b70587bf0bb1934ee98cb0ed00", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee0400000000000000a6e6c44a5d482193b91fac6a4b86aae708a8fe967079b4dc31dbe6e7da840cab00badb0000000000000cde00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf692810400000002a6e6c44a5d482193b91fac6a4b86aae708a8fe967079b4dc31dbe6e7da840cab050000000000000006000000000000009a192de50070c39f440893ca66028c0fde3db238081fadf11e676d5cfd6e16bd4039d2ffffffffff03c3213641a266ecdba0b17573dba5aa10ad9d831686d7e4e4248174f065e0c696de40ac758d31d7dcaeff289171af8d3adc07c59cab6f3faaaae3ab769b9d9ce8c40d49c1602bd6fb0f81d107ddfcc04e9e08ba6bb91af36c94d2a7157e8b7a32b1e1ee7336f42352bf73ecbe68563bf291473c749aea18045e1f1f886c28c1cbe4e4c236a411f76e71fb0917b6e2c4ecdb404934f1dfb4af040bcf2c4dac2d01", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee05000000000000000b041b0865ddff65ae4f592bac31b3e3627cb44ed3bfd4678f33dfcdd03c9e6820c5fb0000000000000cde00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf6928105000000030b041b0865ddff65ae4f592bac31b3e3627cb44ed3bfd4678f33dfcdd03c9e68060000000000000008000000000000005b8ad24298b11d4afe4acabf254c55d99e1badc4d20a3f293fef3c8e9870e6eb4039d2ffffffffff03d3b72f96114a9bdb8b730b48a4075a66d0b5c8641ad0c2e05e35a52e0baa7cb2b946fd25fcd2fff7393541ee8d0fd387535cd9280821cdabfe528936c6784e88c40d49c1602bd6fb0f81d107ddfcc04e9e08ba6bb91af36c94d2a7157e8b7a32acef80c4845b7fb19f6fd811a561f427d385f5066516b6ffdc70555d940e0f9f70edee43ed38e24e8d6bf93e37bfcea45df6423cf20d46b0a4bbe0ec33232501" + ] + ] + }, + "receipt": "574d5231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa8be2039bc5d06e7cf9d0188c3f22c659f1687b08b69b97f50b134552c46da783000000000000000000366e0100000000028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a21a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c10280de8002000000000400003a9800000000016e360001c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720100c31801000000000400004e2000000000016e3600010265b94ec2ec2932104a7583c08050f61547332fe434464d598cad6464025b3040b598d58ceff895b484641dfdea9bf329f40b464895eb31b2271a83af17cce7fb0a000000d2e2268edd1bfc8a0dbebf1f2287b93e200bc8affd02c3e92ac872b3200731a103000000d290530d77935fe173d0d5dcbb2588170f730b423f93543b4f3ceaee06d4e78e2055cc0400000000c0f5220400000000801b680100000000801b680100000000000000000000000000000000000000000000000000000000000000000000000000000000000000001856620200000000070000000000000040548900000000002c00000000000000f000000000000000002d3101000000000001040200000080a1990300000000455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000080000000d3a5365cdd4a6acda275ad18cbec48f1fae9e6f3af5513449d166bffeee689500000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fa41f1621e42cb654d5c55f5595ae7bd41187f8d68105386547106cd5f721c48ff0002f83b2e3048500879d460ef33ffd2603259b365a30846cc8399d2d3d0b", + "hops": [ + { + "record": "574d4831012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7201000000000000000000366e010000000000c31801000000000400004e2000000000016e360001110001025980e26a212cf4c098b0f8a1a36a15333c6975497e60c073b9cd8242943b4c01b603000000000000c0510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001025980e26a212cf4c098b0f8a1a36a15333c6975497e60c073b9cd8242943b4c02d80e00000000000000470900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f5939013a02000000000000d80b0d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f5939023a02000000000000d80b0d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001313ea2ca301a5e17a28cb2d4b11014eaed6c09c8bb27b777993c556d712aa84d01b603000000000000c0510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001313ea2ca301a5e17a28cb2d4b11014eaed6c09c8bb27b777993c556d712aa84d02d80e00000000000000470900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f011700000000000000e0040700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0242000000000000004024140000000000030000000000000000000000000000000000000000000000000000000000000003000000000000000195ab21fa90d692e9fe90bade8cd132b0d17d75f25efa1fab73239d188617366501b6030000000000007094000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000195ab21fa90d692e9fe90bade8cd132b0d17d75f25efa1fab73239d188617366502d80e000000000000c05102000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000019d7a22d6b2f60ddeaf9f0b43a147004709d988a73527b65d08e0ea6372f910db013a0200000000000040ca22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000019d7a22d6b2f60ddeaf9f0b43a147004709d988a73527b65d08e0ea6372f910db023a0200000000000040ca2200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c2c18a1d4c5f9a784336c16e2755ec99f12ba6990cacb81cb23f94aa048d6034013a02000000000000485904000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000011600000000000000781e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000002cd000000000000003cf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000001720000000000000098850000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000002580000000000000020670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007000000c0f5220400000000801b680100000000801b680100000000000000000000000000000000000000000000000000000000000000000000000000000000000000001856620200000000f00000000000000008000000a6c37ecf5177e5e8d350fcc7c97ae32f9046bf15337e07d97453cd8dc79f4aa36d8d6332a0f2a20836f88a057863237aabd71abb0b5f41b8fed92fbbf04d851f567827e521b3ea570cdb126bc2c44e7940eb33690b8244e00abee459e82a8302", + "ledger": "0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffff00000002000000000000000000000018600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018566202000000000c2a9846621d91f9309170b29b95e158e278002ce4cc03a2d097917836a391c0ffffffffffffffffffffffffffffffff000000010000000000000000000000d0870c0000000000a087630100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006dde44606fd8377d9fa0809b2b36b708de3e6119d8c1c3d1f0d675917c28c6198096980000000000ffffffffffffffff000000010000000000000000000000709d0b0000000000607a60010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b9091845dcf9a7fc8e2a12e1e1b2f986888b33fb22f1b42b384cdf47ecfc33a0ffffffffffffffffffffffffffffffff00000001000000e004070000000000c8431200000000000000000000000000801b6801000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf4a5892c8a1bef2afba730a4ec868d0830f9355d10f540b757205945c588cb5ffffffffffffffffffffffffffffffff000000010000002c720200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d1ee853e5c092553edc99a0cc2468a7307afd83533a3a34f401b0695da343314ffffffffffffffffffffffffffffffff0000000100000020e53600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d436e9616073a533831cfbec646020b5d4dcc9d1df4b792ccaff6e7e23ee686affffffffffffffffffffffffffffffff0000000100000014b400000000000084e022000000000000000000000000000000000000000000801b680100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e433cf0201e9b443d808e4406925b6a4a90e9963c6235c742a0ecec87a59e478ffffffffffffffff6400000000000000455552010000000000000000000000d0870c0000000000c0f35e010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + { + "record": "574d4831012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c1020000000000000000801b68010000000080de8002000000000400003a9800000000016e360001050001025980e26a212cf4c098b0f8a1a36a15333c6975497e60c073b9cd8242943b4c01b603000000000000c0510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f5939013a02000000000000d80b0d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001313ea2ca301a5e17a28cb2d4b11014eaed6c09c8bb27b777993c556d712aa84d01b603000000000000c0510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f021700000000000000e004070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000195ab21fa90d692e9fe90bade8cd132b0d17d75f25efa1fab73239d188617366501b60300000000000070940000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000b2083f6a8d3e1c901b93bfb87f5ffe83344214430e91d08f4e422c5ac7f0c0869f25a4e826f34f770f8f63bcea36ec697bbc398f81c06aa8ad11bb1f7a33323de1911cf31bd046195483ef8a8b22a5778adb66cfde02d9e8f8e46360a5577b06", + "ledger": "b9091845dcf9a7fc8e2a12e1e1b2f986888b33fb22f1b42b384cdf47ecfc33a0ffffffffffffffffffffffffffffffff00000001000000c843120000000000e004070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + ], + "descriptors": [ + "57434431012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad6b5a6849f61a8bbd9d4db526a9fc1c73ef949590cdcd9b0b65ce6375262d9bd0000000000000000a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c10005d00201101010010108000100086665617475726564056f72646572000000000000000000000280bb0000046761696e0370616e0c0c13afc5df1dffed9f80c566f13be392cc90ad1ceba5db69473a7993f8a7ff2e64cb5d094b2fdf651392e4a2d4f317cc6a436ab9b0cd5afe626493549677937002" + ], + "compositions": [ + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f000127a00000000008a826f7851f7a89f5ecf581141292c7f69e9d004ed08b01d25dd3c66e30f43f602540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f08000000000000000000000000000000c264cd78485650e03db23e16774c0566ac52d21be1b9edacaad39fa34dc3100c01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f593908000000000000000000000000000000e43f0568538eef406fb5ed7901b131c0b789d9c0fdb04d61d590af9bec169a1dd2ed35929ea1d408e2da06ee1c840bacab38929016f1a7ba820a80472485c9c6159b98672266a380458e260007c2baec97749e4941eb6ac370d1660fb0e5f900", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f04054890000000000ee27588426463e6a765b4b8b5791670a7ad61be12ef1995102f9220928d6038d02540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f090000000000000000000000000000005e05448cb224cb6aa2726e4c0fe448b127e032169bd2ffea55b4693d53ef487f01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f593909000000000000000000000000000000f993d88a6b1a587e0980017103aa4fcdbdf1d7b1c948f95a8bfa36a26493bb1e89b7c717020a8a296d3688d7988d8f2e70e54228d60c58514564a12937b2bb9421199fdee27ccf49acb52a32fa0f698b3eb5b28c35c47a797ae5e7d531f7db0a", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f08096980000000000d8bb5ef530c1fe8d1a0478d5ae231aac802d9f99dff906a66aa111420948048a02540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0a000000000000000000000000000000539e2bb145135d7de5b00ebf1b1178f83ef60d1b5ceedabf98bbb424a1d0a96301e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390a00000000000000000000000000000031a3cdcd31f70593af11aaf7bc4d22cc1ec5c18ee0d918fb2edd5a9c98b5a4758e74da34c2fb00e235278a3010c44dac99131fa49acc910e04f3929e7a6014833b553fad2cd31d127ad862b76999324556e76388192020c8022db9db6cafb803", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0c0d8a70000000000c65182e1c2868d42d9c1a244545f3c3416b14e481ade5c42b1979419f360b3e502540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0b000000000000000000000000000000491fe4bc5c4bafa0066ec9c2baf4b4287fcd09a778e36d5996a97ab827b7eaae01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390b000000000000000000000000000000414a7eb6f2353358646d5b861979c533b344cc01fb9dafdb9d6518c4ad7e3f6f01a03c915891dac04736b7858fdf34c4137e10f593ab5cb3a64900914b360f6b242ee3ead8c5499f325411942dbe827bd51176f2b049c4453a654c49dc701b09", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0001bb70000000000381618c2dfecc2dea3881cb1d4ed69e43dec01f07ebda92e08cd08ff935ed44502540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0c0000000000000000000000000000001915289342627f7f5a8bf4ccaf04c0418735ae65e41ca8692ebd3fca27ea764b01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390c000000000000000000000000000000194d05dbb7218051137b7537fcb320653e7f67071a19ae363969aee47cf08bede3b8b1012f1fc03a132f8793e0eae4389c23da524a7f6482a1d5cdf26ff04249cb81d4f19d8b164ec8eb381c66ba2704c57c7bb6f2389114f14bbfd632199302", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0405dc600000000007629d9fb8d6ac9e846d7ebdbe0a3a64b29ad51f7e4db40be5f96e447abcf6f0202540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0d000000000000000000000000000000f98b3489983bd0476673dc05db4f09c092411512ed2da6578e9c254eee0d72f501e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390d000000000000000000000000000000bc87dab2315f8ea4f21a4b5adcb838b8d89f6d67f93bbdf5bf175069ad81fa5d123e5e3721d1b781cfcc4650a2d571df1142f2160b377be26b692636e885cd037553b9041ac8f13b1f2064568957993ff69556b8673f0ad6e6caafd8b4887c07", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0809fd50000000000d32fbadccdb9742ffff99344fe07069e2e89b1d7f2d99859ad48336fadacd7c202540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0e00000000000000000000000000000053c09229456148744a940ba828e839eca0cf46d7c15e2c3533b4c65dcb0b4ff701e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390e000000000000000000000000000000b5702d26fbc4708196f576c1d46b7fc92471154fe81689707069bcb43173ecedc36859b37258c17a4294d56d36befaa9fbd975ce62e5deb542372aef4c9240b43ba584fc98e7c5a5066e1ba4060e96ca4e50daa116366c3aca9dfccef285f405", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0c0e1e40000000000fe93ba9f3d79ff2d3aeb01726ac4728adf60676494fbd0e596f5972be5f4639102540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0f000000000000000000000000000000341b9ec871ad41102a671b153ccd014f32cfe167bf5cca3989470a574196d0a401e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390f0000000000000000000000000000001eafd27ec73cf8676b02ebb4fa82a5bc67b2298a442322797db8cd996f8866d7bfdca1dadbfc723c27a3b6963a74c78570a41403a9cb454643631157e8bddbda8ccd54062aeec216b94406595aeb140b59afd5e697733f45980831357c0bc804", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f00024f4000000000092a195ef7c5e97512b5eed1dd778db0bd1f1c75cf8cf25b61ec800296e459e6302540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f10000000000000000000000000000000c693ed9bd4639c29ceb962b071e0531512929a139ebed7c5e12989d28b316ddc01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f593910000000000000000000000000000000c5043e90fe94a06a8b47242bb084c7dc5f8e46aebbe9b2c5a8db30d44d1a8b55ab981922c453fc2c78b58f7ec56293bb26d02c335079fdd1e246e33607ef473f513bd939cfe20da8b94af0963156c7218be49ffd1efbc9c14429fddf91bf5006", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f04066030100000000d00e25bb474153c30c4bf88f0fb890a0dd10342388c0be87aa93399d54bbaf5502540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f110000000000000000000000000000005f50d92c07b2f57953a701633e9d59575664bfe9b13644d5db11c5d04629b08c01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f593911000000000000000000000000000000339aa736ed16c43427079481a1b58bb9d370f104bbcba7a9d915ba1528b1a75da2dabf39b4d5d2518a8bef4340b8c5e7b32250ac222cc3e8648a42730b81eeac78da15f7fa7f42f7bb131311eb22aa7f89ada8940b2dd41c18c113a694a50808" + ], + "takes": [ + "57544d31012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f559425465004500032d6893f980a7dad483af70b4e116a10b7caa4e2052593699b2c4d5251f98cf34d11f86821c2cd542f6bbf5152a07ad54b271f65b25309018a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0f222415114ff3e773663df797c88d6e11b094c4ffc6694581d843dbfbe8ba1307020a0000000000000000000000807f612a01000000c0c1702a01000000300000000000000037090d56c333ad6c4392ffbb8f80b7854c7ae3d562e182d6b76685f99db2ec1f0100000000000000c0c1702a010000000004802a0100000055000000000000003b9b3da8c3121692dbe9b75ff7b37a77773cf7d71d8b7c885253d5b3855c53b902000000000000000004802a0100000040468f2a010000003a0000000000000097f5ee9829746c16c29946a44489846e4a385eb6d62804ba22a18a99a92642ab030000000000000040468f2a0100000080889e2a010000005f000000000000004bb0df0483b6370199dc9196f695ec65b029d8ffb09dfd7ec24bf5d5c59a72f3040000000000000080889e2a01000000c0caad2a010000004400000000000000733b22d5d80b9a80443b60a14334000a8ab43f139364fa9cd3a547e8a7c523640500000000000000c0caad2a01000000000dbd2a0100000069000000000000009395a39fcaaab116f14a5e4f1e51abe694d712e0f912827045471c6283730a480600000000000000000dbd2a01000000404fcc2a010000004e000000000000002b869dfefc2fe0873e06d23c09f1629470a788c6b8f2aa8e6aafb1298cd73fe60700000000000000404fcc2a010000008091db2a0100000033000000000000009f10cb7a3feccf63f5c0c398375eaf998628d314dc8159d15845fe5a0f8a006b08000000000000008091db2a01000000c0d3ea2a0100000058000000000000008b03281ec96d96d0e76e48ef184b7a60d4856eddf77629af961fe54746404ef60900000000000000c0d3ea2a010000000016fa2a010000003d000000000000001fb42eb87b4ea4dc72d02c9e6ea82aab91be4bbef122892b8921e2f552a462678883ea090e9f374f91bacaac0864af7b6dafa57c4f8a3ac0040ec7fc669f1f2506000000d7b83f99f213619227fbccadf7069c3f6cffca8e86f2aba6ffb8d15212698fa51be10b08c6e09a1b9314f45c65bf3c9b6bd507cfa5df3f013d50657245ee48c5602f2236c9c17b8a1cca6bae144f8f162627d27095a7256e1c5b8bc199c84fb3d535c9375be68b107355dcfeaab2a78192ba09570642f09de57b7bab1438eadc703cab1c2158dee2277fea9a9b909df1f7926a3a56996b7db1bfbaaba40ffa2970412cc3dc8306ba8f88774f1a622d1bf21b9ef51fb85b990d279f66fed977340159425465004500032d6893f980a7dad483af70b4e116a10b7caa4e205259369900000000000000000000000000000000000000000000000000000000000000001f250bafd7daed705bd36f8138b7419700ffe4c20991a2a46993dfd25b592a3643977ddbb511d2b51471498d51135c1a724a069a507032f2a3ff93aaa9787f0d", + "57544d31012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee2b956ba992dbd2d36d81fc4a4a771b7048b732a2e36ceea2f57448216ef566c6b7091c801fbc133089d9612f76d94401ffd0199edd399f9db0beaf3e8a611d45859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf692810f222415114ff3e773663df797c88d6e11b094c4ffc6694581d843dbfbe8ba130702080000000000000000000000405489000000000010679c000000000030000000000000008fae533ea900dd435a27c7ac6bbe43a78ca53f4ac8bf916accd92a8d374cba94010000000000000010679c0000000000e079af000000000055000000000000006cb63b81a4b11a270dfd91c37eaaed324b1b55999789174218e9990aa186455f0200000000000000e079af0000000000b08cc200000000003a00000000000000e515ec9e690d2bf9be7ce9bd8547b84406445c82682346f3cf84ccb305cf4ae50300000000000000b08cc20000000000809fd500000000005f000000000000008ab19bbbd6173bd1339419c327fadbf9337f7d77aff9e7082ee68da5132756c80400000000000000809fd5000000000050b2e80000000000440000000000000017fca124fd9c32581a3a6f032bc985481cef9852f5f07ba3120fd851511d20ba050000000000000050b2e8000000000020c5fb00000000006900000000000000be2b55d6011931c758ef4700f002f24a947e8e95428049653bc0fc44055b5563060000000000000020c5fb0000000000f0d70e01000000004e0000000000000083da93c67f107a2933b1befbdaaf558a44b2adf41cd3e085f1d48767e8dc3fde0700000000000000f0d70e0100000000c0ea2101000000003300000000000000989f78372ee677d01a1a45c0f4e33989e1e30935ef1f70e0426de38e2cb5ae8a5b8ad24298b11d4afe4acabf254c55d99e1badc4d20a3f293fef3c8e9870e6eb060000000571c24d887ed7ee13662e1b6b5916b05446674f04a48bb9657a88aa791aa4f78c7c1730862c2541afc6faf68f8f1a4b34c99f6c9ea331b8be3a60e97734ac814bcc3352c9690a6b19b9aaa4301286ce746571ec9a1dfd777ce3cdb1ab47ca1ea6e6c44a5d482193b91fac6a4b86aae708a8fe967079b4dc31dbe6e7da840cab0b041b0865ddff65ae4f592bac31b3e3627cb44ed3bfd4678f33dfcdd03c9e684fa04199a174e37753164a4ac950986de9a4abcc3c2cdce827249a9f063289b8012b956ba992dbd2d36d81fc4a4a771b7048b732a2e36ceea2f57448216ef566c600000000000000000000000000000000000000000000000000000000000000008fdd3b426a73d6a2e5ba04781aafc491c5950f29be46baca5bc57f7e54ba95b22dee29ffae444fb5eeee0e941d74e4e71f8dffbe4f45979a1e8c49aebc023c0c" + ], + "logs": "[[{\"v\":1,\"sid\":\"aecd92c612a3b9704723441772d4704e\",\"sn\":0,\"ts_ms\":1792108800500,\"event\":\"start\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"aecd92c612a3b9704723441772d4704e\",\"sn\":1,\"ts_ms\":1792108801100,\"event\":\"present\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":0,\"decode_us\":900},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"aecd92c612a3b9704723441772d4704e\",\"sn\":2,\"ts_ms\":1792108822500,\"event\":\"end\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}],[{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":0,\"ts_ms\":1792108800510,\"event\":\"start\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":1,\"ts_ms\":1792108801147,\"event\":\"present\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":0,\"decode_us\":900},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":2,\"ts_ms\":1792108805510,\"event\":\"stall-start\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":3,\"ts_ms\":1792108805910,\"event\":\"stall-end\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":4,\"ts_ms\":1792108822510,\"event\":\"end\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}],[{\"v\":1,\"sid\":\"59b8375b353f73cd469c9ecaf790f7bb\",\"sn\":0,\"ts_ms\":1792108800520,\"event\":\"start\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"59b8375b353f73cd469c9ecaf790f7bb\",\"sn\":1,\"ts_ms\":1792108801194,\"event\":\"present\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":0,\"decode_us\":900},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"59b8375b353f73cd469c9ecaf790f7bb\",\"sn\":2,\"ts_ms\":1792108822520,\"event\":\"end\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]" + }, + "lesson_mixed_classes": { + "kind": "room", + "room": { + "descriptor": "5752443101d6b5a6849f61a8bbd9d4db526a9fc1c73ef949590cdcd9b0b65ce6375262d9bd0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7200406cd8e95d060040420f0000000000170068747470733a2f2f6c6573736f6e732e6578616d706c651000000004002c0100005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f000000000004086469726563746f720101020b1a0f00000000000000000000000000000000000000000000000000000000000009706572666f726d65720301020018110000000000000000000000000000000000000000000000000000000000000861756469656e63650001010018000000000000000000000000000000000000000000000000000000000000000a636f6d706f7369746f720201020402000000000000000000000000000000000000000000000000000000000000000004067370656563680101046f707573000001000000000000100000004000000080bb0000010f056d757369630101046f70757300000100000000000080000000fe01000080bb000002000b63616d6572612d3130383002020d617630312e302e30384d2e30380b617663312e363430303238010003800268011e0096000000f40100000005d0021e00f4010000dc050000800738041e00dc050000a00f00000000000000000a63616d6572612d37323002020d617630312e302e30354d2e30380b617663312e363430303146010002800268011e0096000000f40100000005d0021e00f4010000dc05000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011e00d0070000f401000002444546527d2038c499b62ca152978601e0dfa4f2e1c5b15e1ef68449ca33b1d6dcd497037e6ce23ed0aa7b849df375b2e89955d6119b4a878fd20be449be1312c6534501", + "grants": [ + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c5200000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a00000000000101020b00000000000000000000000000000000000000000000000000000000000000000057455031011a0f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000100011568747470733a2f2f72656c61792e6578616d706c650000bea5ab08f1545cb8dc513b29a89599253fa6e205f706abf0464328c151580465de8533f866bf72b43df493570544a0255c57f275f4cab2f63c86ad7521b3e600", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f500000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000103010200000000000000000000000000000000000000000000000000000000000000000000574550310118110000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000020102011568747470733a2f2f72656c61792e6578616d706c655900086665617475726564040074727565077375626a656374400035393432353436353030343530303033326436383933663938306137646164343833616637306234653131366131306237636161346532303532353933363939e8c84e771a717476baa11cf942325fe291a1df45c01de5500df09f929cfe4afda793a413095db8ef85c0834bd20532b4e665684a44d311ada6cb0c887942fd06", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000103010200000000000000000000000000000000000000000000000000000000000000000000574550310118110000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000101011568747470733a2f2f72656c61792e6578616d706c654a00077375626a656374400032623935366261393932646264326433366438316663346134613737316237303438623733326132653336636565613266353734343832313665663536366336cfbe463d94a52e508797cf955379ea84c17812512bd8ce814c994de9acbb1b3549879f17e06a51d786bc13c4fa644f39eb0e8afc6720e5157710a4eeb338df00", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c100000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000302010204000000000000000000000000000000000000000000000000000000000000000000574550310102000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000103011568747470733a2f2f72656c61792e6578616d706c6500003869c45831770faf0563fbece44f7cbcef4cd3fbf19d2a59aa34ee7b95676c956edc96c72ed89e2a11bae6c2ac04e150e899269f76ddc766018d439764d01004", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa78a50426ccfca7ce958e1c52db484fc712687799e1ff84bc4cdd4182aad891c600000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c6500006269a123cbac9dd31be7210d27fb2ee2fdd1f062db4f8546fc4d004435da090bdf022e6e8fad6d2e1220582094847a1c7607d330d6056ae1798ec2960c6b2b0c", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa764a37268db038e6a6700c23f1e1b6ded3fd817e2faea48b1e62cf20cc65cd000000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f00000000008096980000000000ffffffffffffffff00000000011568747470733a2f2f72656c61792e6578616d706c650000c4cbf1a57fe4987d85f84b0d28b1a8261efce7cd6cefabbc2881e56696f83f04c65b12a6398643b5cf09c71774cea97564acd3adc6e3ce5576aa431db40de90b", + "57524731012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa2d8bca2005c2ba7300d28cda48ba74c2217bc1990aa7a2b25931bcc89ca580c00000000000000000000000000000000000000000000000000000000000000000000000000000000f05ad16a0000000080bad26a000000000200010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffff640000000000000045555200011568747470733a2f2f72656c61792e6578616d706c6500008a8b973614ae2b0d1e85469d8cffe0e9d207fbbaf0a27e1af25583ba2926f1cc38f5cd8cc3d3cf4ec191227b9242ae798b2c281b684b71f2f7c59deaf981dc03" + ], + "key_log_head": "{\"grant_max_s\":86400,\"issued_at\":\"2026-10-15T00:00:00Z\",\"keys\":[{\"compromised_since\":null,\"id\":\"room-authority\",\"key\":\"d6b5a6849f61a8bbd9d4db526a9fc1c73ef949590cdcd9b0b65ce6375262d9bd\",\"may_sign\":[\"live/room\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"take-store\",\"key\":\"0f222415114ff3e773663df797c88d6e11b094c4ffc6694581d843dbfbe8ba13\",\"may_sign\":[\"capture/store\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"relay\",\"key\":\"0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72\",\"may_sign\":[\"live/relay\",\"energy/OnChipCounter\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"compositor\",\"key\":\"a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c1\",\"may_sign\":[\"live/compositor\",\"energy/OnChipCounter\",\"energy/ModelBased\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"ingest\",\"key\":\"3549cf93c7fb47a5cee15993419720433466b527ad7a7423ccc78de33baf2cc9\",\"may_sign\":[\"live/ingest\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":null,\"id\":\"meter\",\"key\":\"8be2039bc5d06e7cf9d0188c3f22c659f1687b08b69b97f50b134552c46da783\",\"may_sign\":[\"live/meter\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"active\",\"until\":null},{\"compromised_since\":\"2026-10-14T00:00:00Z\",\"id\":\"meter-old\",\"key\":\"1c5430e51de83d2adf16caf0c8ab6ffaeed421d6569611674a685117eb385180\",\"may_sign\":[\"live/meter\",\"energy/OnChipCounter\",\"energy/ModelBased\",\"energy/Estimator\"],\"status\":\"revoked\",\"until\":null},{\"compromised_since\":\"2026-10-14T00:00:00Z\",\"id\":\"relay-old\",\"key\":\"e079d6f85a4fb3fecc7f4a94cde29c757313f84e0bd726c9c9dfe640a5745931\",\"may_sign\":[\"live/relay\",\"energy/OnChipCounter\"],\"status\":\"revoked\",\"until\":null}],\"kind\":\"wai-key-set\",\"next_root\":\"fa2ec0c92dcc45c59d8ede47e5f6cbaa0f89e36186a25c7007ec718415288a8d\",\"origin\":\"https://lessons.example\",\"prev\":null,\"revoked_modules\":[],\"root\":\"93bd7020e78d0ced730d16b3df0385340bb6ad05773df662e19ec3d4735c5e5c\",\"seq\":0,\"sig\":\"412da30d6f3f35c71d6c8075a78713129497b1a28c2887990c99cc16dd26dc9c33593f894286ced846c36b597013b39e583d83ce42cb1ffa75812e9222bad104\",\"status_max_age_s\":3600}", + "chains": [ + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000104000d927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c52d436e9616073a533831cfbec646020b5d4dcc9d1df4b792ccaff6e7e23ee686ad90f34467ecd1ae1beee4715b6ad56a1066885f45de845b5e1254f022b9504b191eee829d5007a0391a30af95d9efc41cb6e7b9fe1bb4591e7f587b35fc12103", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720100000000000000427fb576b2ce3ae19b7f085dc335864687bcc94f64e8d47e8d7a0baab7acffc2000000000000000000104000e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5d1ee853e5c092553edc99a0cc2468a7307afd83533a3a34f401b0695da3433140315765b5e69cf9cda0aa975fff4ff243328d62ddd0d53798472165bba87b9921d60cadec42afa900b559ae469f7a3bb5870d6a180c30d368daca0aca4f43d05", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72020000000000000095ea09d688d1c33f265428db98aa2a268f391f6ffd908ab6c39843c8e591177c000000000000000000104000a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c1b9091845dcf9a7fc8e2a12e1e1b2f986888b33fb22f1b42b384cdf47ecfc33a0e053886f0bc7ed1fc288c3b8dbe6e2e009998f204f921e1ec247fc870003f99598f8216ff5cbda966841da96c98ae2a727607db2d9b08542e2fb9fce4f4bfa04", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720300000000000000ba5015a517cd3fdebc9cd865fc10df47e2e7c97a444dff51c5cfc647434a3ef6000000000000000000122400a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c100020301e449ecf9f6773169161edefbfdb034114849028534b79602cdd2538371a6b21f18b273b29fe0678f781bbf72c3d75bf17d03adc10a531d788d7e693f3858620a", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720400000000000000fc5a8acc446b029d3d479f4b66d09f53e968f32792aee1f4e278ccab6a195a4c20a10700000000000010400078a50426ccfca7ce958e1c52db484fc712687799e1ff84bc4cdd4182aad891c60c2a9846621d91f9309170b29b95e158e278002ce4cc03a2d097917836a391c028cea7fedad7bfdb6b7402c13da6e6b8c1077aa5290e1954f13ac0082da7cdc9d6ff62a798afe163a44575ce65b558f4266798d75e116d34c58f3c3ea8900907", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720500000000000000044bf302c9f47024ab231a640905353115ce057e85a04c509808e7fe9b21b254c027090000000000001040007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eeecf4a5892c8a1bef2afba730a4ec868d0830f9355d10f540b757205945c588cb507ef6ba4e6cecea09d1714d6dd41c963ca58cca289c9839eaf74eba473fc28eb2f5b6190e4da8b5fe796eca8212e2c915ec1459aa081a97de67ec5926f450608", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72060000000000000010ca0251df4b17967f3704e4d3bd4a308ad12fd640aa970e13908ee015aa985660ae0a000000000000104000a764a37268db038e6a6700c23f1e1b6ded3fd817e2faea48b1e62cf20cc65cd06dde44606fd8377d9fa0809b2b36b708de3e6119d8c1c3d1f0d675917c28c619f660a54c57820b95480c0840836278a125593043e58895bdb8e00fed6e0c1308731455a732b1baec43e8e68ee585fc2e272d3c5124e08fdb14c7183eacf33005", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7207000000000000004b49d8a56fe5947607740aef1061d356e7f6e3c83a21701e432405315b10cc5400350c000000000000104000a2d8bca2005c2ba7300d28cda48ba74c2217bc1990aa7a2b25931bcc89ca580ce433cf0201e9b443d808e4406925b6a4a90e9963c6235c742a0ecec87a59e4783ee48164e163b5b78d764f4380a58e1596c7a0b06a78cc26bbc130382bca7e5c863bf635a1f4b4b42cdcc6e788cd6a26e3f89e56af5e83b43b1d12b013282206", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720800000000000000f0719456a072c6baab714eaddccad1b7cd93fa97fe10ecf990edf381e0c1230a40420f000000000000122400e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5000101011fe5381fc3b2acb103f880c2fa9ad52b41f7e792f6dc84b320804531020b477c1dc21fb2afc6b440e441a462e0cb714eda89c7782b0e6c7f9c26f392bb57c307", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720900000000000000062e7b16ad9bfc59a985b0f3e5e68d0c6e28c73355d6be2e11cf8f945ae463ce40420f000000000000122400e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5000202032dbd24bcab531bb61e3759b44e48aa90009652aecc1ac2d66af737af13b577f47adf927eb619d29f59395164eac2f2d3a363c0e4d1847527e8d3b40da9c5ae02", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720a00000000000000fdab89363cafb8a44d66c7bd763a25da8917783ffcf4a97bfd36c70455c2307d40420f000000000000122400d927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520001000103d7b211d87104be28d03b83d15a94431fd9253524acd08eaea84a500ad9883c12cef58ca5016c10185fded407b57717ef13c5a600ed11818502271390122609", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720b00000000000000b2915db782fe866d5dbe91dca3b09c7e9d7d677045feb73cfa3e5a2881edc11b40420f0000000000001224007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee000101010e426f3883cc38a43b114bd429ecb3059f7d86df5569989b55cc45f54a44cd9e3a4a459961bb0a1af8030633d23fcbcb7d5fa39c0352ac0c760999e35b2d6908", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720c000000000000002b70c96549302cc9e63155fab6889ca46f91223f5690a2c21ab7081331acdc73002d31010000000000132200e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50002da3a459c92f7c05ffc10bfa3d65ad4387e2deb2b0152056937992ed445b4acd95eb5b93826b09f3bd959da3d8c87a7bbcd4d6f461bebbf8917e5991fb7367b0a", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720d00000000000000041812e956c700e749826b39425905b8034c109e6deda9b07dc4cc106e419b1a002d31010000000000132200e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50001cfebd12a777c322de9cbd0f5f7f47aa305d741a1887ca8eb9d5c5e88fbee77e52301a011319877a391756aef65f5fcce986d7956539612a97653fec894c4750e", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720e000000000000004a9d94ee96ec81db8f434cbf8d46940217d1ec0849ad66769beecda538455b34002d31010000000000132200d927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520001f9ecc31703ca5a1f00b6cdf9196e371c49a1cb0ab5bd5125835798e52017cf7501e0341746a3e9e81ff559f805b0de751bfea43b60edf33a7a1e808b06333b0d", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720f00000000000000e4fab4da5c6f3c166c5ce005bdc60c491c7137298872ca63fb252d8dad6c1925002d310100000000001322007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee000124288a152f94c15021ffa82d68b2c9fc1351e4b8fb4a21a345f6b85dcaa0bb65ec93c2f5d9ac406b54064316618582247c4166345156e9aeb3e390580403c00f", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b721000000000000000e8f976794e72bb3e1ac846a01f930aa7181468e136a25151275e77a34fca1c4ce09466010000000000132200a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c10002416e0b9f31fedb053ead6f7fe918a3542a9f4f715bad8522e9d77cfaa07d04dce798bb36c48ece9e60cc75deafe5a90afca0185ccf00401877717a3a5384e705", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7211000000000000007adb989ef3fdca75b2116a3c1d8ccc32f9726a370bb1fe74a778367b80ef9b13c0286b01000000000011210078a50426ccfca7ce958e1c52db484fc712687799e1ff84bc4cdd4182aad891c60182ff63588f819d513b3adf9234be70f92276eacbf740d9dd6a2887b35d67ae250874c98219fd203dd46380b790fa11523ef3d647af4bf2c29b6f767fd67d440d", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72120000000000000046d9ca416acfb977917660e77d1c025dd21fcab6b9c77a84be1ba34126ae4705c0286b010000000000112100a764a37268db038e6a6700c23f1e1b6ded3fd817e2faea48b1e62cf20cc65cd001961a1543c72a2ed7cef5e60a729057980ae0333d3ca172d0d7765ea3cb53e37869fc96d783d87f199d84e403a6590b9cee396e80d8d4a11ddf2c1ed7e1a09108", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b721300000000000000a5bc35e28e4dc80e1246f65538b5ee2d5ce1e63415726c1ab0424f4b3082ca49c0286b010000000000112100a2d8bca2005c2ba7300d28cda48ba74c2217bc1990aa7a2b25931bcc89ca580c01b85d69bae728a80312847181411cbf1b857c7c904c6e3ce393731ec05a8b902aa943ddc1177eb5a85d89d3b8188b368cbb8420bbafd9e518364260f9f7f83704", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7214000000000000009136003edf701bb862143262e21c22fef7493df56a28db3557c789a1e7cd2c4bc0286b0100000000001121007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee018afe46c99845ba0d7af81f03eb2c8c4067f34c8674cd33106729df8cfd81c48d21ea9df9ebc4662931d865c3a95683c870b3115a1383f2813967b278803c0109", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7215000000000000006f83d7703375750f5cca72fb4c1179c04f2b65202682adbdb3d67b1ea13fc91ac0286b010000000000112100e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f501d906868dc6a495c8188516e2e90a24fddd72396678bb9bf6a699921b0603cfae109e785229a8e9589bc9f8631239e41060170d7b1139b39050dbc85ad4d0ec08", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72160000000000000095a40cc34fa91bfb041448d1ceaa4d3a30716a6fcef532bfd452ff00242dbe72c0286b010000000000112100d927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520101de18404ca29dde16299566d3688d1188d8124540fd37c7df87457a4b87d94236d6ad5ba1bdaa6b861505e004378c094de1aa08a45057209e1f972f4443ee0a", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b721700000000000000ccd8e80adcaacc780695d2f91dd925f4a4e666adce7e40b7aed60d8e9fd41dd0c0286b010000000000112100a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c1017b0e0acca851520158b977db04889ec317c5b3b63399ffad2d8f1fa2a3e16a584163fe83cf09e6fc88bfd8320594a3c6d25f2979120a79eb4205f513158d4c00" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520000000000000000000000000000000000000000000000000000000000000000000000000000000080841e000000000000092500e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f501a00f00002de246b73724b5efe6fed8b8b500d8d4db72cd1a6bf1fe44a0fcbc1390d38c8b7c34efb79c4f52745b9d945ea0e09b83f2d3ea6e2ebc63ee72cc83963b457e0f", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520100000000000000b2c4d5251f98cf34d11f86821c2cd542f6bbf5152a07ad54b271f65b2530901880841e0000000000000925007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee01a00f00001bdab77a3690419feb21de999193a68bf7856807f63c37989d8b1d0d26bab830f17be4ae69e686f15b5f9f58510474fb68a294e32145e972430955bba78f5308", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c520200000000000000b7091c801fbc133089d9612f76d94401ffd0199edd399f9db0beaf3e8a611d4540548900000000000008160004000000020e00727573686564207468652072756e001895215217dce05b5b18cc8f8638ea08e01ad7077de8a741852f15db966b6df81d9603c321949de5dfe7c53fbc553afe7a2439bc18def689a61e9afd2858670b", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c52030000000000000078223b1dc7500f31098e2ef4ae4f615e1eaa5a06e5f3a19c0698970771536431405dc600000000000008170004000000010f006c6f76656c79207068726173696e67003dde71eca895d2587649a3888baef3917077bd6d500d9fcef2348c70e6590bcf16d2738a9bf9a49c30b14d22203fd088c8f492169118a20b323c955172daab07", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c52040000000000000091ef0040cd22f692bad52da2aa2ee4ff3495d571373249b21f7488b583368af40024f4000000000000092100e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f502159e513cebb1af6f51733013724825fbd4b3301b94c9c31c10b4011ab35bd7e4590002d82faadb93cb25ffafd19a236c5caed905ed849d7c246177d48908450d", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad927eb9f5070076df3e4dc37934542e59028145ef587af8e71dc001a4ba81c5205000000000000007ad53acd82e3e7767b6fe17bf1e5cbbefc07729bcbeeacbc71387e043bd5de8a0024f40000000000000921007d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee02feb7e48b31bf62d8b7974e1c15b19f2abfb6190cdf8912b00c9970b386745e81beec63bfc20c7f8046f392aa238f96f0a41a0c8f71095c4e7405c5b21ee55900" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa78a50426ccfca7ce958e1c52db484fc712687799e1ff84bc4cdd4182aad891c600000000000000000000000000000000000000000000000000000000000000000000000000000000a02526000000000000030b000900676f6f64206c75636baba18e36da1e7d557a55c40c03a8f89ceb77a4dfee113e44680f91c25b60bf8220d0cf49ab0a9f4d1b42b793b90e596a22107b80c7db3a6095749cc3e138bf01" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa764a37268db038e6a6700c23f1e1b6ded3fd817e2faea48b1e62cf20cc65cd00000000000000000000000000000000000000000000000000000000000000000000000000000000080a8120100000000000307000500627261766f94766cf3326e298ea016d3784bb748cce1ae77e4e939b4a75c7bdb3a912978c251a3935f53acea5081479ea000604095c3ce85a53df644491cdc5a0ccec3a803" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f500000000000000000000000000000000000000000000000000000000000000000000000000000000c091210000000000000c7e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da00000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000efad5feffffff0039276d6016497f70ab9d2a5f1ff179a1070b1016d8cdbde6791792f00ada0133af241e3b99a3fca2e21369598281e46c89c41553c97855022f565312fc32a10c", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50100000000000000d7b83f99f213619227fbccadf7069c3f6cffca8e86f2aba6ffb8d15212698fa5e0a57e0000000000000c7e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0100000002d7b83f99f213619227fbccadf7069c3f6cffca8e86f2aba6ffb8d15212698fa500000000000000000200000000000000880211aa301be185e15a6c1895c2d43367360f1104ca7e72fa3c576cf0437c1b000efad5feffffff00cff0422ccfafb46431eeeec478c887b9a5609ed42f7b3b79c0b4c20ae6d87c375a6160907635a9fdd4a3990b2a4dcfdcf4ca46123c7e44dddca935c3354baf06", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f502000000000000001be10b08c6e09a1b9314f45c65bf3c9b6bd507cfa5df3f013d50657245ee48c5602a9d0000000000000c9e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da02000000021be10b08c6e09a1b9314f45c65bf3c9b6bd507cfa5df3f013d50657245ee48c502000000000000000400000000000000c6cda45663cd175aa9ba5f30289803a8059baa9924a9f919ef40d91902ca7e00000efad5feffffff01bbf572927af2d03d45aa66c8f630bda9d29429e07e09a3a3d6c9a8d933326244bec776069cf37a98f02833ca20fb02b81e7ccd6dff6d55266db73157ecb1002955cc3aa3b3d1a698be7314dd222b0b61676b727c6e3ed6b93394f4c55dcd100a", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50300000000000000602f2236c9c17b8a1cca6bae144f8f162627d27095a7256e1c5b8bc199c84fb3e0aebb0000000000000c9e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0300000002602f2236c9c17b8a1cca6bae144f8f162627d27095a7256e1c5b8bc199c84fb304000000000000000600000000000000249f88d1dcac0f7d98b53d89fdbeb22c284ffc97c90ff18a1ee27980e3ce88bd000efad5feffffff01eb6ddc57c1c32eeef5f7ff5f2a73f33e16684a4b4249d3cb0b99f72adb199a21bf6c5f49688d465cbdcc26c2dd73ed5b9ec157bf86dba5d2dc0b44e4112193a511a1fe886a482e6876c09119c470ce35e7a046f9d60df4b4085bb7c2093d9309", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50400000000000000d535c9375be68b107355dcfeaab2a78192ba09570642f09de57b7bab1438eadc6033da0000000000000cde00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0400000002d535c9375be68b107355dcfeaab2a78192ba09570642f09de57b7bab1438eadc06000000000000000800000000000000cef53df7a17ba27234381bf4fb47ad712b9f2aab80d4f837db1155d18af26a55000efad5feffffff03eb6ddc57c1c32eeef5f7ff5f2a73f33e16684a4b4249d3cb0b99f72adb199a21d75f76241ecda381c6d8d9232daab10b13803e2fe52ab86871c6bca67199fef3c6cda45663cd175aa9ba5f30289803a8059baa9924a9f919ef40d91902ca7e009d53f633fc2fae20b44ccc38918c8ac026ebd6fe90d3548fbe195fec26564bbdfa8ae5862663306a82c0680fc9ad127d5cf6304e92beae4ec0db27c4b54e8700", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f50500000000000000703cab1c2158dee2277fea9a9b909df1f7926a3a56996b7db1bfbaaba40ffa29803efa0000000000000c9e00a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0500000003703cab1c2158dee2277fea9a9b909df1f7926a3a56996b7db1bfbaaba40ffa2908000000000000000a000000000000008883ea090e9f374f91bacaac0864af7b6dafa57c4f8a3ac0040ec7fc669f1f25000efad5feffffff01b09aef291cbbb9927cefbbb3a26f4c2d4689a03a6b1cc7f938ce9c615890e9dd16de4bf19b3cfced2d0c7afec09ffdbafe2275901f940330ff39faecfadbc6adf3a784f003d44b0153b753eb6afc1cc30c047dd373457fd21f31de384709060b" + ], + [ + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee000000000000000000000000000000000000000000000000000000000000000000000000000000006018230000000000000c7e00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf69281000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004039d2ffffffffff00727cd67bbd0a01562d596da5eec92f54c9f9ff4c863b4f7eac3a1d9bf2c762f287a80b347596f5f98eb11463381d7729ee84dbc2afbb52d38d4172e23497d005", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee01000000000000000571c24d887ed7ee13662e1b6b5916b05446674f04a48bb9657a88aa791aa4f7802c800000000000000c7e00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf6928101000000020571c24d887ed7ee13662e1b6b5916b05446674f04a48bb9657a88aa791aa4f700000000000000000100000000000000ea1a9c44b721ca2a583fad19bca966298c4958034505c8143c6779e0b73bbd9e4039d2ffffffffff003171796be71e8f39fb36708f2ea8a5c0f9290bb7e2f029298d6df465b6b6f7f89715239ea8eb94b4b5e1747c48c82b01ea80c183879adca17efcaf3a45146204", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee02000000000000008c7c1730862c2541afc6faf68f8f1a4b34c99f6c9ea331b8be3a60e97734ac8100b19e0000000000000cbe00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf6928102000000028c7c1730862c2541afc6faf68f8f1a4b34c99f6c9ea331b8be3a60e97734ac810100000000000000030000000000000075ea44294bf1e20d670345b52e104bd360523168f8b0046b23a6bae059558c5c4039d2ffffffffff02e1a39c21b47bc7d6c78d0678749d84614817a2fb9b0a7dcbf19e940ff564df98a0a547193b22c6cc66acb6f6c926bbb61155ada07da896581acf52eaab6c2112b0c76a9550457dd487ed407b529c2f5e6df599da73b966801709654bd5991bd3ad6a930521ff1b4c63561f13a285c964df44cf4eea7f2ff5c013070e652ae001", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee03000000000000004bcc3352c9690a6b19b9aaa4301286ce746571ec9a1dfd777ce3cdb1ab47ca1e8035bd0000000000000cfe00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf6928103000000024bcc3352c9690a6b19b9aaa4301286ce746571ec9a1dfd777ce3cdb1ab47ca1e030000000000000005000000000000003935b6c803043cd12ec5bd53c252b49dfc9ca51a329fbc669de2c40288a414934039d2ffffffffff04a0a547193b22c6cc66acb6f6c926bbb61155ada07da896581acf52eaab6c21123ef26fe17f9c983fbd0f910c1b02459555aa96cf6bcb0b9b4ee374c6cbbb4890903e2f87b64d80ebb638e313ac851980a6667905bb6fca955b2786600ea9cc45c3213641a266ecdba0b17573dba5aa10ad9d831686d7e4e4248174f065e0c696a3a31821a93f6e49030d4cf063b8fe4cdf87bdb4e06b59ede349228bbb2d6c3ff83d087fcbb4e8ce4f98a341842c838b0aa852b70587bf0bb1934ee98cb0ed00", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee0400000000000000a6e6c44a5d482193b91fac6a4b86aae708a8fe967079b4dc31dbe6e7da840cab00badb0000000000000cde00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf692810400000002a6e6c44a5d482193b91fac6a4b86aae708a8fe967079b4dc31dbe6e7da840cab050000000000000006000000000000009a192de50070c39f440893ca66028c0fde3db238081fadf11e676d5cfd6e16bd4039d2ffffffffff03c3213641a266ecdba0b17573dba5aa10ad9d831686d7e4e4248174f065e0c696de40ac758d31d7dcaeff289171af8d3adc07c59cab6f3faaaae3ab769b9d9ce8c40d49c1602bd6fb0f81d107ddfcc04e9e08ba6bb91af36c94d2a7157e8b7a32b1e1ee7336f42352bf73ecbe68563bf291473c749aea18045e1f1f886c28c1cbe4e4c236a411f76e71fb0917b6e2c4ecdb404934f1dfb4af040bcf2c4dac2d01", + "57455631012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee05000000000000000b041b0865ddff65ae4f592bac31b3e3627cb44ed3bfd4678f33dfcdd03c9e6820c5fb0000000000000cde00859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf6928105000000030b041b0865ddff65ae4f592bac31b3e3627cb44ed3bfd4678f33dfcdd03c9e68060000000000000008000000000000005b8ad24298b11d4afe4acabf254c55d99e1badc4d20a3f293fef3c8e9870e6eb4039d2ffffffffff03d3b72f96114a9bdb8b730b48a4075a66d0b5c8641ad0c2e05e35a52e0baa7cb2b946fd25fcd2fff7393541ee8d0fd387535cd9280821cdabfe528936c6784e88c40d49c1602bd6fb0f81d107ddfcc04e9e08ba6bb91af36c94d2a7157e8b7a32acef80c4845b7fb19f6fd811a561f427d385f5066516b6ffdc70555d940e0f9f70edee43ed38e24e8d6bf93e37bfcea45df6423cf20d46b0a4bbe0ec33232501" + ] + ] + }, + "receipt": "574d5231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa8be2039bc5d06e7cf9d0188c3f22c659f1687b08b69b97f50b134552c46da783000000000000000000366e0100000000039f2e96e3cf68df9d2fce5457c50c8469006e663b04a03ec8d389b77a51a646b7a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c102c00e16020000000002c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720100c31801000000000400004e2000000000016e360001d969cc740d80efd2c77bb615616ea1325471a58c89a1740db6d40fa8c85be6a83549cf93c7fb47a5cee15993419720433466b527ad7a7423ccc78de33baf2cc90380841e0000000000030265b94ec2ec2932104a7583c08050f61547332fe434464d598cad6464025b3040b598d58ceff895b484641dfdea9bf329f40b464895eb31b2271a83af17cce7fb0a000000d2e2268edd1bfc8a0dbebf1f2287b93e200bc8affd02c3e92ac872b3200731a103000000d290530d77935fe173d0d5dcbb2588170f730b423f93543b4f3ceaee06d4e78e2055cc0400000000c0f5220400000000801b680100000000801b680100000000000000000000000000000000000000000000000000000000000000000000000000000000000000001856620200000000070000000000000040548900000000002c00000000000000f000000000000000002d31010000000000030201000000c00e160200000000030100000080841e0000000000040100000000c3180100000000455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d0500006400000008000000e45bdd02312646cd0d657cdf9fde296aed5f71f1a87b5625b362ac275d4800d8000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000089110cc6b6f305631eb3bd06672d082b59ff39d832cc6d54a3985dcc1699641c23c94ae04ea08f8827f9e33df048257c166fa9db3aad23f10d9130c5a67e8a02", + "hops": [ + { + "record": "574d4831012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7201000000000000000000366e010000000000c31801000000000400004e2000000000016e360001110001025980e26a212cf4c098b0f8a1a36a15333c6975497e60c073b9cd8242943b4c01b603000000000000c0510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001025980e26a212cf4c098b0f8a1a36a15333c6975497e60c073b9cd8242943b4c02d80e00000000000000470900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f5939013a02000000000000d80b0d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f5939023a02000000000000d80b0d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001313ea2ca301a5e17a28cb2d4b11014eaed6c09c8bb27b777993c556d712aa84d01b603000000000000c0510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001313ea2ca301a5e17a28cb2d4b11014eaed6c09c8bb27b777993c556d712aa84d02d80e00000000000000470900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f011700000000000000e0040700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0242000000000000004024140000000000030000000000000000000000000000000000000000000000000000000000000003000000000000000195ab21fa90d692e9fe90bade8cd132b0d17d75f25efa1fab73239d188617366501b6030000000000007094000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000195ab21fa90d692e9fe90bade8cd132b0d17d75f25efa1fab73239d188617366502d80e000000000000c05102000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000019d7a22d6b2f60ddeaf9f0b43a147004709d988a73527b65d08e0ea6372f910db013a0200000000000040ca22000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000019d7a22d6b2f60ddeaf9f0b43a147004709d988a73527b65d08e0ea6372f910db023a0200000000000040ca2200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c2c18a1d4c5f9a784336c16e2755ec99f12ba6990cacb81cb23f94aa048d6034013a02000000000000485904000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000011600000000000000781e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000002cd000000000000003cf00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000001720000000000000098850000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000002580000000000000020670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007000000c0f5220400000000801b680100000000801b680100000000000000000000000000000000000000000000000000000000000000000000000000000000000000001856620200000000f00000000000000008000000a6c37ecf5177e5e8d350fcc7c97ae32f9046bf15337e07d97453cd8dc79f4aa36d8d6332a0f2a20836f88a057863237aabd71abb0b5f41b8fed92fbbf04d851f567827e521b3ea570cdb126bc2c44e7940eb33690b8244e00abee459e82a8302", + "ledger": "0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffff00000002000000000000000000000018600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018566202000000000c2a9846621d91f9309170b29b95e158e278002ce4cc03a2d097917836a391c0ffffffffffffffffffffffffffffffff000000010000000000000000000000d0870c0000000000a087630100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006dde44606fd8377d9fa0809b2b36b708de3e6119d8c1c3d1f0d675917c28c6198096980000000000ffffffffffffffff000000010000000000000000000000709d0b0000000000607a60010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b9091845dcf9a7fc8e2a12e1e1b2f986888b33fb22f1b42b384cdf47ecfc33a0ffffffffffffffffffffffffffffffff00000001000000e004070000000000c8431200000000000000000000000000801b6801000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cf4a5892c8a1bef2afba730a4ec868d0830f9355d10f540b757205945c588cb5ffffffffffffffffffffffffffffffff000000010000002c720200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d1ee853e5c092553edc99a0cc2468a7307afd83533a3a34f401b0695da343314ffffffffffffffffffffffffffffffff0000000100000020e53600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d436e9616073a533831cfbec646020b5d4dcc9d1df4b792ccaff6e7e23ee686affffffffffffffffffffffffffffffff0000000100000014b400000000000084e022000000000000000000000000000000000000000000801b680100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e433cf0201e9b443d808e4406925b6a4a90e9963c6235c742a0ecec87a59e478ffffffffffffffff6400000000000000455552010000000000000000000000d0870c0000000000c0f35e010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + { + "record": "574d4831012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c1020000000000000000801b680100000000c00e16020000000002050001025980e26a212cf4c098b0f8a1a36a15333c6975497e60c073b9cd8242943b4c01b603000000000000c0510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f5939013a02000000000000d80b0d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001313ea2ca301a5e17a28cb2d4b11014eaed6c09c8bb27b777993c556d712aa84d01b603000000000000c0510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f021700000000000000e004070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000195ab21fa90d692e9fe90bade8cd132b0d17d75f25efa1fab73239d188617366501b60300000000000070940000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000b2083f6a8d3e1c901b93bfb87f5ffe83344214430e91d08f4e422c5ac7f0c08649cfaf1251a47c0ca5568184a7fc6692ded2f5c7766d773c94369cc9bf95312edcc0e170119c7d837178dee0323371a710ba56d8abe79240b1818340f7cf3b07", + "ledger": "b9091845dcf9a7fc8e2a12e1e1b2f986888b33fb22f1b42b384cdf47ecfc33a0ffffffffffffffffffffffffffffffff00000001000000c843120000000000e004070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + { + "record": "574d4831012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa3549cf93c7fb47a5cee15993419720433466b527ad7a7423ccc78de33baf2cc903c027090000000000406f40010000000080841e000000000003020001313ea2ca301a5e17a28cb2d4b11014eaed6c09c8bb27b777993c556d712aa84d01b603000000000000c0510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001313ea2ca301a5e17a28cb2d4b11014eaed6c09c8bb27b777993c556d712aa84d02b603000000000000c0510200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000062c738ccb6c04434ead674b64b2980a37ced79bea5943c2e19d328e1c1e4eb82d04ef892d090700d3366190d4673b6f5fa4ec917302b92fb4d4db28f75016bf40ba2a0258a6b512b72fe74fb568741811595efff6194b4bd579594f7bfa480a", + "ledger": "cf4a5892c8a1bef2afba730a4ec868d0830f9355d10f540b757205945c588cb5ffffffffffffffffffffffffffffffff00000001000000c051020000000000c051020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + ], + "descriptors": [ + "57434431012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaad6b5a6849f61a8bbd9d4db526a9fc1c73ef949590cdcd9b0b65ce6375262d9bd0000000000000000a693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c10005d00201101010010108000100086665617475726564056f72646572000000000000000000000280bb0000046761696e0370616e0c0c13afc5df1dffed9f80c566f13be392cc90ad1ceba5db69473a7993f8a7ff2e64cb5d094b2fdf651392e4a2d4f317cc6a436ab9b0cd5afe626493549677937002" + ], + "compositions": [ + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f000127a00000000008a826f7851f7a89f5ecf581141292c7f69e9d004ed08b01d25dd3c66e30f43f602540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f08000000000000000000000000000000c264cd78485650e03db23e16774c0566ac52d21be1b9edacaad39fa34dc3100c01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f593908000000000000000000000000000000e43f0568538eef406fb5ed7901b131c0b789d9c0fdb04d61d590af9bec169a1dd2ed35929ea1d408e2da06ee1c840bacab38929016f1a7ba820a80472485c9c6159b98672266a380458e260007c2baec97749e4941eb6ac370d1660fb0e5f900", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f04054890000000000ee27588426463e6a765b4b8b5791670a7ad61be12ef1995102f9220928d6038d02540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f090000000000000000000000000000005e05448cb224cb6aa2726e4c0fe448b127e032169bd2ffea55b4693d53ef487f01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f593909000000000000000000000000000000f993d88a6b1a587e0980017103aa4fcdbdf1d7b1c948f95a8bfa36a26493bb1e89b7c717020a8a296d3688d7988d8f2e70e54228d60c58514564a12937b2bb9421199fdee27ccf49acb52a32fa0f698b3eb5b28c35c47a797ae5e7d531f7db0a", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f08096980000000000d8bb5ef530c1fe8d1a0478d5ae231aac802d9f99dff906a66aa111420948048a02540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0a000000000000000000000000000000539e2bb145135d7de5b00ebf1b1178f83ef60d1b5ceedabf98bbb424a1d0a96301e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390a00000000000000000000000000000031a3cdcd31f70593af11aaf7bc4d22cc1ec5c18ee0d918fb2edd5a9c98b5a4758e74da34c2fb00e235278a3010c44dac99131fa49acc910e04f3929e7a6014833b553fad2cd31d127ad862b76999324556e76388192020c8022db9db6cafb803", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0c0d8a70000000000c65182e1c2868d42d9c1a244545f3c3416b14e481ade5c42b1979419f360b3e502540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0b000000000000000000000000000000491fe4bc5c4bafa0066ec9c2baf4b4287fcd09a778e36d5996a97ab827b7eaae01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390b000000000000000000000000000000414a7eb6f2353358646d5b861979c533b344cc01fb9dafdb9d6518c4ad7e3f6f01a03c915891dac04736b7858fdf34c4137e10f593ab5cb3a64900914b360f6b242ee3ead8c5499f325411942dbe827bd51176f2b049c4453a654c49dc701b09", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0001bb70000000000381618c2dfecc2dea3881cb1d4ed69e43dec01f07ebda92e08cd08ff935ed44502540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0c0000000000000000000000000000001915289342627f7f5a8bf4ccaf04c0418735ae65e41ca8692ebd3fca27ea764b01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390c000000000000000000000000000000194d05dbb7218051137b7537fcb320653e7f67071a19ae363969aee47cf08bede3b8b1012f1fc03a132f8793e0eae4389c23da524a7f6482a1d5cdf26ff04249cb81d4f19d8b164ec8eb381c66ba2704c57c7bb6f2389114f14bbfd632199302", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0405dc600000000007629d9fb8d6ac9e846d7ebdbe0a3a64b29ad51f7e4db40be5f96e447abcf6f0202540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0d000000000000000000000000000000f98b3489983bd0476673dc05db4f09c092411512ed2da6578e9c254eee0d72f501e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390d000000000000000000000000000000bc87dab2315f8ea4f21a4b5adcb838b8d89f6d67f93bbdf5bf175069ad81fa5d123e5e3721d1b781cfcc4650a2d571df1142f2160b377be26b692636e885cd037553b9041ac8f13b1f2064568957993ff69556b8673f0ad6e6caafd8b4887c07", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0809fd50000000000d32fbadccdb9742ffff99344fe07069e2e89b1d7f2d99859ad48336fadacd7c202540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0e00000000000000000000000000000053c09229456148744a940ba828e839eca0cf46d7c15e2c3533b4c65dcb0b4ff701e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390e000000000000000000000000000000b5702d26fbc4708196f576c1d46b7fc92471154fe81689707069bcb43173ecedc36859b37258c17a4294d56d36befaa9fbd975ce62e5deb542372aef4c9240b43ba584fc98e7c5a5066e1ba4060e96ca4e50daa116366c3aca9dfccef285f405", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f0c0e1e40000000000fe93ba9f3d79ff2d3aeb01726ac4728adf60676494fbd0e596f5972be5f4639102540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f0f000000000000000000000000000000341b9ec871ad41102a671b153ccd014f32cfe167bf5cca3989470a574196d0a401e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f59390f0000000000000000000000000000001eafd27ec73cf8676b02ebb4fa82a5bc67b2298a442322797db8cd996f8866d7bfdca1dadbfc723c27a3b6963a74c78570a41403a9cb454643631157e8bddbda8ccd54062aeec216b94406595aeb140b59afd5e697733f45980831357c0bc804", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f00024f4000000000092a195ef7c5e97512b5eed1dd778db0bd1f1c75cf8cf25b61ec800296e459e6302540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f10000000000000000000000000000000c693ed9bd4639c29ceb962b071e0531512929a139ebed7c5e12989d28b316ddc01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f593910000000000000000000000000000000c5043e90fe94a06a8b47242bb084c7dc5f8e46aebbe9b2c5a8db30d44d1a8b55ab981922c453fc2c78b58f7ec56293bb26d02c335079fdd1e246e33607ef473f513bd939cfe20da8b94af0963156c7218be49ffd1efbc9c14429fddf91bf5006", + "57435231012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaaa693d88d083f28b47f2645ed8b2cff1c4c564f4dbef3f273acf037d6375060c185eb41cedc9e3c326c0cc6087de900fb1cd1f72636e2f81db1070f1675c148f04066030100000000d00e25bb474153c30c4bf88f0fb890a0dd10342388c0be87aa93399d54bbaf5502540adcb6a36dc21f91576d7d44c1a247caa4f09cd659376e20820bd77215fc1f110000000000000000000000000000005f50d92c07b2f57953a701633e9d59575664bfe9b13644d5db11c5d04629b08c01e57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f5159614c2c42bc1b1258aee7faf878dcc0ae9505962d2efde8ce5b156a44f593911000000000000000000000000000000339aa736ed16c43427079481a1b58bb9d370f104bbcba7a9d915ba1528b1a75da2dabf39b4d5d2518a8bef4340b8c5e7b32250ac222cc3e8648a42730b81eeac78da15f7fa7f42f7bb131311eb22aa7f89ada8940b2dd41c18c113a694a50808" + ], + "takes": [ + "57544d31012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaae57d3fbe7257b2118009296bf654b41c7823e730f86568799b01b442d7a300f559425465004500032d6893f980a7dad483af70b4e116a10b7caa4e2052593699b2c4d5251f98cf34d11f86821c2cd542f6bbf5152a07ad54b271f65b25309018a0bf0e356fbe70268d3166f7ac16582f4cde87f1ce46d109e4bd0562c3d436da0f222415114ff3e773663df797c88d6e11b094c4ffc6694581d843dbfbe8ba1307020a0000000000000000000000807f612a01000000c0c1702a01000000300000000000000037090d56c333ad6c4392ffbb8f80b7854c7ae3d562e182d6b76685f99db2ec1f0100000000000000c0c1702a010000000004802a0100000055000000000000003b9b3da8c3121692dbe9b75ff7b37a77773cf7d71d8b7c885253d5b3855c53b902000000000000000004802a0100000040468f2a010000003a0000000000000097f5ee9829746c16c29946a44489846e4a385eb6d62804ba22a18a99a92642ab030000000000000040468f2a0100000080889e2a010000005f000000000000004bb0df0483b6370199dc9196f695ec65b029d8ffb09dfd7ec24bf5d5c59a72f3040000000000000080889e2a01000000c0caad2a010000004400000000000000733b22d5d80b9a80443b60a14334000a8ab43f139364fa9cd3a547e8a7c523640500000000000000c0caad2a01000000000dbd2a0100000069000000000000009395a39fcaaab116f14a5e4f1e51abe694d712e0f912827045471c6283730a480600000000000000000dbd2a01000000404fcc2a010000004e000000000000002b869dfefc2fe0873e06d23c09f1629470a788c6b8f2aa8e6aafb1298cd73fe60700000000000000404fcc2a010000008091db2a0100000033000000000000009f10cb7a3feccf63f5c0c398375eaf998628d314dc8159d15845fe5a0f8a006b08000000000000008091db2a01000000c0d3ea2a0100000058000000000000008b03281ec96d96d0e76e48ef184b7a60d4856eddf77629af961fe54746404ef60900000000000000c0d3ea2a010000000016fa2a010000003d000000000000001fb42eb87b4ea4dc72d02c9e6ea82aab91be4bbef122892b8921e2f552a462678883ea090e9f374f91bacaac0864af7b6dafa57c4f8a3ac0040ec7fc669f1f2506000000d7b83f99f213619227fbccadf7069c3f6cffca8e86f2aba6ffb8d15212698fa51be10b08c6e09a1b9314f45c65bf3c9b6bd507cfa5df3f013d50657245ee48c5602f2236c9c17b8a1cca6bae144f8f162627d27095a7256e1c5b8bc199c84fb3d535c9375be68b107355dcfeaab2a78192ba09570642f09de57b7bab1438eadc703cab1c2158dee2277fea9a9b909df1f7926a3a56996b7db1bfbaaba40ffa2970412cc3dc8306ba8f88774f1a622d1bf21b9ef51fb85b990d279f66fed977340159425465004500032d6893f980a7dad483af70b4e116a10b7caa4e205259369900000000000000000000000000000000000000000000000000000000000000001f250bafd7daed705bd36f8138b7419700ffe4c20991a2a46993dfd25b592a3643977ddbb511d2b51471498d51135c1a724a069a507032f2a3ff93aaa9787f0d", + "57544d31012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa7d3b3098d82fddc8585faa3e84b8f2c13cb7fc723a01bfb8bbacfba4ed388eee2b956ba992dbd2d36d81fc4a4a771b7048b732a2e36ceea2f57448216ef566c6b7091c801fbc133089d9612f76d94401ffd0199edd399f9db0beaf3e8a611d45859f57a4fec068477f3a45544119f5202eaf32ee3796401d6319ec7b1bf692810f222415114ff3e773663df797c88d6e11b094c4ffc6694581d843dbfbe8ba130702080000000000000000000000405489000000000010679c000000000030000000000000008fae533ea900dd435a27c7ac6bbe43a78ca53f4ac8bf916accd92a8d374cba94010000000000000010679c0000000000e079af000000000055000000000000006cb63b81a4b11a270dfd91c37eaaed324b1b55999789174218e9990aa186455f0200000000000000e079af0000000000b08cc200000000003a00000000000000e515ec9e690d2bf9be7ce9bd8547b84406445c82682346f3cf84ccb305cf4ae50300000000000000b08cc20000000000809fd500000000005f000000000000008ab19bbbd6173bd1339419c327fadbf9337f7d77aff9e7082ee68da5132756c80400000000000000809fd5000000000050b2e80000000000440000000000000017fca124fd9c32581a3a6f032bc985481cef9852f5f07ba3120fd851511d20ba050000000000000050b2e8000000000020c5fb00000000006900000000000000be2b55d6011931c758ef4700f002f24a947e8e95428049653bc0fc44055b5563060000000000000020c5fb0000000000f0d70e01000000004e0000000000000083da93c67f107a2933b1befbdaaf558a44b2adf41cd3e085f1d48767e8dc3fde0700000000000000f0d70e0100000000c0ea2101000000003300000000000000989f78372ee677d01a1a45c0f4e33989e1e30935ef1f70e0426de38e2cb5ae8a5b8ad24298b11d4afe4acabf254c55d99e1badc4d20a3f293fef3c8e9870e6eb060000000571c24d887ed7ee13662e1b6b5916b05446674f04a48bb9657a88aa791aa4f78c7c1730862c2541afc6faf68f8f1a4b34c99f6c9ea331b8be3a60e97734ac814bcc3352c9690a6b19b9aaa4301286ce746571ec9a1dfd777ce3cdb1ab47ca1ea6e6c44a5d482193b91fac6a4b86aae708a8fe967079b4dc31dbe6e7da840cab0b041b0865ddff65ae4f592bac31b3e3627cb44ed3bfd4678f33dfcdd03c9e684fa04199a174e37753164a4ac950986de9a4abcc3c2cdce827249a9f063289b8012b956ba992dbd2d36d81fc4a4a771b7048b732a2e36ceea2f57448216ef566c600000000000000000000000000000000000000000000000000000000000000008fdd3b426a73d6a2e5ba04781aafc491c5950f29be46baca5bc57f7e54ba95b22dee29ffae444fb5eeee0e941d74e4e71f8dffbe4f45979a1e8c49aebc023c0c" + ], + "logs": "[[{\"v\":1,\"sid\":\"aecd92c612a3b9704723441772d4704e\",\"sn\":0,\"ts_ms\":1792108800500,\"event\":\"start\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"aecd92c612a3b9704723441772d4704e\",\"sn\":1,\"ts_ms\":1792108801100,\"event\":\"present\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":0,\"decode_us\":900},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"aecd92c612a3b9704723441772d4704e\",\"sn\":2,\"ts_ms\":1792108822500,\"event\":\"end\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}],[{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":0,\"ts_ms\":1792108800510,\"event\":\"start\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":1,\"ts_ms\":1792108801147,\"event\":\"present\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":0,\"decode_us\":900},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":2,\"ts_ms\":1792108805510,\"event\":\"stall-start\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":3,\"ts_ms\":1792108805910,\"event\":\"stall-end\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"b5b91401f0762725dedbee0f0e447680\",\"sn\":4,\"ts_ms\":1792108822510,\"event\":\"end\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}],[{\"v\":1,\"sid\":\"59b8375b353f73cd469c9ecaf790f7bb\",\"sn\":0,\"ts_ms\":1792108800520,\"event\":\"start\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"59b8375b353f73cd469c9ecaf790f7bb\",\"sn\":1,\"ts_ms\":1792108801194,\"event\":\"present\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":0,\"decode_us\":900},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"59b8375b353f73cd469c9ecaf790f7bb\",\"sn\":2,\"ts_ms\":1792108822520,\"event\":\"end\",\"cid\":null,\"st\":\"ll\",\"sf\":\"o\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]" + }, + "not_a_source": { + "kind": "statement" + }, + "package_base": { + "kind": "package", + "receipt": "{\"asset_id\":\"video-base-01\",\"components\":[{\"id\":\"v\",\"index\":\"index-v.wsix\",\"index_digest\":\"5ffc2be3f21af79078a1158e3d669e4268a35b0ca61a6e73ff252fdf38f346d0\",\"media_class\":\"video\",\"stage_root\":\"bd0c7799633b108cf9ccb0c7a4cedbdf6173a83470d3d96b94d075c03e0db7b6\"}],\"energy_provenance\":null,\"grant\":{\"capability\":\"wai.video.package\",\"funds_ceiling\":null,\"grant_hash\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"joule_ceiling_micro\":null},\"joules_micro\":0,\"kind\":\"package\",\"packager\":\"builtin:wai-pack@0.1.0\",\"parent_kind\":\"none\",\"parent_receipt_hash\":null,\"policy_digest\":\"e1c131d56798eb840e22736076d74908bca2b7585aeae7e4bb1698218c01a35e\",\"receipt_hash\":\"22c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728\",\"sig\":\"4554bd1c09aaa4af8799e37e2379cde9d341c7982a6d31a050162ee075c07f93c507abfda9c8223e70b9c56a4dbf9cee626ddaa680bd807fb4eda1fd2f885b06\",\"signer_id\":\"did:key:z6MkfMo6gxqdBhaHMNnmfhgZFBjpCDTkmJMJLoypsBZS9PwD\",\"signer_pubkey\":\"0d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0\",\"stored_bytes\":119779,\"total_bytes\":130963,\"total_entries\":10,\"unique_objects\":8,\"version\":2,\"work_units\":10}", + "indexes": [ + "57534958010000001900000001000000050000000a0000000600000003000000e1c131d56798eb840e22736076d74908bca2b7585aeae7e4bb1698218c01a35e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005867fc2ae20b6764311037b4dd14166d15e011b93481df24fdb2a116be659ae80000000000000000000000000000000000000000000000003c00000000000000020003000000000000000000000000003c0000000000000019000000020000000200020100000000000000000000000055000000000000003200000004000000020003010000000000000000000000008700000000000000190000000600000002000201000000000000000000000000a00000000000000028000000080000000200030100000000000000000000000024761a434c91c627f11abd6265371966b50573602a200dd7bb04e0d90889326d929b0000000000000000000000000000ffffffff040000000200000000000000f60c722a119439058495ee4b99919e3dd41aaf6abe6b39badbc7b0e9e9a157b558d6800000000000b600000000000000d09a000000000000000000000000000035c7a45323b7afe979c123fa885f35cc55fabd2a1954759aebe4b95dbc76361f580a0000000000000000000000000000ffffffff0500020000040000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffd3010000000000007908000000000000000000000000000061c7c3b7ccab07ed01df5b0d7ca0240598b914dbf2cabe545aa001e8de12d365de260000000000000100000000000000ffffffff040000000200000000000000ff43964375c1bd5277901262e213359564ae1d06c85ce9aca930247e28532f2c90821a0000000000b6000000000000001c2600000000000000000000000000007cc2b824ad6105abfccae27093190311b6a2348842e571684b021a213341b94ad2040000000000000100000000000000ffffffff0500020000040000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffd301000000000000f3020000000000000000000000000000c2ce1907255a041c388afd06ccd007436368e8d20467fdec2a1eebbb84b03bf22a8c0000000000000200000000000000ffffffff0400000002000000000000000ac192ed19d0db9a162adfaa0dc76669f392e98cb3a1d7e26e14db15d42f6be119e4690000000000b600000000000000688b00000000000000000000000000008f748dc64787b5c5ece081270c8559277f926322dd6ddc345306b8231f2d1752c7090000000000000200000000000000ffffffff0500020000040000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffd301000000000000e807000000000000000000000000000061c7c3b7ccab07ed01df5b0d7ca0240598b914dbf2cabe545aa001e8de12d365de260000000000000300000000000000ffffffff040000000202000000000000ff43964375c1bd5277901262e213359564ae1d06c85ce9aca930247e28532f2c90821a0000000000b6000000000000001c2600000000000000000000000000007cc2b824ad6105abfccae27093190311b6a2348842e571684b021a213341b94ad2040000000000000300000000000000ffffffff0500020000060000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffd301000000000000f3020000000000000000000000000000618e0a89be8c7e556b380326b219c2f6fbfc5a0cb97754e871c1c23a99bdb481aa630000000000000400000000000000ffffffff04000000020000000000000085521683dbd317477d96484c48fd2c5176a131968c0b04d944385ad53df931b3b5ee580000000000b600000000000000e8620000000000000000000000000000761c4649427c51796a2b699568775765b209ee80d2f4fc1cf040838c6724aa2dae080000000000000400000000000000ffffffff0500020000040000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffd301000000000000cf0600000000000000000000000000000100000000000000000000000000000002000000640000000000000000000000030000010000000000000000000000000000000000ff00ff00ffffffffffff000100760200747606006d6f62696c6506006c656761637914007761692e766964656f2e696e745f6d6f74696f6e0d007761692e766964656f2e61763100000000" + ] + }, + "package_no_index": { + "kind": "package", + "receipt": "{\"asset_id\":\"video-base-01\",\"components\":[{\"id\":\"v\",\"index\":\"index-v.wsix\",\"index_digest\":\"5ffc2be3f21af79078a1158e3d669e4268a35b0ca61a6e73ff252fdf38f346d0\",\"media_class\":\"video\",\"stage_root\":\"bd0c7799633b108cf9ccb0c7a4cedbdf6173a83470d3d96b94d075c03e0db7b6\"}],\"energy_provenance\":null,\"grant\":{\"capability\":\"wai.video.package\",\"funds_ceiling\":null,\"grant_hash\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"joule_ceiling_micro\":null},\"joules_micro\":0,\"kind\":\"package\",\"packager\":\"builtin:wai-pack@0.1.0\",\"parent_kind\":\"none\",\"parent_receipt_hash\":null,\"policy_digest\":\"e1c131d56798eb840e22736076d74908bca2b7585aeae7e4bb1698218c01a35e\",\"receipt_hash\":\"22c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728\",\"sig\":\"4554bd1c09aaa4af8799e37e2379cde9d341c7982a6d31a050162ee075c07f93c507abfda9c8223e70b9c56a4dbf9cee626ddaa680bd807fb4eda1fd2f885b06\",\"signer_id\":\"did:key:z6MkfMo6gxqdBhaHMNnmfhgZFBjpCDTkmJMJLoypsBZS9PwD\",\"signer_pubkey\":\"0d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0\",\"stored_bytes\":119779,\"total_bytes\":130963,\"total_entries\":10,\"unique_objects\":8,\"version\":2,\"work_units\":10}", + "indexes": [] + }, + "package_staged": { + "kind": "package", + "receipt": "{\"asset_id\":\"video-staged-01\",\"components\":[{\"id\":\"v\",\"index\":\"index-v.wsix\",\"index_digest\":\"6842a02b45fb2ddf8095621836901abdbf2526451d740976ffe7079fc210932c\",\"media_class\":\"video\",\"stage_root\":\"c10304943d547971b05fdb57231dcd6e097d7df38cc68ea92c376ddc957e022d\"}],\"energy_provenance\":null,\"grant\":{\"capability\":\"wai.video.package\",\"funds_ceiling\":null,\"grant_hash\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"joule_ceiling_micro\":null},\"joules_micro\":0,\"kind\":\"package\",\"packager\":\"builtin:wai-pack@0.1.0\",\"parent_kind\":\"none\",\"parent_receipt_hash\":null,\"policy_digest\":\"1bed669917d9d78e1a0f65a5a998db50e98e3dbdf836cc2ae00ff2ac97d67e57\",\"receipt_hash\":\"4441a653f101172ee9d4b76f788a10f23ab359e7bf3116fb09327699c2c83ef0\",\"sig\":\"f62bd725148a4203b440769906eacf7083895accc0191782f7e43197d92a598b90f1a5ea04eedd493dda87a5e622889562f0f09a5010cb053700b016f44cb504\",\"signer_id\":\"did:key:z6MkhGok4PzLmtw1v5uW3Bpip8gHHifrCt42QJn4gEBfRVQG\",\"signer_pubkey\":\"29e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d55\",\"stored_bytes\":484464,\"total_bytes\":566398,\"total_entries\":18,\"unique_objects\":14,\"version\":2,\"work_units\":18}", + "indexes": [ + "57534958010000001900000001000000050000001200000008000000030000001bed669917d9d78e1a0f65a5a998db50e98e3dbdf836cc2ae00ff2ac97d67e5700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000087b4a755fc396489e440b638763a46d57f4b9ecb2a7443b155da37b79d68951400000000000000000000000000000000000000000000000032000000000000000400030002000000000000000000000032000000000000001900000004000000040002010000000000000000000000004b000000000000002800000008000000020001010100000000000000000000007300000000000000190000000a000000040002010000000000000000000000008c000000000000003c0000000e00000004000001040000000100000000000000cf015505a36b0b76b50c41f15f3a0210163886054d53d35f1c4d414bf1c22d27ae650000000000000000000000000000ffffffff0400000002000000000000000f4143e04eb23d6ea0b5f5cc1febcc9e032f2058ff428442aa8c72d5c63b30a32fc6ff0000000000b600000000000000ec6400000000000000000000000000007aeabafc9c50d289b5f65a7480a88774f94be9eec5b706c16835a59bbe59a0ecc3080100000000000000000000000000000000000500010102000000dc0500009b3ee6552ac361d8304e9f40bd4bd1d83eea9be3144892b8fbc06c74b463f39cf77f3b0000000000430100000000000074070100000000000000000000000000d955b5f2232046829b6a5da0c436412c91e97b7d0c16afb07e5f6e68a39ef66c1b510100000000000000000000000000010000000500010202000000dc0500005eee77c41d8b6db8edc1807477118bab174bf8b8da3e40910608306a2080fd93877b0700000000004301000000000000cc4f01000000000000000000000000003522115bc258246c4dd67879e2b9c10f3be41cbc88aec1254e1d26e9d87b442309090000000000000000000000000000ffffffff0600020000040000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffd3010000000000002a0700000000000000000000000000002b95867d323012d663b0f8c88fc8d8d0c3b5642a9784f00d30ce2ab176341c61e2250000000000000100000000000000ffffffff040000000200000000000000ebc6c6c51a30bd0207a309bd4dec48590d2d1955cfafeb79510f0fc3bcac7c7000b35b0000000000b60000000000000020250000000000000000000000000000656e5f72c8b8165d9f59f30a6cbca465383cc3d478b6474e2a91e592796f320beb770000000000000100000000000000040000000500010102000000dc05000072c3e4f23c6c2cc93d9b58bb1bc22d2e25502c1c664789774ddd22a90d8f91f5008025000000000043010000000000009c760000000000000000000000000000f5cacd6f092d317d0972e3d8ed4efcf4702b820bd5295aebdd252dd8dbc409e96f9d0000000000000100000000000000050000000500010202000000dc05000037d05e053d5151577543e08a954c1b66d3600d01c7a9ba417ddd387d937ec73500b00400000000004301000000000000209c000000000000000000000000000066c03382125a5a72828ca5223599007817784a12129e431066290f8ff5df562ed2040000000000000100000000000000ffffffff0600020000040000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffd301000000000000f30200000000000000000000000000008173f0eca11aaae0c29d415aedb60252cd8c8c32460c13e124ad70d9a4acd90fce030000000000000200000000000000ffffffff070000000200000000000000fcd856376835704fe46e56316d34382941e1201e6c3ac2fbf59875e5d35245640000000000000000a8000000000000001a030000000000000000000000000000a411541dd2eb555c9fd5b6dfe0cd00baae6574568225afa839e36af62f6bbdaebc070000000000000200000000000000ffffffff0600020000040000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffcc01000000000000e40500000000000000000000000000002b95867d323012d663b0f8c88fc8d8d0c3b5642a9784f00d30ce2ab176341c61e2250000000000000300000000000000ffffffff040000000202000000000000ebc6c6c51a30bd0207a309bd4dec48590d2d1955cfafeb79510f0fc3bcac7c7000b35b0000000000b60000000000000020250000000000000000000000000000656e5f72c8b8165d9f59f30a6cbca465383cc3d478b6474e2a91e592796f320beb7700000000000003000000000000000a0000000500010102020000dc05000072c3e4f23c6c2cc93d9b58bb1bc22d2e25502c1c664789774ddd22a90d8f91f5008025000000000043010000000000009c760000000000000000000000000000f5cacd6f092d317d0972e3d8ed4efcf4702b820bd5295aebdd252dd8dbc409e96f9d00000000000003000000000000000b0000000500010202020000dc05000037d05e053d5151577543e08a954c1b66d3600d01c7a9ba417ddd387d937ec73500b00400000000004301000000000000209c000000000000000000000000000066c03382125a5a72828ca5223599007817784a12129e431066290f8ff5df562ed2040000000000000300000000000000ffffffff0600020000060000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffd301000000000000f30200000000000000000000000000009fb263069ffb6a50052e79baeeee7c5814106011d57be9c4f6396c5dbd9902a97a7b0000000000000400000000000000ffffffff04000000020000000000000056c9c3945ffd189a58ae0508951f1e2e8ac29f34d4aaf6def14b25ce1d45acadfbef360100000000b600000000000000b87a000000000000000000000000000098bb5bbdb41e89f39288a59eb469b152e55b175983e46521ed2bb40999fc511dff3d01000000000004000000000000000e0000000500010102000000200300001eeea03692e42ac3432af47de5127c774fc0fc3b46635656660db7b06beaf8f1c3e64d00000000004301000000000000b03c0100000000000000000000000000a59afe3368e48b835e3f0c170d8fc42b87fce91bb8567f7e8a9b091db0c51b87af8b01000000000004000000000000000f000000050001020200000020030000d0063b2270236649b327135cd180257818927461d2910b34fb54fa2639d10035d38a0900000000004301000000000000608a010000000000000000000000000093439520366affbd9ec0a51d57120d0e45a6b4651884ece2eaf5b8954c1880381b0b0000000000000400000000000000ffffffff0600020000040000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffd3010000000000003c09000000000000000000000000000001000200000000000000000000000000020001004a01000000000000000000000300000100000000000000000000000001020002020001000100ffffffffff000100760200747606006d6f62696c6506006c656761637914007761692e766964656f2e696e745f6d6f74696f6e14007761692e766964656f2e696e745f726566696e650d007761692e766964656f2e6176310d007761692e696d6167652e706e6700000000000000" + ] + }, + "relay": { + "kind": "room", + "room": { + "descriptor": "5752443101d6b5a6849f61a8bbd9d4db526a9fc1c73ef949590cdcd9b0b65ce6375262d9bd0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7200001b14125e060040420f00000000000000080000000200000000005745503101fe1f0000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f000000000002097075626c69736865720301020018000000000000000000000000000000000000000000000000000000000000000861756469656e636500010100180000000000000000000000000000000000000000000000000000000000000000010a63616d6572612d37323002020d617630312e302e30354d2e30380b617663312e363430303146010002800268011e0096000000f40100000005d0021e00f4010000dc05000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bfeaee6e1ec8d49668f0a9b74e6bf0884d494488654d2aabf011125ef453336b6e4c34a26c478a88eaa9a1f1e5b036fcde785f13f07bacf4f3dace2608f7560b", + "grants": [ + "575247310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102fffe32d9d821d4c96672440f5afc91e6c367649c074cbf89d682ca414d8a27aed00000000000000000000000000000000000000000000000000000000000000000000000000000000f0fdd36a00000000802fe56a000000000003010200000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff0000000100000000363d356ef31dd578c4a9efd3eee7c863ddb5f6ea8cb182a84671320bb9bd713e1c44976fc96085c51cd4037e57d484af6607ce757883c24ebaf65c21a28bc50f", + "575247310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f493ee2136de6e2135efae46582aafdfcc3b4b198480fd0f027f4ff4e1005b5dd00000000000000000000000000000000000000000000000000000000000000000000000000000000f0fdd36a00000000802fe56a000000000003010200000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff000000010000000031fe2d6c1cba19639616e75adaed9e1679566e03c2612a1321e75a72afd31d8162cfa3a7d65c85869df621a202f4e09d1e200259d272d79179acecab3c584a00", + "575247310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f3c62c6da5cf14bd5c78c760ab8086bf29c690ad4c47cc593f1a3cd0cc0b26ea500000000000000000000000000000000000000000000000000000000000000000000000000000000f0fdd36a00000000802fe56a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000404b4c0000000000f401000000000000455552000000000128f6dccae9dfb51b3ea76f801619b4f03ea354679c89224a5477f9a1287cbe5cdc94a495beb82e9d35aae398fa291a59d66aa1a98db71ddd0744db70c1a80f", + "575247310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f9e628faf610b22f4c3594642b36c8ee0763421b0e2c6a867977bd9e60d0283dd00000000000000000000000000000000000000000000000000000000000000000000000000000000f0fdd36a00000000802fe56a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000000000a8f8ec03ca50e3c5ca6ad98995823879c91b5766930f68b94cc4134a719978ed67aff93015a0f5ffc9c69eae40a556c3bd131c0cfd455df46f4aeb470f821208", + "575247310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102fc8643ce65f7e7091502f1e806fcecbead23bd50288fa53ab69dc042cc5ce035200000000000000000000000000000000000000000000000000000000000000000000000000000000f0fdd36a00000000802fe56a000000000100010100000000000000000000000000000000000000000000000000000000000000000000574550310118000000000000000000000000000000000000000000000000000000000000000004f4013200000040420f00000000000100000040420f0000000000ffffffffffffffffffffffffffffffff00000000000000ba96365f0dd40dcc04852d55da081ae354853262df2165c72e9967aab09158472935f6864e4561bcddfa9824302d956a6e4285180ee8f73cbf821af11508770f" + ], + "key_log_head": null, + "chains": [ + [ + "574556310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720000000000000000000000000000000000000000000000000000000000000000000000000000000030936bdb0900000000104000ffe32d9d821d4c96672440f5afc91e6c367649c074cbf89d682ca414d8a27aed510124baa39d99a07d9d6575698f70183759e3da8b458f8edb09f09215e143185516d0130512b9112b467f08cc8cdda6f755c559c0adb9bf3ff0c186fcb7f045d5e3ac32114c66f7c22a32ad53ad42e67123ceb5fcbdd7f4b3fe144fe1bbc90c", + "574556310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72010000000000000053dd0e2e849175ffffad517d73e223a9a9383672701bded1868eae4bc425dfc330936bdb0900000000104000493ee2136de6e2135efae46582aafdfcc3b4b198480fd0f027f4ff4e1005b5dd4fc2bb567c1e950b92bb2dd6796df52eb67f3e5c2e5753b3ee9bae483600f17e71c60745f45d2b0968b05fe1a9d4534b6811024fcfb72c6ecc0471c205b57d44547bcf4eec0de32d5a002dad3bb4874112f17957fcb2a9deb6d05565387bc901", + "574556310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720200000000000000ae3d338834295ebc56bcf05077d391be3528201592864af65fa7275a8184984f30936bdb09000000001040003c62c6da5cf14bd5c78c760ab8086bf29c690ad4c47cc593f1a3cd0cc0b26ea552786b7331441935c2143544988bccb4a4db6f5c76d734292a843a0b5e20e83df48b15a708baa3eae6b569820f828bb6022f43d7bf8e15213807f23501e1fdf2a03c1499aef65f08443ef6ad25461332019846fb74aab99b3a0ff487099aa500", + "574556310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7203000000000000002c4471fef05727665643645e343b49ead1023da1d584b5d24941573a43f51ef530936bdb09000000001040009e628faf610b22f4c3594642b36c8ee0763421b0e2c6a867977bd9e60d0283ddfec5bc703c31e2600d5a51cceb45eb2e5011ec2c1890f8dc5b96fd683ef85362780f10798634ad41c12c2ed77155e61f5517d820a0c9adb3cf56d832c3b3e9aed425f81cdcb5a9450764213c83a484da51fb06f008a01bc75bdfbfbf9210bb0f", + "574556310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720400000000000000199f2a9b831ca660d17034a012a2a32dc76fba041c352d93556245fa8bc9ffa830936bdb0900000000104000c8643ce65f7e7091502f1e806fcecbead23bd50288fa53ab69dc042cc5ce03526b4272b140a49c75d1dbedfca74343ad6c13a8a9d3a20429e7d457f30d2afd82a473bf4c106067b59d507efb614d95734a604a466062f74db108edf7df29698e4dc85b9eecc559720f1a4eab9295046c579673e24daded517f2aa20926918101", + "574556310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7205000000000000005ce91d0c7084543ed034342a5527548ff935d3fac048884c5f71e0d0d8ed5ea230936bdb0900000000122400ffe32d9d821d4c96672440f5afc91e6c367649c074cbf89d682ca414d8a27aed000200016a858ce60cfa61125065a0adb28315943412e5c8f761679b0e5a4e3e6090d2cc825fdcdb0bb80c0baf208632595c6625f1e07c2ecd555d29d31ba532a5e7250d", + "574556310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b7206000000000000006108c972594a70c642d97b989fae6c25b06baea59ee5403135cb4125e03c7e4a30936bdb0900000000122400493ee2136de6e2135efae46582aafdfcc3b4b198480fd0f027f4ff4e1005b5dd000200010df2871fea5b898e444044116f939f12d9044f9df1cffb66a90ba1276f33452529669a72d7582c42edf586bf9adbda7f921bc6a933cb253415eded37c431bd0a", + "574556310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720700000000000000888869a039d8f5af4f86cc4103d96d10bfdc70ee9a92e7ee38e5f56f63d882ab90579cdb0900000000132200ffe32d9d821d4c96672440f5afc91e6c367649c074cbf89d682ca414d8a27aed0002135a95471ff9c06cf0c3685b3db77a3f6b9454fe18d89769328726c44541a1cab6cfc9c1923093f5cd91982775ffa79d3e25c7062ae4497f9858846571031202", + "574556310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b72080000000000000003c8eedfc4554db79e9c2c8668b1151d80cd6f761b8ca760d09c2ab3c1bca30990579cdb0900000000132200493ee2136de6e2135efae46582aafdfcc3b4b198480fd0f027f4ff4e1005b5dd0002a1a2bfe42e568bca3a1584ef78a0aba34870310ad6879058193bb527e17ca0f2aa62f167c363918857e22f4bc90c61853ab938a2b98ae3110fe4c7552ef82c06", + "574556310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720900000000000000f7a302ab9f266655be70279f7886e02dce7dfe33c44dac0b34e6d41b9524b51590579cdb0900000000112100ffe32d9d821d4c96672440f5afc91e6c367649c074cbf89d682ca414d8a27aed01a77429039131fec4a3d43115f714017717a4d40e5b35ea8a5210348d5048fa01d53e06ec47eda6431e84ec4ef92bc28bcbca244cbeb55dc4dadff8744f078300", + "574556310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720a00000000000000088fbe766d14475e5941465adfd6b4f72081f143769708d336aa434edd610b7190579cdb0900000000112100493ee2136de6e2135efae46582aafdfcc3b4b198480fd0f027f4ff4e1005b5dd01ce23bc87d9b2b2d8c181b00ab83fbd5b4d81eb754837a721f214e20086b0e792d09a9d95b2be3d73cdc879be45ebd08c02d17b1f14577c570e5d8120d5ae4a0d", + "574556310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720b000000000000009ebbc15f543d17a40fefbf83d82e44df324b0547ef4ac861d7a2c1d17bc2532290579cdb09000000001121003c62c6da5cf14bd5c78c760ab8086bf29c690ad4c47cc593f1a3cd0cc0b26ea501c8e35745789c4b8bbf3b0fd372560714e7f119b2d2b10828573b3bb7d324c59a0b62202a5a03210af21cae39ca8c37fd91ee7ea13b040396ec061814d266b709", + "574556310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720c000000000000001ac28b260c688e53359de35d83a075c5f83fa6fc7f83692541d7f84d5608c6da90579cdb09000000001121009e628faf610b22f4c3594642b36c8ee0763421b0e2c6a867977bd9e60d0283dd01589462c8ba7ca43250836cfdb2bcca2d5c13ae265da24fd73483b6fe41e5c80c0adce6d9b4d94363a98a6c0fda37a2090742b5cacd9932f3d2a454d90ff60207", + "574556310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720d0000000000000080e84486e2dd139a69600a91d36dd4c6e88aece9db35d190d091be5e4517155290579cdb0900000000112100c8643ce65f7e7091502f1e806fcecbead23bd50288fa53ab69dc042cc5ce03520142cbd3b5170cbdb1a65cd96e39f296b122470ae9e308bba32a5df40abdfa9697650cf3d0a64817ee3052fbb9d0647a891c641a949c8b688f32099addf65a5f0a" + ] + ] + }, + "receipt": "574d52310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102fd6b5a6849f61a8bbd9d4db526a9fc1c73ef949590cdcd9b0b65ce6375262d9bd30936bdb0900000090579cdb09000000018a5ce3302cb410fe4344c89094a21edd5c2d2958c41da3bf09525e8e7cc051d70a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c08861000000000048c98f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000700000000000000240000000000000000000000000000000100455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d050000640000000500000056c81edd7a0db83bdc1f9a4d149f136b0734213d30e53d9aa248519a9695c2fa00000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001852979771a17d1cbd817c612fb0173fc3c954ac5323556bb29847106dea892e5f346c5dc487bdc83e799eeae333b9651040c299936afdaffed6cf0d847edd08", + "hops": [ + { + "record": "574d48310189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0a29cf514deebfd5ca029b1d658b198bade764af98e09848c6fd53b129678b720130936bdb0900000090579cdb0900000000000000000000000008000152d69b7a8f4a75770ec3e98a5f566eb81be7e64991e6e05d574a296d1ae5cf38011e000000000000007869000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000152d69b7a8f4a75770ec3e98a5f566eb81be7e64991e6e05d574a296d1ae5cf38025a00000000000000683c0100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001daed485c7654eadd0391882eee64c9236f80c50d402067f940d18886b1e69a82011e0000000000000000400b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001daed485c7654eadd0391882eee64c9236f80c50d402067f940d18886b1e69a82023f0000000000000000a01700000000001b000000000000000000000000000000000000000000000000000000000000001b00000000000000020000000000000000000000000000000000000000000000000000000000000000011200000000000000a80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000023600000000000000f80100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000013c000000000000006eeb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000002b4000000000000004ac2020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000048c98f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002400000000000000050000005f9c7c612cb249d862dff12795c3a5558f377084218e9e1a5799dbabeb177a85684771242e95715f8ca352396003f3aef431b9bf96c8f31fbc68b9d24fd3adb9bf6526097699d988641486d379f3cde1b8ffd4a188177c0cd56f0e89bd13480d", + "ledger": "4fc2bb567c1e950b92bb2dd6796df52eb67f3e5c2e5753b3ee9bae483600f17effffffffffffffffffffffffffffffff0000000100000098b50b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000510124baa39d99a07d9d6575698f70183759e3da8b458f8edb09f09215e14318ffffffffffffffffffffffffffffffff00000001000000f6df000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000052786b7331441935c2143544988bccb4a4db6f5c76d734292a843a0b5e20e83d404b4c0000000000f4010000000000004555520100000000000000000000008e950c00000000009bfd2f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b4272b140a49c75d1dbedfca74343ad6c13a8a9d3a20429e7d457f30d2afd82ffffffffffffffffffffffffffffffff0000000100000000000000000000008e7502000000000006dd2f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fec5bc703c31e2600d5a51cceb45eb2e5011ec2c1890f8dc5b96fd683ef85362ffffffffffffffffffffffffffffffff0000000100000000000000000000008e950c0000000000a7ee2f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + ], + "descriptors": [], + "compositions": [], + "takes": [], + "logs": null + } + }, + "vectors": [ + { + "name": "a_day", + "about": "one day: a lesson room, two delivering parties and a viewer that forwarded sealed objects under no tariff of its own, a package, ads under a policy that requires claims and under one that does not", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "accept", + "digest": "1dc98075e380a5689f41e818cdf459814da6b4fd8af7baca0df81dd13ec4cef6", + "tariff": "ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8", + "currency": "EUR", + "start": 1792108800000000, + "end": 1792195200000000, + "records": 35, + "lines": [ + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "publisher_us", + "quantity": 80500000, + "price": 2778, + "amount": 223629, + "cites": [ + [ + 1, + "795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d" + ] + ] + }, + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "subscriber_us_0", + "quantity": 69400000, + "price": 1389, + "amount": 96396, + "cites": [ + [ + 1, + "795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d" + ] + ] + }, + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "subscriber_us_1", + "quantity": 23600000, + "price": 2778, + "amount": 65560, + "cites": [ + [ + 1, + "795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d" + ] + ] + }, + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "subscriber_us_2", + "quantity": 23600000, + "price": 5556, + "amount": 131121, + "cites": [ + [ + 1, + "795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d" + ] + ] + }, + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "subscriber_us_8", + "quantity": 39999000, + "price": 278, + "amount": 11119, + "cites": [ + [ + 1, + "795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d" + ] + ] + }, + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "composition_us", + "quantity": 9000000, + "price": 5556, + "amount": 50004, + "cites": [ + [ + 1, + "795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d" + ] + ] + }, + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "recorded_us", + "quantity": 20000000, + "price": 1389, + "amount": 27780, + "cites": [ + [ + 1, + "795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d" + ] + ] + }, + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "events_sent", + "quantity": 44, + "price": 100, + "amount": 4400, + "cites": [ + [ + 1, + "795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d" + ] + ] + }, + { + "source_kind": "delivery", + "source": "123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3", + "unit": "live", + "quantity": 12100, + "price": 20000000, + "amount": 242, + "cites": [ + [ + 3, + "c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b" + ], + [ + 4, + "1c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d0" + ], + [ + 4, + "c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea110" + ] + ] + }, + { + "source_kind": "delivery", + "source": "123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3", + "unit": "ancillary", + "quantity": 5000, + "price": 5000000, + "amount": 25, + "cites": [ + [ + 3, + "c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b" + ], + [ + 4, + "1c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d0" + ] + ] + }, + { + "source_kind": "delivery", + "source": "a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb", + "unit": "live", + "quantity": 18800, + "price": 20000000, + "amount": 376, + "cites": [ + [ + 3, + "9afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf" + ], + [ + 4, + "cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a472423767200" + ] + ] + }, + { + "source_kind": "delivery", + "source": "b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83", + "unit": "live", + "quantity": 14500, + "price": 20000000, + "amount": 290, + "cites": [ + [ + 3, + "49ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928" + ], + [ + 4, + "09f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639" + ], + [ + 4, + "35a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d50" + ], + [ + 4, + "d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea99" + ] + ] + }, + { + "source_kind": "package", + "source": "0d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0", + "unit": "packages", + "quantity": 1, + "price": 500000, + "amount": 500000, + "cites": [ + [ + 5, + "22c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728" + ] + ] + }, + { + "source_kind": "package", + "source": "0d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0", + "unit": "entries", + "quantity": 10, + "price": 1000, + "amount": 10000, + "cites": [ + [ + 5, + "22c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728" + ] + ] + }, + { + "source_kind": "package", + "source": "0d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0", + "unit": "stored_bytes", + "quantity": 119779, + "price": 2000000, + "amount": 239, + "cites": [ + [ + 5, + "22c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728" + ] + ] + }, + { + "source_kind": "ads", + "source": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "unit": "presented_impressions", + "quantity": 4, + "price": 1500, + "amount": 6000, + "cites": [ + [ + 6, + "3e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb35695" + ], + [ + 6, + "811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037" + ], + [ + 6, + "a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c" + ], + [ + 7, + "46be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c" + ], + [ + 7, + "5981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef" + ], + [ + 7, + "5d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf" + ], + [ + 7, + "e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc356" + ] + ] + }, + { + "source_kind": "ads", + "source": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "unit": "billable_impressions", + "quantity": 3, + "price": 1000, + "amount": 3000, + "cites": [ + [ + 6, + "1c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95" + ], + [ + 6, + "64cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da8345" + ] + ] + } + ], + "energy": [ + { + "scope": "room_hops", + "class": "OnChipCounter", + "figures": 2, + "joules": 60400000, + "max_ppm": 20000 + }, + { + "scope": "delivery_hops", + "class": "ModelBased", + "figures": 1, + "joules": 12500000, + "max_ppm": 0 + }, + { + "scope": "delivery_hops", + "class": "OnChipCounter", + "figures": 1, + "joules": 9000000, + "max_ppm": 25000 + }, + { + "scope": "ad_decisions", + "class": "ModelBased", + "figures": 1, + "joules": 1200, + "max_ppm": 0 + }, + { + "scope": "ad_decisions", + "class": "OnChipCounter", + "figures": 1, + "joules": 900, + "max_ppm": 30000 + } + ], + "unmetered": { + "room_hops": 0, + "delivery_hops": 0, + "packaging": 1, + "ad_decisions": 5 + }, + "coverage": { + "room_hops": "whole", + "delivery_hops": "whole", + "packaging": "partial", + "ad_decisions": "partial" + }, + "amount": 1130181, + "reports": { + "ads": [ + { + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "claims_required": true, + "receipts": [ + { + "identity": "a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c", + "decider": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "cue": "e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa", + "session": "0df27375ac69306db3c554c1316c28fa", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-x", + "price_micros": 40000, + "quartiles_reached": 5, + "billable": true, + "counts": true, + "verdict": "presented" + }, + { + "slot": 1, + "ad_id": "ad-y", + "price_micros": 30000, + "quartiles_reached": 4, + "billable": true, + "counts": true, + "verdict": "presented" + } + ], + "billable_impressions": 2, + "billable_spend_micros": 70000, + "counted_impressions": 2, + "counted_spend_micros": 70000 + }, + { + "identity": "1e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b", + "decider": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "cue": "e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa", + "session": "0eb873fe6fd3d9ab69d21ec031ce36df", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-x", + "price_micros": 40000, + "quartiles_reached": 5, + "billable": true, + "counts": false, + "verdict": "claim_not_viewable" + }, + { + "slot": 1, + "ad_id": "ad-y", + "price_micros": 30000, + "quartiles_reached": 2, + "billable": true, + "counts": false, + "verdict": "claim_not_viewable" + } + ], + "billable_impressions": 2, + "billable_spend_micros": 70000, + "counted_impressions": 0, + "counted_spend_micros": 0 + }, + { + "identity": "3e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb35695", + "decider": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "cue": "e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa", + "session": "f723833b66cc049001d83ca85d3e9ea3", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-x", + "price_micros": 40000, + "quartiles_reached": 5, + "billable": true, + "counts": true, + "verdict": "presented" + }, + { + "slot": 1, + "ad_id": "ad-y", + "price_micros": 30000, + "quartiles_reached": 5, + "billable": true, + "counts": false, + "verdict": "claim_asset_mismatch" + } + ], + "billable_impressions": 2, + "billable_spend_micros": 70000, + "counted_impressions": 1, + "counted_spend_micros": 40000 + }, + { + "identity": "8c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b", + "decider": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "cue": "e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa", + "session": "d67c9c9715765cd08d686b0b418f8f4f", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-x", + "price_micros": 40000, + "quartiles_reached": 5, + "billable": true, + "counts": false, + "verdict": "claim_quartile_mismatch" + }, + { + "slot": 1, + "ad_id": "ad-y", + "price_micros": 30000, + "quartiles_reached": 5, + "billable": true, + "counts": false, + "verdict": "claim_absent" + } + ], + "billable_impressions": 2, + "billable_spend_micros": 70000, + "counted_impressions": 0, + "counted_spend_micros": 0 + }, + { + "identity": "811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037", + "decider": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "cue": "e9a7c58217dd8811ffb663763bd71832a9df5b06fd13ba1dc00328c8888860aa", + "session": "28d08ac9bb09ab45c22ff9a59e54ad9d", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-x", + "price_micros": 40000, + "quartiles_reached": 5, + "billable": true, + "counts": false, + "verdict": "claim_not_viewable" + }, + { + "slot": 1, + "ad_id": "ad-y", + "price_micros": 30000, + "quartiles_reached": 5, + "billable": true, + "counts": true, + "verdict": "presented" + } + ], + "billable_impressions": 2, + "billable_spend_micros": 70000, + "counted_impressions": 1, + "counted_spend_micros": 30000 + } + ], + "billable_impressions": 10, + "counted_impressions": 4, + "billable_spend_micros": 350000, + "counted_spend_micros": 140000, + "not_counted": { + "claim_absent": 1, + "claim_asset_mismatch": 1, + "claim_not_viewable": 3, + "claim_quartile_mismatch": 1 + } + }, + { + "room": "0cd040155378d4dd3132ac06126fe7ad98cf014c13f4381f3cd87d39cb332ddc", + "claims_required": false, + "receipts": [ + { + "identity": "1c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95", + "decider": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "72d537a7839c90be3593e83fafee3a2f", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "price_micros": 40000, + "quartiles_reached": 5, + "billable": true, + "counts": true, + "verdict": "beacons" + }, + { + "slot": 1, + "ad_id": "ad-b", + "price_micros": 30000, + "quartiles_reached": 3, + "billable": true, + "counts": true, + "verdict": "beacons" + } + ], + "billable_impressions": 2, + "billable_spend_micros": 70000, + "counted_impressions": 2, + "counted_spend_micros": 70000 + }, + { + "identity": "64cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da8345", + "decider": "dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575", + "cue": "d0f201740eb1313f519b4d9feb3be4291f37663b62ab970b719bf2e012641bc0", + "session": "0f041639e63167e41cc74c151102c01b", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-a", + "price_micros": 40000, + "quartiles_reached": 5, + "billable": true, + "counts": true, + "verdict": "beacons" + } + ], + "billable_impressions": 1, + "billable_spend_micros": 40000, + "counted_impressions": 1, + "counted_spend_micros": 40000 + } + ], + "billable_impressions": 3, + "counted_impressions": 3, + "billable_spend_micros": 110000, + "counted_spend_micros": 110000, + "not_counted": {} + } + ], + "delivery": { + "parties": [ + { + "party": "123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3", + "statements": [ + "c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b" + ], + "currency": "EUR", + "classes": { + "live": { + "objects": 4, + "bytes": 12100 + }, + "fetch": { + "objects": 0, + "bytes": 0 + }, + "fill": { + "objects": 0, + "bytes": 0 + }, + "ancillary": { + "objects": 1, + "bytes": 5000 + } + }, + "free_bytes": 0, + "declared": { + "declarations": 1, + "objects": 2, + "bytes": 5100 + }, + "stated_amount": 267, + "invoiced_amount": 267, + "energy": [ + { + "class": "ModelBased", + "figures": 1, + "joules": 12500000, + "max_ppm": 0 + } + ], + "unmetered": 0 + }, + { + "party": "a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb", + "statements": [ + "9afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf" + ], + "currency": null, + "classes": { + "live": { + "objects": 3, + "bytes": 18800 + }, + "fetch": { + "objects": 0, + "bytes": 0 + }, + "fill": { + "objects": 0, + "bytes": 0 + }, + "ancillary": { + "objects": 0, + "bytes": 0 + } + }, + "free_bytes": 0, + "declared": { + "declarations": 0, + "objects": 0, + "bytes": 0 + }, + "stated_amount": 0, + "invoiced_amount": 376, + "energy": [], + "unmetered": 0 + }, + { + "party": "b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83", + "statements": [ + "49ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928" + ], + "currency": "EUR", + "classes": { + "live": { + "objects": 5, + "bytes": 14500 + }, + "fetch": { + "objects": 1, + "bytes": 3600 + }, + "fill": { + "objects": 0, + "bytes": 0 + }, + "ancillary": { + "objects": 0, + "bytes": 0 + } + }, + "free_bytes": 3600, + "declared": { + "declarations": 0, + "objects": 0, + "bytes": 0 + }, + "stated_amount": 290, + "invoiced_amount": 290, + "energy": [ + { + "class": "OnChipCounter", + "figures": 1, + "joules": 9000000, + "max_ppm": 25000 + } + ], + "unmetered": 0 + } + ], + "overlaps": [ + { + "content_hash": "de1fd9f932bdc0a1f660fcf9c7d312f08d367a1f75014cbd3be36a7fb7391cb2", + "downstream": "5c7e9427189195ef433885dba1d68c6138b787c2ab8d8f55d8e3fb3ab6f47975", + "bytes": 600, + "first": { + "party": "123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3", + "record": "c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea110" + }, + "second": { + "party": "b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83", + "record": "d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea99" + } + } + ] + } + } + } + }, + { + "name": "the_next_invoice", + "about": "the next invoice of the chain: a relay's session whose energy is unmetered, and a second package", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a81dc98075e380a5689f41e818cdf459814da6b4fd8af7baca0df81dd13ec4cef600a043f6fd5d06000060f231265e06004555520300000001b4230f69357cd2c95c03faf646be74d1e53f3f8eac72679670b5634dd74512e9028a5ce3302cb410fe4344c89094a21edd5c2d2958c41da3bf09525e8e7cc051d7054441a653f101172ee9d4b76f788a10f23ab359e7bf3116fb09327699c2c83ef006000189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f01c088610000000000da0a0000000000005c450000000000000100000012e423c9cfe9b0bf35a7e921f914ce5b854913d5361e9a7b378e02dda65c70be0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0248c98f00000000006d0500000000000020330000000000000100000012e423c9cfe9b0bf35a7e921f914ce5b854913d5361e9a7b378e02dda65c70be0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0d07000000000000006400000000000000bc020000000000000100000012e423c9cfe9b0bf35a7e921f914ce5b854913d5361e9a7b378e02dda65c70be0329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5501010000000000000020a107000000000020a1070000000000010000001b8fd442535ab14bb54e5efe3a8bd29a875af14a914d0b907497d567c8401bb10329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d55021200000000000000e8030000000000005046000000000000010000001b8fd442535ab14bb54e5efe3a8bd29a875af14a914d0b907497d567c8401bb10329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5503706407000000000080841e0000000000c803000000000000010000001b8fd442535ab14bb54e5efe3a8bd29a875af14a914d0b907497d567c8401bb1000100000000000000010000000000000070660800000000004af32bd32cead4eb36430fd22bd16d1edb07a255853c7813b3a9a7852211b83102c90e28c406d9a20cb34463976dfba10f2aeac4f95f4f3053c5be65bd54b800", + "previous": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "sources": [ + "relay", + "package_staged" + ], + "outcome": { + "outcome": "accept", + "digest": "2b260faf574366d73ea44023e13885789a3529d38412ed4e78a2b08a13b62807", + "tariff": "ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8", + "currency": "EUR", + "start": 1792195200000000, + "end": 1792368000000000, + "records": 3, + "lines": [ + { + "source_kind": "room", + "source": "89a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f", + "unit": "publisher_us", + "quantity": 6392000, + "price": 2778, + "amount": 17756, + "cites": [ + [ + 1, + "b4230f69357cd2c95c03faf646be74d1e53f3f8eac72679670b5634dd74512e9" + ] + ] + }, + { + "source_kind": "room", + "source": "89a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f", + "unit": "subscriber_us_0", + "quantity": 9423176, + "price": 1389, + "amount": 13088, + "cites": [ + [ + 1, + "b4230f69357cd2c95c03faf646be74d1e53f3f8eac72679670b5634dd74512e9" + ] + ] + }, + { + "source_kind": "room", + "source": "89a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f", + "unit": "events_sent", + "quantity": 7, + "price": 100, + "amount": 700, + "cites": [ + [ + 1, + "b4230f69357cd2c95c03faf646be74d1e53f3f8eac72679670b5634dd74512e9" + ] + ] + }, + { + "source_kind": "package", + "source": "29e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d55", + "unit": "packages", + "quantity": 1, + "price": 500000, + "amount": 500000, + "cites": [ + [ + 5, + "4441a653f101172ee9d4b76f788a10f23ab359e7bf3116fb09327699c2c83ef0" + ] + ] + }, + { + "source_kind": "package", + "source": "29e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d55", + "unit": "entries", + "quantity": 18, + "price": 1000, + "amount": 18000, + "cites": [ + [ + 5, + "4441a653f101172ee9d4b76f788a10f23ab359e7bf3116fb09327699c2c83ef0" + ] + ] + }, + { + "source_kind": "package", + "source": "29e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d55", + "unit": "stored_bytes", + "quantity": 484464, + "price": 2000000, + "amount": 968, + "cites": [ + [ + 5, + "4441a653f101172ee9d4b76f788a10f23ab359e7bf3116fb09327699c2c83ef0" + ] + ] + } + ], + "energy": [], + "unmetered": { + "room_hops": 1, + "delivery_hops": 0, + "packaging": 1, + "ad_decisions": 0 + }, + "coverage": { + "room_hops": "partial", + "delivery_hops": "none", + "packaging": "partial", + "ad_decisions": "none" + }, + "amount": 550512, + "reports": { + "ads": [], + "delivery": { + "parties": [], + "overlaps": [] + } + } + } + }, + { + "name": "room_hops_of_three_classes", + "about": "a room whose hops are a model, a counter and an estimate: one row each", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c698f25ea3a2c247f29e94b9280ebf10b50c1a7412b49a0b559a2c131c0b87ba0100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaacf17c74c9c910f52f43d79a19acaa5709cb0f5e2ff12a7d4254a79d138916276994f1d8fa9cbe38dbb250991078584c7e58e64efd99cfaeb537d9f93a1a1fd05444041dff0fe81c30d2ac7b9c22fe03e5ee5f1db076c603ce57f00585dc61b6c12cee938d4e60447f783ff791ed403e080f9b4e2c00e0b5b27b5846f0f03d503", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c698f25ea3a2c247f29e94b9280ebf10b50c1a7412b49a0b559a2c131c0b87ba01294dc15f852b7213e824fedc8073c4b57764d9d02d365352a2824c1e0221a2d3000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555520400000001d0b7e1999618911e90c6fcf97ef1424b2960976ac649cd1212e8888334305427029f2e96e3cf68df9d2fce5457c50c8469006e663b04a03ec8d389b77a51a646b702c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02d969cc740d80efd2c77bb615616ea1325471a58c89a1740db6d40fa8c85be6a80800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d690300000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e634012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c780100000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e634012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a00000000000018000100000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e634012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b41500000000000031000200000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e634012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b0000000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e634012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c30000000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e634012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c0000000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e634012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c00000000000000640000000000000030110000000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e63403010201000000c00e1602000000000000000001030100000080841e00000000000000000001040100000000c3180100000000204e000000000000000000000000000000000000d94e090000000000e29ced131a8e526eaf7a7ed68ab2bcdd6b6a56fefe5ce41d5a927de1885ac5b8eba7e8ff9ce6fbfb2bceff3daf616601ffdab31e192938af6db1a552c92b960b", + "previous": null, + "sources": [ + "lesson_mixed_classes" + ], + "outcome": { + "outcome": "accept", + "digest": "68e24144ec78c3fdd6723a9ff9c99dbf51acf39694ab9bd0daf002d8ef63a496", + "tariff": "294dc15f852b7213e824fedc8073c4b57764d9d02d365352a2824c1e0221a2d3", + "currency": "EUR", + "start": 1792108800000000, + "end": 1792195200000000, + "records": 4, + "lines": [ + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "publisher_us", + "quantity": 80500000, + "price": 2778, + "amount": 223629, + "cites": [ + [ + 1, + "d0b7e1999618911e90c6fcf97ef1424b2960976ac649cd1212e8888334305427" + ] + ] + }, + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "subscriber_us_0", + "quantity": 69400000, + "price": 1389, + "amount": 96396, + "cites": [ + [ + 1, + "d0b7e1999618911e90c6fcf97ef1424b2960976ac649cd1212e8888334305427" + ] + ] + }, + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "subscriber_us_1", + "quantity": 23600000, + "price": 2778, + "amount": 65560, + "cites": [ + [ + 1, + "d0b7e1999618911e90c6fcf97ef1424b2960976ac649cd1212e8888334305427" + ] + ] + }, + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "subscriber_us_2", + "quantity": 23600000, + "price": 5556, + "amount": 131121, + "cites": [ + [ + 1, + "d0b7e1999618911e90c6fcf97ef1424b2960976ac649cd1212e8888334305427" + ] + ] + }, + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "subscriber_us_8", + "quantity": 39999000, + "price": 278, + "amount": 11119, + "cites": [ + [ + 1, + "d0b7e1999618911e90c6fcf97ef1424b2960976ac649cd1212e8888334305427" + ] + ] + }, + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "composition_us", + "quantity": 9000000, + "price": 5556, + "amount": 50004, + "cites": [ + [ + 1, + "d0b7e1999618911e90c6fcf97ef1424b2960976ac649cd1212e8888334305427" + ] + ] + }, + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "recorded_us", + "quantity": 20000000, + "price": 1389, + "amount": 27780, + "cites": [ + [ + 1, + "d0b7e1999618911e90c6fcf97ef1424b2960976ac649cd1212e8888334305427" + ] + ] + }, + { + "source_kind": "room", + "source": "2c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa", + "unit": "events_sent", + "quantity": 44, + "price": 100, + "amount": 4400, + "cites": [ + [ + 1, + "d0b7e1999618911e90c6fcf97ef1424b2960976ac649cd1212e8888334305427" + ] + ] + } + ], + "energy": [ + { + "scope": "room_hops", + "class": "ModelBased", + "figures": 1, + "joules": 35000000, + "max_ppm": 0 + }, + { + "scope": "room_hops", + "class": "Estimator", + "figures": 1, + "joules": 2000000, + "max_ppm": 0 + }, + { + "scope": "room_hops", + "class": "OnChipCounter", + "figures": 1, + "joules": 18400000, + "max_ppm": 20000 + } + ], + "unmetered": { + "room_hops": 0, + "delivery_hops": 0, + "packaging": 0, + "ad_decisions": 0 + }, + "coverage": { + "room_hops": "whole", + "delivery_hops": "none", + "packaging": "none", + "ad_decisions": "none" + }, + "amount": 610009, + "reports": { + "ads": [], + "delivery": { + "parties": [], + "overlaps": [] + } + } + } + }, + { + "name": "tariff_truncated", + "about": "a tariff one byte short", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f141", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "truncated", + "at": [ + "tariff" + ] + } + }, + { + "name": "tariff_buyer_signature", + "about": "a tariff whose buyer's signature does not verify", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14102", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "bad_signature", + "at": [ + "tariff", + "buyer" + ] + } + }, + { + "name": "tariff_seller_small_order", + "about": "a tariff whose seller is a key of small order", + "tariff": "574c543101010000000000000000000000000000000000000000000000000000000000000063fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "bad_key", + "at": [ + "tariff", + "seller" + ] + } + }, + { + "name": "tariff_period_empty", + "about": "a tariff whose period is empty, signed by both", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c060000a0cd18bc5c0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f468257515548eb21a7ab4dcd0b4e56cffca0e9d508859881093cfa0574539596449ad445d4bba5a6fe83d340f8eb9e2e5fffa7325ebe4f2809a038797cdc7c7857e8c03d60fd4b75342501675ec9272b58e4b127b4d29572d08670c2e6a043086025a2db0f4ee3020d01d0b3cf7808b02b5f268cade4f1fb7f4462f052f02f4649b4404", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "tariff_invalid", + "at": [ + "tariff" + ] + } + }, + { + "name": "invoice_trailing_byte", + "about": "an invoice with a byte after its signature", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d450300", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "malformed", + "at": [ + "invoice" + ] + } + }, + { + "name": "invoice_version", + "about": "an invoice of version 2", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493102c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "unknown_version", + "at": [ + "invoice" + ] + } + }, + { + "name": "invoice_signature", + "about": "an invoice whose signature does not verify", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4502", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "bad_signature", + "at": [ + "invoice" + ] + } + }, + { + "name": "invoice_lines_unordered", + "about": "an invoice whose first two lines are swapped", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e930957901a12c08cec013dab04be01a7170a2f2c11516093e0f9b7b89ec590fe69be4a09884347def9449d072b715c96fbd1bc6e8a9d19e1e34b72659a88902", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "invoice_invalid", + "at": [ + "invoice" + ] + } + }, + { + "name": "invoice_row_of_no_class", + "about": "an energy row of tag 0: joules of no one class", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960601000100000001000000000000000000000001040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e1100000000002fd5c92c43f1e261fd3aa89b9968989fcb0c124221f24034b31142fa060c6c452135af63cff987df85d05d3cc8fbee073e7453837cf0280033eddfd8761f690f", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "classes_summed", + "at": [ + "invoice" + ] + } + }, + { + "name": "invoice_of_another_tariff", + "about": "an invoice naming another tariff", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61294dc15f852b7213e824fedc8073c4b57764d9d02d365352a2824c1e0221a2d3000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000fa7b03df918b49941f034181892018a0a1be67f9eb3f7027148488f5ef4f916b9487b8db87ed582c82c935e3f5d2642cf5784a9a12b06eb83d7f2109b234920d", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "tariff_mismatch", + "at": [ + "invoice" + ] + } + }, + { + "name": "invoice_past_its_tariff", + "about": "an invoice whose period runs past the tariff's", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a0b9d33f5f06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e1100000000002271fb33d3a38f7c4d9b646c6fa23787442bd1ab5e75b6026f3de46bb2c4650f36d6a6d002a128a5f713541468ae497f6f9d3c0a0b23100486cc7ab68524540e", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "outside_tariff", + "at": [ + "invoice" + ] + } + }, + { + "name": "previous_not_given", + "about": "the next invoice without the invoice it names", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a81dc98075e380a5689f41e818cdf459814da6b4fd8af7baca0df81dd13ec4cef600a043f6fd5d06000060f231265e06004555520300000001b4230f69357cd2c95c03faf646be74d1e53f3f8eac72679670b5634dd74512e9028a5ce3302cb410fe4344c89094a21edd5c2d2958c41da3bf09525e8e7cc051d7054441a653f101172ee9d4b76f788a10f23ab359e7bf3116fb09327699c2c83ef006000189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f01c088610000000000da0a0000000000005c450000000000000100000012e423c9cfe9b0bf35a7e921f914ce5b854913d5361e9a7b378e02dda65c70be0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0248c98f00000000006d0500000000000020330000000000000100000012e423c9cfe9b0bf35a7e921f914ce5b854913d5361e9a7b378e02dda65c70be0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0d07000000000000006400000000000000bc020000000000000100000012e423c9cfe9b0bf35a7e921f914ce5b854913d5361e9a7b378e02dda65c70be0329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5501010000000000000020a107000000000020a1070000000000010000001b8fd442535ab14bb54e5efe3a8bd29a875af14a914d0b907497d567c8401bb10329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d55021200000000000000e8030000000000005046000000000000010000001b8fd442535ab14bb54e5efe3a8bd29a875af14a914d0b907497d567c8401bb10329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5503706407000000000080841e0000000000c803000000000000010000001b8fd442535ab14bb54e5efe3a8bd29a875af14a914d0b907497d567c8401bb1000100000000000000010000000000000070660800000000004af32bd32cead4eb36430fd22bd16d1edb07a255853c7813b3a9a7852211b83102c90e28c406d9a20cb34463976dfba10f2aeac4f95f4f3053c5be65bd54b800", + "previous": null, + "sources": [ + "relay", + "package_staged" + ], + "outcome": { + "outcome": "refuse", + "reason": "record_missing", + "at": [ + "previous" + ] + } + }, + { + "name": "previous_overlaps", + "about": "the next invoice, its period starting before the previous one ends", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a81dc98075e380a5689f41e818cdf459814da6b4fd8af7baca0df81dd13ec4cef600fcaf1ffd5d06000060f231265e06004555520300000001b4230f69357cd2c95c03faf646be74d1e53f3f8eac72679670b5634dd74512e9028a5ce3302cb410fe4344c89094a21edd5c2d2958c41da3bf09525e8e7cc051d7054441a653f101172ee9d4b76f788a10f23ab359e7bf3116fb09327699c2c83ef006000189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f01c088610000000000da0a0000000000005c450000000000000100000012e423c9cfe9b0bf35a7e921f914ce5b854913d5361e9a7b378e02dda65c70be0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0248c98f00000000006d0500000000000020330000000000000100000012e423c9cfe9b0bf35a7e921f914ce5b854913d5361e9a7b378e02dda65c70be0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0d07000000000000006400000000000000bc020000000000000100000012e423c9cfe9b0bf35a7e921f914ce5b854913d5361e9a7b378e02dda65c70be0329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5501010000000000000020a107000000000020a1070000000000010000001b8fd442535ab14bb54e5efe3a8bd29a875af14a914d0b907497d567c8401bb10329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d55021200000000000000e8030000000000005046000000000000010000001b8fd442535ab14bb54e5efe3a8bd29a875af14a914d0b907497d567c8401bb10329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5503706407000000000080841e0000000000c803000000000000010000001b8fd442535ab14bb54e5efe3a8bd29a875af14a914d0b907497d567c8401bb1000100000000000000010000000000000070660800000000006cdd4107888accaa035c5bb263267f183017e212899b61000c7b86d0df8f83d62efd1aaa7406b74c482d4aae792eba15001252ff90c9499a7cb90579b9d91e05", + "previous": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "sources": [ + "relay", + "package_staged" + ], + "outcome": { + "outcome": "refuse", + "reason": "invoice_overlap", + "at": [ + "previous" + ] + } + }, + { + "name": "package_billed_again", + "about": "the next invoice billing the first invoice's package again", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a81dc98075e380a5689f41e818cdf459814da6b4fd8af7baca0df81dd13ec4cef600a043f6fd5d06000060f231265e06004555520400000001b4230f69357cd2c95c03faf646be74d1e53f3f8eac72679670b5634dd74512e9028a5ce3302cb410fe4344c89094a21edd5c2d2958c41da3bf09525e8e7cc051d70522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728054441a653f101172ee9d4b76f788a10f23ab359e7bf3116fb09327699c2c83ef009000189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f01c088610000000000da0a0000000000005c450000000000000100000012e423c9cfe9b0bf35a7e921f914ce5b854913d5361e9a7b378e02dda65c70be0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0248c98f00000000006d0500000000000020330000000000000100000012e423c9cfe9b0bf35a7e921f914ce5b854913d5361e9a7b378e02dda65c70be0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f0d07000000000000006400000000000000bc020000000000000100000012e423c9cfe9b0bf35a7e921f914ce5b854913d5361e9a7b378e02dda65c70be030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a0329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5501010000000000000020a107000000000020a1070000000000010000001b8fd442535ab14bb54e5efe3a8bd29a875af14a914d0b907497d567c8401bb10329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d55021200000000000000e8030000000000005046000000000000010000001b8fd442535ab14bb54e5efe3a8bd29a875af14a914d0b907497d567c8401bb10329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5503706407000000000080841e0000000000c803000000000000010000001b8fd442535ab14bb54e5efe3a8bd29a875af14a914d0b907497d567c8401bb100010000000000000002000000000000008f2f10000000000098899c491894768f78fc64b9fff4d0140e60fe3cf0046c742c0b40afe7f76b19346556e4a6ac0b5cfdc46e50a5879929876fb99c81404a2592774abb0412cf05", + "previous": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "sources": [ + "relay", + "package_staged", + "package_base" + ], + "outcome": { + "outcome": "refuse", + "reason": "record_rebilled", + "at": [ + "source", + 2 + ] + } + }, + { + "name": "room_outside_scope", + "about": "a tariff whose scope does not name the lesson room", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e80300000000000007000189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575aef94b156fa5f3548e57521875b91157f60ddf9b2eaedb74eda95e9f5f4a8831990dc07432e0bdbab9e75257b731311866e4382519b5d65f4cf23673716d7a0a68aefc12951aafbf6c77a64c0d245d8e83b13533a7aee73593b6465ea76e18ba5b909c3307d3530e94c9fb8e63a1f8090c69361d1ad6662bbdd5aca71de1ca0d", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab611465ba20b0cd138bee8ba6d6ed3d42f3a5121c44a8ff14c3887ed921c33d2d62000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e1100000000002108543eb2142d40feb75ef09848a92afe168bd6b03b882dad94ccdcdca163a608124d191b479b5772cc069c5f3bac9c996d5b01e9ea33cb87bc411a514a8c03", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "outside_scope", + "at": [ + "source", + 0 + ] + } + }, + { + "name": "statement_under_another_tariff", + "about": "a delivering party's statement under prices in another currency", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a_usd", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "tariff_conflict", + "at": [ + "source", + 1 + ] + } + }, + { + "name": "room_outside_period", + "about": "a relay's session of another day in the first invoice", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break", + "relay" + ], + "outcome": { + "outcome": "refuse", + "reason": "outside_period", + "at": [ + "source", + 7 + ] + } + }, + { + "name": "ads_room_with_a_gap", + "about": "an ad room whose events have a gap", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break", + "ads_gap" + ], + "outcome": { + "outcome": "refuse", + "reason": "source_gap", + "at": [ + "source", + 7 + ] + } + }, + { + "name": "package_given_twice", + "about": "one package given as two sources", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break", + "package_base" + ], + "outcome": { + "outcome": "refuse", + "reason": "record_duplicate", + "at": [ + "source", + 7 + ] + } + }, + { + "name": "room_session_billed_twice", + "about": "a second receipt of the lesson room's session", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break", + "lesson_mixed_classes" + ], + "outcome": { + "outcome": "refuse", + "reason": "record_overlap", + "at": [ + "source", + 7 + ] + } + }, + { + "name": "source_not_a_source", + "about": "a source of a kind this extension does not read", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break", + "not_a_source" + ], + "outcome": { + "outcome": "refuse", + "reason": "malformed", + "at": [ + "source", + 7 + ] + } + }, + { + "name": "room_receipt_refused", + "about": "a room-session receipt whose signature does not verify, refused by live-metering", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson_bad_signature", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "bad_signature", + "at": [ + "source", + 0, + "receipt" + ] + } + }, + { + "name": "statement_refused", + "about": "a statement whose amount does not recompute, refused by settlement", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a_amount", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "statement_mismatch", + "at": [ + "source", + 1, + "statement" + ] + } + }, + { + "name": "package_refused", + "about": "a package receipt given without its index, refused by packaging", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_no_index", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "receipt-index-count", + "at": [ + "source", + 4, + "receipt" + ] + } + }, + { + "name": "ads_refused", + "about": "presented spend past the decider's ceiling, refused by live-ads", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000e980c61261881099b8ca02864e72faa50f3c8ac9ead5ed11469a659a8c18cd224c94e3c3126fc8a33dac5c7166b52cd600dacd3d0ae52564a1a416798d3d4503", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_past_ceiling", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "funds_exceeded", + "at": [ + "source", + 5, + "receipt", + 1 + ] + } + }, + { + "name": "line_bills_more_than_recorded", + "about": "a line one unit above what its records state", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012155cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000a72add875e0a25e5daceba8fd35dc7c899fada838f1c867f2365f0fc291fb1734bb6ac5aecda2491e42389bdf2d5ce67676c2705aba93504bc55d54fd6fe520c", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "quantity_not_recorded", + "at": [ + "invoice" + ] + } + }, + { + "name": "line_cites_other_records", + "about": "a room line whose citation digest is an ad line's", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d690300000000000100000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df396012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e11000000000094de77a6de3527cb94f7db6793118ee83e73ea5a0c2f5bb36d268a771ad774abeb0538c1075abc841e32b5f20a51059b4307a077670347f47e9c9748dbe84a01", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "citation_mismatch", + "at": [ + "invoice" + ] + } + }, + { + "name": "line_amount", + "about": "a line whose amount is one above its quantity at its price, and the total with it", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522300000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc35607e29408132068a989d84fa5e4404d2393715e2e1074613ebc77acd35ba7276bff1100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008e69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c63e11000000000093b91d788f5a05be238465bfedd93b45780ecac9ed5274be8ac578e139f224db5822e6ac23b803df0c1223f29bf128e24583cff56e012ee575711d3d5bab220f", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "invoice_mismatch", + "at": [ + "invoice" + ] + } + }, + { + "name": "records_short", + "about": "an invoice that leaves one of its records out", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab6100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0189a7be137d7f3c51d15b0d4ece614d60743d7542b7693c96ac7633793e9c102f02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be302a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb02b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f83030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b00329e5833a915a6429a4e3a7948475c338ef436eb82be89c92f059704403db9d5504dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575356b1ca28a5fdcaf1722314877dcddfe81f120a29b987f595e76d56ddc63a959098d592f1fd40024b9c3b47035564146414296dd5973ff7f169637d6c51d340ff91b4899b8510e8e7ee3008f530b97d4c9ed62d406ab6017a60a23f453b8ef9c232e947e0d875f2b21ad7bbdebea890f176c12d1736cfbd64d1fb0dc83f14103", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c663fece2d2864108f19568bbfa6f08b9905a76ab46a19aa7310128d1995c8ab61ba582548ae3be18dd71613ef980c14293c5c3093bde02da32b44002279ea90a8000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555522200000001795b769781fc401a38250614edad12994c05aecaae7687849f25105ff029269d028070e1923387154875aacbeb894c20dc919c95dd4128758b5d47df86652c4a2102acbe25f7796dd0a3bafe6b443c66a7c7313e759125abed03cbb036ba697c657c02c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02e28e3d8b84792817760e604cc5d01437f4c06ac01b93bc1ff31753f73396b6ad0349ab20c605d8603471ebe98ba3ed459213fdb620a25fda582f092f2811c22928039afdd42ebb416835c8556cce58d13b0a87c971299086d78255fa40fd8d229cdf03c54a8bc4385d13448aedcb5d1a1a77af507e863e3ab729622d87cee0ee4de26b0406ad5f6e482b877aef68dc2f4260c3816c62f0192524729fda42df2abf1134e10409f417544891b15aac83909edb04f8b9f098a068b1f00a78613d73802793b639041c9beac4631e7d594be6ceb92a6e4709ef512246f37c02e9af31f01f4c0f25d00435a9a5bb9b0094beed7688d3c0c018fe9bda993e1ec6b97647e226ff4edf9d5004c0ad3878d2a1cb0d37689b7c4e388e5e12e5236325d63f4e989d9bcacb8ea11004cb6887d61f4b576652c134da7f9ec5ff4c0bf81220d7a5019f5a47242376720004d1f831945866a2f3a1288ecd4e269ad8ce95a07469e3315c50eda59ad32eea990522c6f558063499040127d58b062f08f9ec2a6cee54b51c97d42e8741d76ad728061c8bff9084c93038bbb8cd767c644800f65048eab2fc258e5d22f9c435ab1b95061e4cbd4e8e8c222e666d430941885cc9b5f7f6f4328b4df38b582c18a71d905b063e6703d3a0d334f8c5a8d3baf2ce176d78cd812a3e2b7fdd3c04142e7eb356950664cf280dec65f0c7d98004308f76520ae42ced5aebf41bd457104aaec8da834506811279c01d8d2c9deb6c0d3a2f5b14143a0fdad50951824ef97378fc810ee037068c044b98f9a891c0840265ac35f526f6b507adb64cd3a7929ed0f10cb9a0fe9b06a685926b46841d22878e0c8f953c53002c539f2525a803d92631e114a709402c0704ecdb6b231ba15abef1ca3085aacecff064f4fb6af7f6ca82e18ce18118230d0746be24fe2cf22bfa77910f9cfaad9d92dab6a4b1bec88f1cd1cf621a5701768c075981f226e204dd8f9ec4bb4ba53d39cf2026a881b3a92bd7e8dcccf09aa65fef075d5e9b8c474daaa3e841a2ab0cecbdb0375ac3fb1231c9818a41a5a9f9a31eaf076cfd5648a26dc864b170a3455c6df4a1c77b45f61b4a0c23c936e14ab43d680e0782e66a104cae4d5c7153254bee825008f7b5383fc83e180fea57e689cca1ad090784610c7818d7907e71ad803287875884826af7f2cded13645d91dc503fb304b107a2de513194881a4ee11377aa6c1701b52b5fbb82cdff49d725b39a1303ceecfe07bbc10c5007ad65dd899f7548a8b61f1ef3208c2c57195e3e62977880f134e0ff07d3920e8dddffca8f01598c5465444a2fcb2e86333bb87493131173ede53ab82907e20fbdd624230fb1f69e7adc59fb22ea90dc5eaa1770339bad2b9f19625bc3561100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d69030000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c78010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a0000000000001800010000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b4150000000000003100020000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c3000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c0000000000000064000000000000003011000000000000010000008534d2eec3090faef669dd11affb99095324d47d1be6191bdbb2bd543028a16d02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be301442f000000000000002d310100000000f20000000000000003000000a17df995f9de8e497a6e09efcf23447932836eaae64ba4f9695cfecd324d1f0a02123149749a48decea3d95364f688b821bb45bd7993dcfd3ae12b4e5f0b5b0be3048813000000000000404b4c000000000019000000000000000200000042db429a7a23eeb698760f7d4da77f4276d612334d0f863ce81489d9592ad14802a84fe45dff31d5277a3a7f557454d8f4bb0402a96c58e9215a9b4d896940bceb017049000000000000002d310100000000780100000000000002000000f7847c186a8bbf714cf005e91f9bab910f5943899d33c952a0777b31504c3dd502b7ccb31f589ef602603d4ae5cd3bada28af9e22cd6858ee65403c8772e9e6f8301a438000000000000002d3101000000002201000000000000040000003836ff78a74a6133527e5aab6a81cc914d4b49df9a00bc132913d2ecde01c97c030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b001010000000000000020a107000000000020a107000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0020a00000000000000e803000000000000102700000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a030d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b003e3d301000000000080841e0000000000ef0000000000000001000000953cfab07058f45b391d8e2e0d92cdea272c861da868d3b51bd4cc25b1e5889a04dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575010400000000000000dc050000000000007017000000000000070000002c46144cb834d250fea655575742a3698a5ad710f3d4e53d0f848a7483e36c4304dd8db34c7d7be6dd1da6d72abf89889bd2353de94849c6b39107ca09f4682575020300000000000000e803000000000000b80b0000000000000200000009b6f0b8d12c79b3fcb72953b7b8d856fc26b94ea02720631266ffa59a8df3960501040200000080a1990300000000204e000002020100000020bcbe0000000000000000000204010000004054890000000000a8610000040201000000b0040000000000000000000004040100000084030000000000003075000000000000000000000100000005000000c53e110000000000b40dbe1c96064fe44e6c4ace51f554b43f0c4c1970154503f93735a654adb65953373f6327606d7775d75a0244242d07b2f5dc3b9c86ba6012efae7411107301", + "previous": null, + "sources": [ + "lesson", + "cdn_a", + "cdn_b", + "forwarder", + "package_base", + "ads_presented", + "ads_live_break" + ], + "outcome": { + "outcome": "refuse", + "reason": "invoice_mismatch", + "at": [ + "invoice" + ] + } + }, + { + "name": "energy_summed_across_classes", + "about": "a counter row that adds the model's joules to its own", + "tariff": "574c543101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c698f25ea3a2c247f29e94b9280ebf10b50c1a7412b49a0b559a2c131c0b87ba0100a0cd18bc5c06000040e2b52b5f0600455552da0a00006d050000da0a0000b41500008d2000000000000000000000000000000000000016010000b41500006d05000064000000002d31010000000080f0fa020000000000b4c40400000000404b4c000000000020a1070000000000e80300000000000080841e0000000000dc05000000000000e8030000000000000100012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaacf17c74c9c910f52f43d79a19acaa5709cb0f5e2ff12a7d4254a79d138916276994f1d8fa9cbe38dbb250991078584c7e58e64efd99cfaeb537d9f93a1a1fd05444041dff0fe81c30d2ac7b9c22fe03e5ee5f1db076c603ce57f00585dc61b6c12cee938d4e60447f783ff791ed403e080f9b4e2c00e0b5b27b5846f0f03d503", + "invoice": "574c493101c45c21d62e3beb229eff8f9d1160ccbfd6fcc2ff7d3fdd5cffca4475da5619c698f25ea3a2c247f29e94b9280ebf10b50c1a7412b49a0b559a2c131c0b87ba01294dc15f852b7213e824fedc8073c4b57764d9d02d365352a2824c1e0221a2d3000000000000000000000000000000000000000000000000000000000000000000406cd8e95d060000a043f6fd5d06004555520400000001d0b7e1999618911e90c6fcf97ef1424b2960976ac649cd1212e8888334305427029f2e96e3cf68df9d2fce5457c50c8469006e663b04a03ec8d389b77a51a646b702c3193ab4736af61acb0b4177aa6ec8fa8ee0924b3f727e9f21eb20b9dc72ef2d02d969cc740d80efd2c77bb615616ea1325471a58c89a1740db6d40fa8c85be6a80800012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa012055cc0400000000da0a0000000000008d690300000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e634012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa02c0f52204000000006d050000000000008c780100000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e634012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa03801b680100000000da0a00000000000018000100000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e634012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa04801b680100000000b41500000000000031000200000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e634012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0a185662020000000016010000000000006f2b0000000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e634012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0b4054890000000000b41500000000000054c30000000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e634012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0c002d3101000000006d05000000000000846c0000000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e634012c3f0f62be557631417a492ef4abb5499bf32c6c980dbc9c0539769ecc2bcaaa0d2c00000000000000640000000000000030110000000000000100000041600e6ea4837e25ed69c856c9895f8c9f3515c6b0817e1940d7a284c979e63403010201000000c00e1602000000000000000001030100000080841e000000000000000000010401000000c0d12e0300000000204e000000000000000000000000000000000000d94e09000000000087c25c6b98eddd4312cb1bf6ea5b1ca51487367ea87abf43ac6e57677eba8495c387f6402a31c846f73d8821321bce915e29a5436c179fce71f38a98f1303405", + "previous": null, + "sources": [ + "lesson_mixed_classes" + ], + "outcome": { + "outcome": "refuse", + "reason": "classes_summed", + "at": [ + "invoice" + ] + } + } + ] +} diff --git a/wai/live-conformance/receipt_ledger_verify.py b/wai/live-conformance/receipt_ledger_verify.py new file mode 100644 index 000000000..16005d0f6 --- /dev/null +++ b/wai/live-conformance/receipt_ledger_verify.py @@ -0,0 +1,880 @@ +#!/usr/bin/env python3 +"""Independent verifier of the receipt-ledger corpus (extensions/receipt-ledger.md). + +Written from the extension's text. It shares no code with wai-rs, which generates the corpus, +and never runs it. It checks Ed25519 signatures with ../claim-conformance/_ed25519.py, WAI's +strict rule (SPEC §7 *Signatures*) written from RFC 8032 with the standard library. Each source +is verified by the second implementation of the corpus of its own extension, each written from +its text alone: a room-session receipt by live_metering_verify.py (live-metering §5), a +settlement statement by settlement_verify.py (settlement §8), a package receipt by +../pack-conformance/pack_verify.py (packaging §9.1), and an ads room by live_ads_verify.py with +ad_presentation_verify.py (live-ads §9, ad-presentation §9). Everything else is here: + +- the tariff (§4): reading, rules, both signatures, its scope; +- the invoice (§6): reading, rules, signature, chain; +- what each source contributes (§5.2), its placement in time (§5.3), and the checks across + sources (§7.2 step 6); +- every line, citation, energy row and amount recomputed (§6.2 to §6.6), compared in §7.2 step + 7's order; +- the two reports (§8). + +For every vector it requires the outcome the corpus states; of the corpus, that every reason of +§10 appears in it and that no other file is there. Then it mutates copies of the corpus, one +defect each, and requires every copy to fail; and it drops each of its own checks in turn and +requires the corpus to catch the weakened verifier. + + python3 -I receipt_ledger_verify.py + +Exit status 0 only if every vector verifies, every mutated copy fails and every weakened verifier +fails. Python 3.8 or later; standard library only. +""" + +import copy +import hashlib +import json +import os +import re +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HERE) +sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance")) +sys.path.insert(0, os.path.join(HERE, "..", "pack-conformance")) +import _ed25519 as ED # noqa: E402 +import live_rooms_verify as LRV # noqa: E402 +import live_metering_verify as LMV # noqa: E402 +import settlement_verify as SV # noqa: E402 +import live_ads_verify as LAV # noqa: E402 +import ad_presentation_verify as APV # noqa: E402 +import pack_verify as PV # noqa: E402 + +EXTENSION = os.path.join(HERE, "..", "extensions", "receipt-ledger.md") + +TARIFF_MAGIC, INVOICE_MAGIC = b"WLT1", b"WLI1" # §4.1, §6.1 +TARIFF_DOMAIN = b"wai:ledger-tariff\x01" # §4.2 +ACCEPT_DOMAIN = b"wai:ledger-accept\x01" # §4.2 +INVOICE_DOMAIN = b"wai:ledger-invoice\x01" # §6.7 +CITES_DOMAIN = b"wai:ledger-cites\x01" # §6.5 +ZERO = bytes(32) +U64 = 2 ** 64 - 1 +ROOM, DELIVERY, PACKAGE, ADS = 1, 2, 3, 4 # §6.3 source kinds +SESSION, HOP, STATEMENT, ACK, PACK, AD_RECEIPT, CLAIM = 1, 2, 3, 4, 5, 6, 7 # §5.2 +KIND_NAMES = {ROOM: "room", DELIVERY: "delivery", PACKAGE: "package", ADS: "ads"} +SCOPES = {1: "room_hops", 2: "delivery_hops", 3: "packaging", 4: "ad_decisions"} # §6.6 +LABELS = {2: "ModelBased", 3: "Estimator", 4: "OnChipCounter", 5: "CalibratedInstrument"} +TAGS = {v: k for k, v in LABELS.items()} +CLASS_NAMES = ["live", "fetch", "fill", "ancillary"] +UNITS = { # §6.3: name, divisor + ROOM: [("publisher_us", 10 ** 6)] + [("subscriber_us_%d" % c, 10 ** 6) for c in range(9)] + + [("composition_us", 10 ** 6), ("recorded_us", 10 ** 6), ("events_sent", 1)], + DELIVERY: [(n, 10 ** 9) for n in CLASS_NAMES], + PACKAGE: [("packages", 1), ("entries", 1), ("stored_bytes", 10 ** 9)], + ADS: [("presented_impressions", 1), ("billable_impressions", 1)], +} + +SKIP = set() + + +class Refuse(Exception): + def __init__(self, reason, at): + super().__init__(reason) + self.reason, self.at = reason, at + + +def sha(*parts): + h = hashlib.sha256() + for p in parts: + h.update(p) + return h.digest() + + +class R: + """A cursor over little-endian fields; running out is `truncated`.""" + + def __init__(self, b, p=0): + self.b, self.p = b, p + + def take(self, n): + if self.p + n > len(self.b): + raise ValueError("truncated") + s = self.b[self.p:self.p + n] + self.p += n + return s + + def u(self, n): + return int.from_bytes(self.take(n), "little") + + +def head(b, magic): + """§4.3's first steps.""" + if len(b) < 4: + return "truncated" + if b[:4] != magic: + return "malformed" + if len(b) < 5: + return "truncated" + if b[4] != 1: + return "unknown_version" + return None + + +def capitals(c): + return len(c) == 3 and all(0x41 <= x <= 0x5A for x in c) + + +def ascending(xs): + return all(a < b for a, b in zip(xs, xs[1:])) + + +def verify_sig(key, domain, body, sig): + return None if "signatures" in SKIP else ED.verify(key, domain + body, sig) + + +# ---------------------------------------------------------------- §4 the tariff + +def read_tariff(b): + bad = head(b, TARIFF_MAGIC) + if bad: + raise Refuse(bad, ["tariff"]) + r = R(b, 5) + try: + t = {"seller": r.take(32), "buyer": r.take(32), "start": r.u(8), "end": r.u(8), "currency": r.take(3)} + t["room"] = [r.u(4) for _ in range(13)] + t["delivery"] = [r.u(8) for _ in range(4)] + t["package"] = [r.u(8) for _ in range(3)] + t["ads"] = [r.u(8) for _ in range(2)] + t["scope"] = [(r.u(1), r.take(32)) for _ in range(r.u(2))] + body_end = r.p + seller_sig, buyer_sig = r.take(64), r.take(64) + except ValueError: + raise Refuse("truncated", ["tariff"]) + if r.p != len(b): + raise Refuse("malformed", ["tariff"]) + rules = (t["start"] < t["end"] and capitals(t["currency"]) and t["seller"] != ZERO and t["buyer"] != ZERO + and t["seller"] != t["buyer"] and t["scope"] and ascending(t["scope"]) + and all(1 <= k <= 4 and i != ZERO for k, i in t["scope"])) + if not rules and "tariff-rules" not in SKIP: + raise Refuse("tariff_invalid", ["tariff"]) + body = b[:body_end] + bad = verify_sig(t["seller"], TARIFF_DOMAIN, body, seller_sig) + if bad: + raise Refuse(bad, ["tariff", "seller"]) + bad = None if "buyer-signature" in SKIP else verify_sig(t["buyer"], ACCEPT_DOMAIN, body, buyer_sig) + if bad: + raise Refuse(bad, ["tariff", "buyer"]) + t["digest"] = sha(body) + return t + + +def price(t, kind, unit): + return {ROOM: t["room"], DELIVERY: t["delivery"], PACKAGE: t["package"], ADS: t["ads"]}[kind][unit - 1] + + +# ---------------------------------------------------------------- §6 the invoice + +def read_invoice(b, at): + bad = head(b, INVOICE_MAGIC) + if bad: + raise Refuse(bad, at) + r = R(b, 5) + try: + i = {"seller": r.take(32), "buyer": r.take(32), "tariff": r.take(32), "prev": r.take(32), "start": r.u(8), + "end": r.u(8), "currency": r.take(3)} + i["records"] = [(r.u(1), r.take(32)) for _ in range(r.u(4))] + i["lines"] = [] + for _ in range(r.u(2)): + i["lines"].append({"kind": r.u(1), "source": r.take(32), "unit": r.u(1), "quantity": r.u(8), "price": r.u(8), + "amount": r.u(8), "cited": r.u(4), "cites": r.take(32)}) + i["energy"] = [(r.u(1), r.u(1), r.u(4), r.u(8), r.u(4)) for _ in range(r.u(1))] + i["unmetered"] = [r.u(4) for _ in range(4)] + i["amount"] = r.u(8) + sig = r.take(64) + except ValueError: + raise Refuse("truncated", at) + if r.p != len(b): + raise Refuse("malformed", at) + if any(e[1] == 0 for e in i["energy"]) and "row-of-no-class" not in SKIP: + raise Refuse("classes_summed", at) + keys = [(x["kind"], x["source"], x["unit"]) for x in i["lines"]] + rules = (i["start"] < i["end"] and capitals(i["currency"]) and ascending(i["records"]) + and all(1 <= k <= 7 for k, _ in i["records"]) and ascending(keys) + and all(x["kind"] in UNITS and 1 <= x["unit"] <= len(UNITS[x["kind"]]) and x["quantity"] > 0 and x["cited"] > 0 + for x in i["lines"]) + and ascending([(e[0], e[1]) for e in i["energy"]]) + and all(1 <= e[0] <= 4 and 2 <= e[1] <= 5 and e[2] > 0 for e in i["energy"])) + if not rules and "invoice-rules" not in SKIP: + raise Refuse("invoice_invalid", at) + bad = verify_sig(i["seller"], INVOICE_DOMAIN, b[:-64], sig) + if bad: + raise Refuse(bad, at) + i["digest"] = sha(b[:-64]) + return i + + +# ---------------------------------------------------------------- §5 sources + +def figure(joules, cls): + """(tag or None, joules, declared ppm): a figure as §6.6 counts it.""" + if cls is None or joules == 0: + return (None, 0, 0) + return (cls[0], joules, cls[1] if cls[0] in (4, 5) else 0) + + +def room_source(s): + keys = {"kind", "room", "receipt", "hops", "descriptors", "compositions", "takes", "logs"} + if set(s) != keys or not isinstance(s["room"], dict) or set(s["room"]) != {"descriptor", "grants", "key_log_head", "chains"}: + raise Refuse("malformed", []) + doc = {"rooms": {"r": s["room"]}} + v = {"room": "r", "receipt": s["receipt"], "hops": s["hops"], "descriptors": s["descriptors"], + "compositions": s["compositions"], "takes": s["takes"], "logs": s["logs"]} + got = LMV.verify_vector(v, doc, LMV.Cache()) + if got["outcome"] != "accept": + raise Refuse(got["reason"], got["at"]) + d = LRV.read_descriptor(bytes.fromhex(s["room"]["descriptor"])) + rc = LMV.read_receipt(bytes.fromhex(s["receipt"]), d["room"]) + u = rc["units"] + units = [u["publisher_us"]] + list(u["subscriber_us"]) + [u["composition_us"], u["recorded_us"], u["events_sent"]] + return {"kind": ROOM, "digest": rc["digest"], "room": d["room"], "t0": d["t0"], "start": rc["start"], "end": rc["end"], + "units": units, "currency": rc["currency"], "prices": rc["prices"], + "hops": [(h["record"], figure(h["joules"], h["class"])) for h in rc["hops"]]} + + +def delivery_source(s): + if set(s) != {"kind", "statement", "records", "proofs", "hops", "rooms"}: + raise Refuse("malformed", []) + doc = {"records": [{"record": r["record"], "items": r["items"]} for r in s["records"]], + "proofs": [{"proof": p} for p in s["proofs"]], "hops": [{"hop": h} for h in s["hops"]], + "rooms": {str(k): r for k, r in enumerate(s["rooms"])}} + v = {"statement": s["statement"], "rooms": [str(k) for k in range(len(s["rooms"]))], "withhold": [], + "extra": {"records": [], "proofs": []}} + got = SV.statement_outcome(doc, v) + if got["outcome"] != "accept": + raise Refuse(got["reason"], got["at"]) + st = SV.read_statement(bytes.fromhex(s["statement"])) + given = {} + for r in s["records"]: + b = bytes.fromhex(r["record"]) + given[sha(b[:-64])] = (b, [bytes.fromhex(x) for x in r["items"]]) + acks = [] + for d in st["records"]: + b, lists = given[d] + a = SV.read_ack(b) + a["item_lists"] = [SV.read_items(x) for x in lists] + acks.append(a) + hops = {} + for h in s["hops"]: + b = bytes.fromhex(h) + hops[sha(b[:-64])] = b + figs = [] + for d in st["hops"]: + x = LMV.read_hop(hops[d], hops[d][5:37]) + figs.append((d, figure(x["joules"], x["class"]))) + return {"kind": DELIVERY, "digest": st["digest"], "statement": st, "acks": acks, "hops": figs} + + +def package_source(s): + if set(s) != {"kind", "receipt", "indexes"} or not isinstance(s["receipt"], str): + raise Refuse("malformed", []) + try: + r = PV.read_receipt(s["receipt"].encode("utf-8")) + PV.verify_receipt_v2(r, [bytes.fromhex(x) for x in s["indexes"]]) + except PV.Refused as e: + raise Refuse(e.code, ["receipt"]) + j = r["json"] + cls = None + if j["energy_provenance"] is not None: + tag = TAGS[j["energy_provenance"]] + cls = (tag, j["energy_uncertainty"]["relative_ppm"] if tag in (4, 5) else 0) + return {"kind": PACKAGE, "digest": bytes.fromhex(j["receipt_hash"]), "signer": bytes.fromhex(j["signer_pubkey"]), + "entries": j["total_entries"], "stored": j["stored_bytes"], "figure": figure(j["joules_micro"], cls)} + + +def ads_source(s): + keys = {"kind", "descriptor", "grants", "key_log", "chains", "policy", "pt0_ms", "receipts", "pods", "claims", + "creatives", "issuers"} + if set(s) != keys: + raise Refuse("malformed", []) + holder = {"creatives": s["creatives"], "issuers": s["issuers"]} + APV.held(holder) + v = dict(s, name="source") + got, _ = LAV.ads(v, judge=lambda vv, d, p: APV.Judge(holder, vv, p)) + if got["outcome"] == "gap": + raise Refuse("source_gap", []) + if got["outcome"] != "accept": + raise Refuse(got["reason"], got["at"]) + d = LRV.read_descriptor(bytes.fromhex(s["descriptor"])) + claims_required = "claim_facts" in got + breaks = {bytes.fromhex(b["digest"]): b["t"] for b in got["breaks"]} + claim_digests = [None if c["digest"] is None else bytes.fromhex(c["digest"]) for c in got.get("claim_facts", [])] + receipts = [] + for o, fact in zip(s["receipts"], got["receipt_facts"]): + r = LAV.read_receipt(o) + imps = [] + for k, i in enumerate(r["impressions"]): + billable = i["quartiles_reached"] >= 1 + if claims_required: + c = fact["impressions"][k] + counts = c["verdict"] == "presented" + verdict = c["verdict"] + claim = claim_digests[c["claim"]] if counts and c["claim"] is not None else None + else: + counts, verdict, claim = billable, "beacons", None + imps.append({"slot": i["slot"], "ad_id": i["ad_id"], "price_micros": i["price_micros"], + "quartiles_reached": i["quartiles_reached"], "billable": billable, "counts": counts, + "verdict": verdict, "claim": claim}) + cls = None + if r["class"] is not None: + tag = TAGS[r["class"]["label"]] + cls = (tag, r["class"].get("ppm", 0)) + receipts.append({"identity": bytes.fromhex(fact["identity"]), "decider": r["signer_pubkey"], "cue": r["cue"], + "session": r["session"], "t": breaks[r["cue"]], "spend": r["budget_spent_micros"], + "counted_spend": fact.get("presented_spend_micros", r["budget_spent_micros"]), + "impressions": imps, "figure": figure(r["joules_micro"], cls)}) + return {"kind": ADS, "room": d["room"], "t0": d["t0"], "claims_required": claims_required, "receipts": receipts, + "claims": [c for c in claim_digests if c is not None]} + + +# A source's verification is a pure function of the source, and the corpus verifies the same +# sources under every mutated copy and every weakened verifier (none of whose checks is a +# source's own): each is verified once. +_SOURCES = {} + + +def verify_source(s): + """§5.1: the source verified by its own extension, or Refuse with that extension's place.""" + key = json.dumps(s, sort_keys=True) + if key not in _SOURCES: + try: + _SOURCES[key] = read_source(s) + except Refuse as e: + _SOURCES[key] = e + got = _SOURCES[key] + if isinstance(got, Refuse): + raise Refuse(got.reason, got.at) + return got + + +def read_source(s): + if not isinstance(s, dict): + raise Refuse("malformed", []) + readers = {"room": room_source, "delivery": delivery_source, "package": package_source, "ads": ads_source} + if s.get("kind") not in readers: + raise Refuse("malformed", []) + return readers[s["kind"]](s) + + +def heads(f): + """§5.2.""" + if f["kind"] == ROOM: + return [(SESSION, f["digest"])] + if f["kind"] == DELIVERY: + return [(STATEMENT, f["digest"])] + if f["kind"] == PACKAGE: + return [(PACK, f["digest"])] + return [(AD_RECEIPT, r["identity"]) for r in f["receipts"]] + + +def contributed(f): + """§5.2: every record a source contributes.""" + out = set(heads(f)) + if f["kind"] == ROOM: + out |= {(HOP, d) for d, _ in f["hops"]} + elif f["kind"] == DELIVERY: + out |= {(ACK, d) for d in f["statement"]["records"]} | {(HOP, d) for d in f["statement"]["hops"]} + elif f["kind"] == ADS: + out |= {(CLAIM, c) for c in f["claims"]} + return out + + +def in_scope(t, kind, i): + return "scope" in SKIP or (kind, i) in t["scope"] + + +def check_source(t, start, end, f): + """§7.2 step 5 after the source's own verification: scope, one tariff (§3), period (§5.3).""" + def within(a, b): + return "period" in SKIP or (start <= a and b <= end) + if f["kind"] == ROOM: + if not in_scope(t, 1, f["room"]): + return "outside_scope" + if f["currency"] != bytes(3) and (f["currency"] != t["currency"] or f["prices"] != t["room"]) and "conflict" not in SKIP: + return "tariff_conflict" + if not within(f["t0"] + f["start"], f["t0"] + f["end"]): + return "outside_period" + elif f["kind"] == DELIVERY: + st = f["statement"] + if not in_scope(t, 2, st["party"]): + return "outside_scope" + if st["currency"] != bytes(3) and (st["currency"] != t["currency"] or st["prices"] != t["delivery"]) and "conflict" not in SKIP: + return "tariff_conflict" + if not within(st["start"], st["end"]): + return "outside_period" + elif f["kind"] == PACKAGE: + if not in_scope(t, 3, f["signer"]): + return "outside_scope" + else: + if not all(in_scope(t, 4, r["decider"]) for r in f["receipts"]): + return "outside_scope" + if "period" not in SKIP and not all(start <= f["t0"] + r["t"] < end for r in f["receipts"]): + return "outside_period" + return None + + +def overlapping(a, b): + return a[0] < b[1] and b[0] < a[1] + + +def bills_again(f, g): + """§5.3: whether f bills what g, an earlier source, billed.""" + if f["kind"] != g["kind"]: + return False + if f["kind"] == ROOM: + return f["room"] == g["room"] and overlapping((f["start"], f["end"]), (g["start"], g["end"])) + if f["kind"] == DELIVERY: + a, b = f["statement"], g["statement"] + return a["party"] == b["party"] and overlapping((a["start"], a["end"]), (b["start"], b["end"])) + if f["kind"] == ADS: + return f["room"] == g["room"] and any((x["cue"], x["session"]) == (y["cue"], y["session"]) + for x in f["receipts"] for y in g["receipts"]) + return False + + +def check_across(facts, previous): + """§7.2 step 6: (the index, the reason) of the first source refused, or None.""" + billed = set(previous["records"]) if previous else set() + seen = set() + for k, f in enumerate(facts): + hs = heads(f) + if "duplicate" not in SKIP and (any(h in seen for h in hs) or len(set(hs)) != len(hs)): + return k, "record_duplicate" + if "overlap" not in SKIP and any(bills_again(f, g) for g in facts[:k]): + return k, "record_overlap" + if "rebilled" not in SKIP and contributed(f) & billed: + return k, "record_rebilled" + seen |= set(hs) + return None + + +# ---------------------------------------------------------------- §6.2–§6.6 the figures + +def free(c, grant): + return c in (2, 3) and grant == ZERO + + +def cites_digest(cited): + return sha(CITES_DOMAIN, b"".join(bytes([k]) + i for k, i in sorted(cited))) + + +def compute(t, facts): + """The records, lines (with their cited records), rows, unmetered counts, amount and the sums + per (scope, tag); None when a figure does not fit its field.""" + records = set() + for f in facts: + records |= contributed(f) + q = {} + + def add(kind, source, unit, n, cites): + if n == 0: + return + e = q.setdefault((kind, source, unit), [0, set()]) + e[0] += n + e[1] |= set(cites) + for f in facts: + if f["kind"] == ROOM: + for unit in range(1, 14): + add(ROOM, f["room"], unit, f["units"][unit - 1], [(SESSION, f["digest"])]) + elif f["kind"] == DELIVERY: + st = f["statement"] + for c in range(1, 5): + n = sum(x["bytes"] for x in st["lines"] if x["kind"] == 1 and x["class"] == c and not free(c, x["payer"])) + if n == 0: + continue + cites = [(STATEMENT, f["digest"])] + cites += [(ACK, a["digest"]) for a in f["acks"] + if a["mode"] == 1 and any(e["class"] == c and not free(c, e["grant"]) for e in a["entries"])] + add(DELIVERY, st["party"], c, n, cites) + elif f["kind"] == PACKAGE: + cite = [(PACK, f["digest"])] + add(PACKAGE, f["signer"], 1, 1, cite) + add(PACKAGE, f["signer"], 2, f["entries"], cite) + add(PACKAGE, f["signer"], 3, f["stored"], cite) + else: + unit = 1 if f["claims_required"] else 2 + for r in f["receipts"]: + counted = [i for i in r["impressions"] if i["counts"]] + cites = [(AD_RECEIPT, r["identity"])] + [(CLAIM, i["claim"]) for i in counted if i["claim"] is not None] + add(ADS, r["decider"], unit, len(counted), cites) + lines, total = [], 0 + for (kind, source, unit) in sorted(q): + n, cited = q[(kind, source, unit)] + p = price(t, kind, unit) + a = n * p // UNITS[kind][unit - 1][1] + if n > U64 or a > U64: + return None + total += a + lines.append({"kind": kind, "source": source, "unit": unit, "quantity": n, "price": p, "amount": a, + "cited": len(cited), "cites": cites_digest(cited), "cited_records": sorted(cited)}) + figs, seen = [], set() + for f in facts: + if f["kind"] == ROOM: + for d, fig in f["hops"]: + if d not in seen: + seen.add(d) + figs.append((1, fig)) + for f in facts: + if f["kind"] == DELIVERY: + for d, fig in f["hops"]: + if d not in seen: + seen.add(d) + figs.append((2, fig)) + for f in facts: + if f["kind"] == PACKAGE: + figs.append((3, f["figure"])) + elif f["kind"] == ADS: + figs += [(4, r["figure"]) for r in f["receipts"]] + rows, unmetered, sums = rows_of(figs) + if rows is None or total > U64: + return None + return {"records": sorted(records), "lines": lines, "energy": rows, "unmetered": unmetered, "amount": total, "sums": sums} + + +def rows_of(figs): + unmetered = [0, 0, 0, 0] + acc = {} + for s, (tag, joules, ppm) in figs: + if tag is None: + unmetered[s - 1] += 1 + continue + e = acc.setdefault((s, tag), [0, 0, 0]) + e[0] += 1 + e[1] += joules + e[2] = max(e[2], ppm) + rows = [(s, tag) + tuple(acc[(s, tag)]) for s, tag in sorted(acc)] + if any(r[3] > U64 for r in rows): + return None, unmetered, {} + return rows, unmetered, {k: v[1] for k, v in acc.items()} + + +def summed_across(row, sums): + """§6.6: a row whose joules sum its class and another class of its scope, not its own.""" + s, tag, _, joules, _ = row + own = sums.get((s, tag)) + if own is None or own == joules: + return False + others = [j for (sc, tg), j in sums.items() if sc == s and tg != tag] + for m in range(1, 1 << len(others)): + if own + sum(j for i, j in enumerate(others) if m >> i & 1) == joules: + return True + return False + + +def check_figures(inv, c): + """§7.2 step 7, in its order.""" + got = {(x["kind"], x["source"], x["unit"]): x for x in c["lines"]} + for x in inv["lines"]: + g = got.get((x["kind"], x["source"], x["unit"])) + if "quantity" not in SKIP and (g is None or x["quantity"] > g["quantity"]): + return "quantity_not_recorded" + if "summed" not in SKIP and any(summed_across(r, c["sums"]) for r in inv["energy"]): + return "classes_summed" + for x in inv["lines"]: + g = got.get((x["kind"], x["source"], x["unit"])) + if "citations" not in SKIP and g is not None and (x["cited"] != g["cited"] or x["cites"] != g["cites"]): + return "citation_mismatch" + strip = [{k: v for k, v in x.items() if k != "cited_records"} for x in c["lines"]] + if "mismatch" not in SKIP and (inv["records"] != c["records"] or inv["lines"] != strip or inv["energy"] != c["energy"] + or inv["unmetered"] != c["unmetered"] or inv["amount"] != c["amount"]): + return "invoice_mismatch" + return None + + +# ---------------------------------------------------------------- §8 the reports + +def rows_json(rows): + return [{"class": LABELS[r[1]], "figures": r[2], "joules": r[3], "max_ppm": r[4]} for r in rows] + + +def ads_report(f): + receipts, nc = [], {} + tot = {"b": 0, "c": 0, "bs": 0, "cs": 0} + for r in f["receipts"]: + b = sum(1 for i in r["impressions"] if i["billable"]) + c = sum(1 for i in r["impressions"] if i["counts"]) + for i in r["impressions"]: + if i["billable"] and not i["counts"]: + nc[i["verdict"]] = nc.get(i["verdict"], 0) + 1 + tot["b"] += b + tot["c"] += c + tot["bs"] += r["spend"] + tot["cs"] += r["counted_spend"] + receipts.append({ + "identity": r["identity"].hex(), "decider": r["decider"].hex(), "cue": r["cue"].hex(), "session": r["session"].hex(), + "impressions": [{k: i[k] for k in ("slot", "ad_id", "price_micros", "quartiles_reached", "billable", "counts", "verdict")} + for i in r["impressions"]], + "billable_impressions": b, "billable_spend_micros": r["spend"], "counted_impressions": c, + "counted_spend_micros": r["counted_spend"]}) + return {"room": f["room"].hex(), "claims_required": f["claims_required"], "receipts": receipts, + "billable_impressions": tot["b"], "counted_impressions": tot["c"], "billable_spend_micros": min(tot["bs"], U64), + "counted_spend_micros": min(tot["cs"], U64), "not_counted": dict(sorted(nc.items()))} + + +def overlaps_of(facts): + """§8.2: by (content hash, downstream, first party, first record, second party, second record), + the bytes their items share.""" + acks = [a for f in facts if f["kind"] == DELIVERY for a in f["acks"] if a["mode"] == 1] + out = {} + for x in acks: + for y in acks: + if not (x["upstream"] < y["upstream"] and x["downstream"] == y["downstream"] + and overlapping((x["start"], x["end"]), (y["start"], y["end"]))): + continue + for a in [i for l in x["item_lists"] for i in l]: + for b in [i for l in y["item_lists"] for i in l]: + if a[0] == b[0] and overlapping(a[1:], b[1:]): + k = (a[0], x["downstream"], x["upstream"], x["digest"], y["upstream"], y["digest"]) + out[k] = min(out.get(k, 0) + min(a[2], b[2]) - max(a[1], b[1]), U64) + return out + + +def delivery_report(facts, c): + parties = {} + for f in facts: + if f["kind"] == DELIVERY: + parties.setdefault(f["statement"]["party"], []).append(f) + rows = [] + for party in sorted(parties): + fs = parties[party] + classes = [[0, 0] for _ in range(4)] + free_bytes = stated = 0 + declared = [0, 0, 0] + figs, seen = [], set() + currency = next((f["statement"]["currency"] for f in fs if f["statement"]["currency"] != bytes(3)), None) + for f in fs: + st = f["statement"] + for k in range(4): + classes[k][0] += st["classes"][k][0] + classes[k][1] += st["classes"][k][1] + free_bytes += sum(x["bytes"] for x in st["lines"] if x["kind"] == 1 and free(x["class"], x["payer"])) + stated += st["amount"] + for k in range(3): + declared[k] += st["unack"][k] + for d, fig in f["hops"]: + if d not in seen: + seen.add(d) + figs.append((2, fig)) + energy, unmetered, _ = rows_of(figs) + rows.append({ + "party": party.hex(), "statements": [f["digest"].hex() for f in fs], + "currency": None if currency is None else currency.decode("ascii"), + "classes": {CLASS_NAMES[k]: {"objects": classes[k][0], "bytes": classes[k][1]} for k in range(4)}, + "free_bytes": free_bytes, + "declared": {"declarations": declared[0], "objects": declared[1], "bytes": declared[2]}, + "stated_amount": stated, + "invoiced_amount": sum(x["amount"] for x in c["lines"] if x["kind"] == DELIVERY and x["source"] == party), + "energy": rows_json(energy or []), "unmetered": unmetered[1]}) + ov = overlaps_of(facts) + overlaps = [{"content_hash": h.hex(), "downstream": d.hex(), "bytes": ov[(h, d, p1, r1, p2, r2)], + "first": {"party": p1.hex(), "record": r1.hex()}, "second": {"party": p2.hex(), "record": r2.hex()}} + for (h, d, p1, r1, p2, r2) in sorted(ov)] + return {"parties": rows, "overlaps": overlaps} + + +# ---------------------------------------------------------------- §7 verification + +def unhex_field(doc, k): + v = doc.get(k) + if not isinstance(v, str) or not re.fullmatch(r"(?:[0-9a-f]{2})*", v): + raise Refuse("malformed", [k]) + return bytes.fromhex(v) + + +def verify(doc): + """§7.2: the outcome of a verification document.""" + try: + tb, ib = unhex_field(doc, "tariff"), unhex_field(doc, "invoice") + pb = None if doc.get("previous") is None else unhex_field(doc, "previous") + sources = doc.get("sources") + if not isinstance(sources, list): + raise Refuse("malformed", ["source"]) + t = read_tariff(tb) # step 1 + inv = read_invoice(ib, ["invoice"]) # step 2 + if "binding" not in SKIP and (inv["tariff"] != t["digest"] or inv["seller"] != t["seller"] + or inv["buyer"] != t["buyer"] or inv["currency"] != t["currency"]): # step 3 + raise Refuse("tariff_mismatch", ["invoice"]) + if "outside-tariff" not in SKIP and not (t["start"] <= inv["start"] and inv["end"] <= t["end"]): + raise Refuse("outside_tariff", ["invoice"]) + prev = None + if inv["prev"] != ZERO: # step 4 + if "previous-given" not in SKIP and (pb is None or sha(pb[:-64]) != inv["prev"]): + raise Refuse("record_missing", ["previous"]) + if pb is not None: + prev = read_invoice(pb, ["previous"]) + if prev["tariff"] != inv["tariff"] or prev["seller"] != inv["seller"] or prev["buyer"] != inv["buyer"]: + raise Refuse("tariff_mismatch", ["previous"]) + if "previous-overlap" not in SKIP and prev["end"] > inv["start"]: + raise Refuse("invoice_overlap", ["previous"]) + facts = [] + for k, s in enumerate(sources): # step 5 + try: + f = verify_source(s) + except Refuse as e: + raise Refuse(e.reason, ["source", k] + list(e.at)) + bad = check_source(t, inv["start"], inv["end"], f) + if bad: + raise Refuse(bad, ["source", k]) + facts.append(f) + across = check_across(facts, prev) # step 6 + if across: + raise Refuse(across[1], ["source", across[0]]) + c = compute(t, facts) # step 7 + if c is None: + raise Refuse("invoice_mismatch", ["invoice"]) + bad = check_figures(inv, c) + if bad: + raise Refuse(bad, ["invoice"]) + except Refuse as e: + return {"outcome": "refuse", "reason": e.reason, "at": e.at} + return accepted(inv, facts, c) + + +def accepted(inv, facts, c): + unmetered, coverage = {}, {} + for s in range(1, 5): + n = inv["unmetered"][s - 1] + unmetered[SCOPES[s]] = n + coverage[SCOPES[s]] = "partial" if n > 0 else "whole" if any(r[0] == s for r in inv["energy"]) else "none" + return { + "outcome": "accept", + "digest": inv["digest"].hex(), + "tariff": inv["tariff"].hex(), + "currency": inv["currency"].decode("ascii"), + "start": inv["start"], + "end": inv["end"], + "records": len(inv["records"]), + "lines": [{"source_kind": KIND_NAMES[x["kind"]], "source": x["source"].hex(), "unit": UNITS[x["kind"]][x["unit"] - 1][0], + "quantity": x["quantity"], "price": x["price"], "amount": x["amount"], + "cites": [[k, i.hex()] for k, i in x["cited_records"]]} for x in c["lines"]], + "energy": [{"scope": SCOPES[r[0]], "class": LABELS[r[1]], "figures": r[2], "joules": r[3], "max_ppm": r[4]} + for r in inv["energy"]], + "unmetered": unmetered, + "coverage": coverage, + "amount": inv["amount"], + "reports": {"ads": [ads_report(f) for f in facts if f["kind"] == ADS], "delivery": delivery_report(facts, c)}, + } + + +# ---------------------------------------------------------------- the corpus + +def document(doc, v): + return {"tariff": v["tariff"], "invoice": v["invoice"], "previous": v["previous"], + "sources": [doc["sources"][n] for n in v["sources"]]} + + +def check(doc, quiet=False): + """The failures: every vector whose stated outcome is not the one this verifier reaches.""" + fails = [] + for v in doc.get("vectors", []): + try: + got = verify(document(doc, v)) + except Exception as e: # a crash on a mutated copy is a failure to verify it + got = {"outcome": "crash", "error": "%s: %s" % (type(e).__name__, e)} + ok = got == v.get("outcome") + if not quiet: + what = got["outcome"] + (" %s at %s" % (got["reason"], got["at"]) if got["outcome"] == "refuse" else "") + print(("PASS" if ok else "FAIL") + " %s: %s" % (v.get("name"), what)) + if not ok: + fails.append(v.get("name")) + return fails + + +def reasons_listed(): + """§10's table, in order.""" + text = open(EXTENSION, encoding="utf-8").read() + at = text.index("\n## 10. Reasons") + end = text.find("\n## ", at + 1) + return [m.group(1) for m in re.finditer(r"^\| `([a-z_]+)` \|", text[at:end], re.M)] + + +def mutations(doc): + """Copies of the corpus, one defect each, that a verifier must reject.""" + def flip_hex(h, at): + b = bytearray(bytes.fromhex(h)) + b[at % len(b)] ^= 0x01 + return b.hex() + out = [] + for k, v in enumerate(doc["vectors"]): + if v["outcome"]["outcome"] != "accept": + continue + for field, at in (("tariff", 6), ("tariff", -1), ("invoice", 40), ("invoice", -70), ("invoice", -1)): + m = copy.deepcopy(doc) + m["vectors"][k][field] = flip_hex(v[field], at) + out.append(("%s: %s byte %d flipped" % (v["name"], field, at), m)) + m = copy.deepcopy(doc) + m["vectors"][k]["sources"] = v["sources"][:-1] + out.append(("%s: its last source dropped" % v["name"], m)) + m = copy.deepcopy(doc) + m["vectors"][k]["outcome"]["amount"] += 1 + out.append(("%s: its stated amount raised" % v["name"], m)) + m = copy.deepcopy(doc) + m["vectors"][k]["outcome"]["lines"][0]["cites"] = [] + out.append(("%s: its first line's citations dropped" % v["name"], m)) + m = copy.deepcopy(doc) + rep = m["vectors"][0]["outcome"]["reports"] + rep["delivery"]["overlaps"] = [] + out.append(("a_day: its overlaps dropped", m)) + m = copy.deepcopy(doc) + m["vectors"][0]["outcome"]["reports"]["ads"][0]["not_counted"] = {} + out.append(("a_day: its ad report's not-counted figures dropped", m)) + for k, v in enumerate(doc["vectors"]): + if v["outcome"]["outcome"] == "refuse": + m = copy.deepcopy(doc) + m["vectors"][k]["outcome"]["reason"] = "invoice_mismatch" if v["outcome"]["reason"] != "invoice_mismatch" else "malformed" + out.append(("%s: its stated reason changed" % v["name"], m)) + break + return out + + +CHECKS = ["signatures", "buyer-signature", "tariff-rules", "invoice-rules", "row-of-no-class", "binding", "outside-tariff", + "previous-given", "previous-overlap", "scope", "conflict", "period", "duplicate", "overlap", "rebilled", "quantity", + "summed", "citations", "mismatch"] + + +def main(corpus_dir): + doc = json.load(open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8")) + fails = check(doc) + files = sorted(os.listdir(corpus_dir)) + if files != ["README.md", "vectors.json"]: + fails.append("the corpus holds other files: %s" % files) + seen = {v["outcome"].get("reason") for v in doc["vectors"]} + listed = reasons_listed() + missing = [r for r in listed + ["truncated", "malformed", "unknown_version", "bad_key", "bad_signature", "record_missing", + "classes_summed"] if r not in seen] + if missing: + fails.append("no vector is refused %s" % missing) + print("%s every reason of §10 refuses a vector (%d listed)" % ("PASS" if not missing else "FAIL", len(listed))) + weak = [] + for name, m in mutations(doc): + if not check(m, quiet=True): + weak.append(name) + print("%s every mutated copy of the corpus fails (%d copies)" % ("PASS" if not weak else "FAIL", len(mutations(doc)))) + fails += ["the mutated copy %r verifies" % w for w in weak] + undetected = [] + for c in CHECKS: + SKIP.add(c) + try: + if not check(doc, quiet=True): + undetected.append(c) + finally: + SKIP.discard(c) + print("%s the corpus catches the verifier with each of its checks dropped (%d checks)" % ("PASS" if not undetected else "FAIL", len(CHECKS))) + fails += ["dropping the check %r goes unnoticed" % c for c in undetected] + for f in fails: + print("FAIL", f) + return 1 if fails else 0 + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("usage: receipt_ledger_verify.py ", file=sys.stderr) + sys.exit(2) + sys.exit(main(sys.argv[1])) diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index 4416f8c46..cf27959ee 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -383,6 +383,13 @@ ai_usage = ["live_room"] # declared bytes, per-class joules from live-metering's WMH1 and integer amount a verifier # recomputes. SHA-256, BLAKE3, Ed25519 and the room's records only; no new dependency. settlement = ["live_metering", "jwp_hash"] +# Receipt ledger (extensions/receipt-ledger): `wai.delivery.ledger` — the tariff (WLT1) a seller +# and a buyer both sign, its scope and its one price per unit across rooms, packages, delivery +# and ads; each source verified by its own extension (live_metering, settlement, video_package, +# live_ads); the invoice (WLI1) whose lines cite their records and whose joules are per scope and +# per class, never summed across either, recomputed by a verifier; its chain; and the ad and +# delivery reports. SHA-256, Ed25519 and the sources' own readers; no new dependency. +receipt_ledger = ["live_metering", "settlement", "live_ads", "video_package"] # Co-performance (extensions/co-performance): `wai.live.coperformance` — the room authority's # co-performance policy (WPP1); intents (WPI1) by digest or as notes, a text line or a camera # move, compared with what played under pinned rules, and the clock's fidelity statement @@ -977,6 +984,11 @@ name = "wai_settlement_vectors" path = "src/bin/wai_settlement_vectors.rs" required-features = ["settlement"] +[[bin]] +name = "wai_receipt_ledger_vectors" +path = "src/bin/wai_receipt_ledger_vectors.rs" +required-features = ["receipt_ledger"] + [[bin]] name = "wai_co_performance_vectors" path = "src/bin/wai_co_performance_vectors.rs" @@ -1777,6 +1789,11 @@ name = "settlement" path = "tests/settlement.rs" required-features = ["settlement"] +[[test]] +name = "receipt_ledger" +path = "tests/receipt_ledger.rs" +required-features = ["receipt_ledger"] + [[test]] name = "co_performance" path = "tests/co_performance.rs" diff --git a/wai/wai-rs/src/bin/wai_receipt_ledger_vectors.rs b/wai/wai-rs/src/bin/wai_receipt_ledger_vectors.rs new file mode 100644 index 000000000..725bff286 --- /dev/null +++ b/wai/wai-rs/src/bin/wai_receipt_ledger_vectors.rs @@ -0,0 +1,67 @@ +//! Conformance corpus for the receipt ledger (`live-conformance/ledger/`, +//! extensions/receipt-ledger.md §11). +//! +//! wai_receipt_ledger_vectors gen write vectors.json, from fixed seeds and the +//! sources of the corpora it reads +//! wai_receipt_ledger_vectors verify vectors.json regenerates byte for byte, and +//! every vector gets the outcome it states +//! +//! The corpus directory is `wai/live-conformance/ledger`; the corpora it reads are found from it. +//! The generator and this verifier are one implementation. The corpus's second implementation is +//! `live-conformance/receipt_ledger_verify.py`, written from the extension's text and sharing no +//! code with this binary. + +use std::path::{Path, PathBuf}; +use wai::receipt_ledger::corpus; + +fn wai_dir(corpus_dir: &Path) -> PathBuf { + corpus_dir.join("..").join("..") +} + +fn main() { + let a: Vec = std::env::args().collect(); + match (a.get(1).map(String::as_str), a.get(2)) { + (Some("gen"), Some(d)) => { + let p = Path::new(d).join("vectors.json"); + let text = match corpus::generate(&wai_dir(Path::new(d))) { + Ok(t) => t, + Err(e) => { + eprintln!("generate: {e}"); + std::process::exit(1); + } + }; + if let Err(e) = std::fs::create_dir_all(d).and_then(|_| std::fs::write(&p, text)) { + eprintln!("write {}: {e}", p.display()); + std::process::exit(1); + } + println!("wrote {}", p.display()); + } + (Some("verify"), Some(d)) => std::process::exit(verify(Path::new(d))), + _ => { + eprintln!("usage: wai_receipt_ledger_vectors gen|verify "); + std::process::exit(2); + } + } +} + +fn verify(dir: &Path) -> i32 { + let p = dir.join("vectors.json"); + let doc = match std::fs::read_to_string(&p) { + Ok(s) => s, + Err(e) => { + eprintln!("read {}: {e}", p.display()); + return 1; + } + }; + let mut fail = 0; + let same = corpus::generate(&wai_dir(dir)).is_ok_and(|g| g == doc); + println!("{} vectors.json regenerates byte for byte from its seeds and sources", if same { "PASS" } else { "FAIL" }); + fail += (!same) as i32; + let fails = corpus::verify(&doc); + for f in &fails { + println!("FAIL {f}"); + } + println!("{} every vector gets the outcome the corpus states", if fails.is_empty() { "PASS" } else { "FAIL" }); + fail += (!fails.is_empty()) as i32; + (fail != 0) as i32 +} diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 38b0b7fc4..72347f1ef 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -589,6 +589,15 @@ pub mod ai_usage; /// cbindgen:ignore #[cfg(feature = "settlement")] pub mod settlement; +// Receipt ledger (extensions/receipt-ledger.md): `wai.delivery.ledger` — a tariff a seller and a +// buyer both sign, sources verified by their own extensions (room-session receipts, settlement +// statements, package receipts, ad receipts with presentation claims), and an invoice whose +// lines cite the records they came from and whose energy is per scope and per class, recomputed +// by a verifier; with the ad and delivery reports. Rust-only, like the live modules: its +// constants must not become macros in include/wai.h. +/// cbindgen:ignore +#[cfg(feature = "receipt_ledger")] +pub mod receipt_ledger; // Co-performance (extensions/co-performance.md): `wai.live.coperformance` — intents compared with // what played under pinned rules and the fidelity statement recomputed; turn schedules, // handovers, the floor, latency budgets and marks; audience inputs to selection windows, the diff --git a/wai/wai-rs/src/receipt_ledger.rs b/wai/wai-rs/src/receipt_ledger.rs new file mode 100644 index 000000000..9fe904e09 --- /dev/null +++ b/wai/wai-rs/src/receipt_ledger.rs @@ -0,0 +1,1704 @@ +//! Receipt ledger — `wai.delivery.ledger` ([extensions/receipt-ledger.md]). +//! +//! Invoice lines a customer can recompute from the signed records WAI already defines. A seller +//! and a buyer both sign a **tariff** (`WLT1`): one currency, a price for each unit the records +//! state (a room session's units, the acknowledged bytes of each object class, a package's +//! packages, entries and stored bytes, an ad decision's counted impressions), and the scope of +//! rooms, parties, packagers and deciders it bills. Each **source** is verified by the +//! extension that defines it ([`crate::live_metering`], [`crate::settlement`], +//! [`crate::package`], [`crate::live_ads`]); the seller signs an **invoice** (`WLI1`) whose lines +//! cite the records they came from and whose energy rows are per scope and per class, never +//! summed across either; and [`verify`] recomputes every figure. Two reports are stated beside +//! an accepted invoice: ad decisions against the players' presentation claims, and delivery by +//! party with the bytes two parties were acknowledged for twice. +//! +//! What this module implements, by section of the extension: +//! +//! - §4: the tariff ([`Tariff`]), its two signatures and its scope; +//! - §5: the sources ([`verify_source`]) and what each contributes ([`Facts`]); +//! - §6: the invoice ([`Invoice`]), its lines, citations, energy and chain ([`compute`]); +//! - §7: verification ([`verify`]); §8: the reports. +//! +//! The corpus `live-conformance/ledger/` is written by [`corpus::generate`] and checked by +//! [`corpus::verify`], and by `live-conformance/receipt_ledger_verify.py`, which shares no code +//! with this module. +//! +//! [extensions/receipt-ledger.md]: ../../extensions/receipt-ledger.md + +use std::collections::{BTreeMap, BTreeSet}; + +use ed25519_dalek::{Signer, SigningKey}; +use serde_json::{json, Map, Value}; +use sha2::{Digest, Sha256}; + +use crate::energy_class::EnergyProvenance; +use crate::live_ads::{self as ads, presentation as pr}; +use crate::live_compose::RoomInput as ComposeRoom; +use crate::live_metering::{self as metering, Place, SessionInputs, SessionOutcome, SessionReceipt}; +use crate::live_room::{self as room, Descriptor}; +use crate::package::PackageReceiptV2; +use crate::settlement::{self, Ack, Item}; +use crate::wai_video_ssai::{AdCandidate, SsaiBreakReceipt}; + +pub mod corpus; + +/// The record this extension registers. +pub const CAPABILITY: &str = "wai.delivery.ledger"; +pub const TARIFF_MAGIC: [u8; 4] = *b"WLT1"; +pub const INVOICE_MAGIC: [u8; 4] = *b"WLI1"; +pub const VERSION: u8 = 1; +pub const TARIFF_DOMAIN: &[u8] = b"wai:ledger-tariff\x01"; +pub const ACCEPT_DOMAIN: &[u8] = b"wai:ledger-accept\x01"; +pub const INVOICE_DOMAIN: &[u8] = b"wai:ledger-invoice\x01"; +pub const CITES_DOMAIN: &[u8] = b"wai:ledger-cites\x01"; +/// A line's length (§6.3). +pub const LINE_LEN: usize = 94; +/// An energy row's length (§6.1). +pub const ROW_LEN: usize = 18; +/// The room prices of a tariff, as live-metering §4.1 orders them. +pub const ROOM_PRICES: usize = 13; + +/// The kinds of a source and of a line (§6.3). +pub mod source_kind { + pub const ROOM: u8 = 1; + pub const DELIVERY: u8 = 2; + pub const PACKAGE: u8 = 3; + pub const ADS: u8 = 4; + + pub fn name(k: u8) -> &'static str { + ["", "room", "delivery", "package", "ads"].get(k as usize).copied().unwrap_or("") + } +} + +/// The kinds of a record (§5.2). +pub mod record_kind { + pub const SESSION: u8 = 1; + pub const HOP: u8 = 2; + pub const STATEMENT: u8 = 3; + pub const ACK: u8 = 4; + pub const PACKAGE: u8 = 5; + pub const AD_RECEIPT: u8 = 6; + pub const CLAIM: u8 = 7; +} + +/// The energy scopes (§6.6). +pub mod scope { + pub const ROOM_HOPS: u8 = 1; + pub const DELIVERY_HOPS: u8 = 2; + pub const PACKAGING: u8 = 3; + pub const AD_DECISIONS: u8 = 4; + + pub fn name(s: u8) -> &'static str { + ["", "room_hops", "delivery_hops", "packaging", "ad_decisions"].get(s as usize).copied().unwrap_or("") + } +} + +/// The unit names of each source kind, in unit order (§6.3). +pub fn unit_name(kind: u8, unit: u8) -> Option<&'static str> { + const ROOM: [&str; 13] = [ + "publisher_us", "subscriber_us_0", "subscriber_us_1", "subscriber_us_2", "subscriber_us_3", "subscriber_us_4", "subscriber_us_5", "subscriber_us_6", "subscriber_us_7", + "subscriber_us_8", "composition_us", "recorded_us", "events_sent", + ]; + let names: &[&str] = match kind { + source_kind::ROOM => &ROOM, + source_kind::DELIVERY => &["live", "fetch", "fill", "ancillary"], + source_kind::PACKAGE => &["packages", "entries", "stored_bytes"], + source_kind::ADS => &["presented_impressions", "billable_impressions"], + _ => &[], + }; + names.get((unit as usize).checked_sub(1)?).copied() +} + +/// A unit's divisor (§6.3). +pub fn divisor(kind: u8, unit: u8) -> u128 { + match (kind, unit) { + (source_kind::ROOM, 13) => 1, + (source_kind::ROOM, _) => 1_000_000, + (source_kind::DELIVERY, _) => 1_000_000_000, + (source_kind::PACKAGE, 3) => 1_000_000_000, + _ => 1, + } +} + +// --------------------------------------------------------------------------------------- +// Reasons +// --------------------------------------------------------------------------------------- + +/// The codes this extension adds (§10). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Reason { + TariffInvalid, + InvoiceInvalid, + TariffMismatch, + OutsideTariff, + InvoiceOverlap, + SourceGap, + OutsideScope, + TariffConflict, + OutsidePeriod, + RecordDuplicate, + RecordOverlap, + RecordRebilled, + QuantityNotRecorded, + CitationMismatch, + InvoiceMismatch, +} + +impl Reason { + /// §10's order. + pub const ALL: [Reason; 15] = [ + Reason::TariffInvalid, + Reason::InvoiceInvalid, + Reason::TariffMismatch, + Reason::OutsideTariff, + Reason::InvoiceOverlap, + Reason::SourceGap, + Reason::OutsideScope, + Reason::TariffConflict, + Reason::OutsidePeriod, + Reason::RecordDuplicate, + Reason::RecordOverlap, + Reason::RecordRebilled, + Reason::QuantityNotRecorded, + Reason::CitationMismatch, + Reason::InvoiceMismatch, + ]; + + /// The codes of other extensions this one raises itself, besides those of its sources. + pub const REUSED: [&'static str; 7] = ["truncated", "malformed", "unknown_version", "bad_key", "bad_signature", "record_missing", "classes_summed"]; + + pub fn code(self) -> &'static str { + match self { + Reason::TariffInvalid => "tariff_invalid", + Reason::InvoiceInvalid => "invoice_invalid", + Reason::TariffMismatch => "tariff_mismatch", + Reason::OutsideTariff => "outside_tariff", + Reason::InvoiceOverlap => "invoice_overlap", + Reason::SourceGap => "source_gap", + Reason::OutsideScope => "outside_scope", + Reason::TariffConflict => "tariff_conflict", + Reason::OutsidePeriod => "outside_period", + Reason::RecordDuplicate => "record_duplicate", + Reason::RecordOverlap => "record_overlap", + Reason::RecordRebilled => "record_rebilled", + Reason::QuantityNotRecorded => "quantity_not_recorded", + Reason::CitationMismatch => "citation_mismatch", + Reason::InvoiceMismatch => "invoice_mismatch", + } + } +} + +impl std::fmt::Display for Reason { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.code()) + } +} + +/// A refusal: the reason's code, and where, as a JSON array (§7.2). +pub type Refusal = (&'static str, Value); + +const TRUNCATED: &str = "truncated"; +const MALFORMED: &str = "malformed"; +const UNKNOWN_VERSION: &str = "unknown_version"; +const RECORD_MISSING: &str = "record_missing"; +const CLASSES_SUMMED: &str = "classes_summed"; + +// --------------------------------------------------------------------------------------- +// Bytes +// --------------------------------------------------------------------------------------- + +fn sha256(parts: &[&[u8]]) -> [u8; 32] { + let mut h = Sha256::new(); + for p in parts { + h.update(p); + } + h.finalize().into() +} + +pub(crate) fn hex(b: &[u8]) -> String { + b.iter().map(|x| format!("{x:02x}")).collect() +} + +pub(crate) fn unhex(s: &str) -> Option> { + if s.len() % 2 != 0 || !s.bytes().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) { + return None; + } + (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok()).collect() +} + +fn capitals(c: &[u8; 3]) -> bool { + c.iter().all(|x| x.is_ascii_uppercase()) +} + +fn sign(key: &SigningKey, domain: &[u8], body: &[u8]) -> [u8; 64] { + let mut m = domain.to_vec(); + m.extend_from_slice(body); + key.sign(&m).to_bytes() +} + +fn check_sig(key: &[u8; 32], domain: &[u8], body: &[u8], sig: &[u8; 64]) -> Result<(), &'static str> { + let mut m = domain.to_vec(); + m.extend_from_slice(body); + crate::sig::verify_strict(key, &m, sig).map_err(|e| match e { + crate::sig::SigError::BadKey => "bad_key", + crate::sig::SigError::BadSignature => "bad_signature", + }) +} + +/// A cursor; running out is `truncated`. +struct Rd<'a> { + b: &'a [u8], + p: usize, +} + +impl<'a> Rd<'a> { + fn take(&mut self, n: usize) -> Result<&'a [u8], &'static str> { + let e = self.p.checked_add(n).filter(|&e| e <= self.b.len()).ok_or(TRUNCATED)?; + let s = &self.b[self.p..e]; + self.p = e; + Ok(s) + } + fn arr(&mut self) -> Result<[u8; N], &'static str> { + Ok(self.take(N)?.try_into().expect("N bytes")) + } + fn u8(&mut self) -> Result { + Ok(self.take(1)?[0]) + } + fn u16(&mut self) -> Result { + Ok(u16::from_le_bytes(self.arr()?)) + } + fn u32(&mut self) -> Result { + Ok(u32::from_le_bytes(self.arr()?)) + } + fn u64(&mut self) -> Result { + Ok(u64::from_le_bytes(self.arr()?)) + } + fn done(&self) -> bool { + self.p == self.b.len() + } +} + +/// The first steps of reading (§4.3, §6.8): the magic and the version. +fn read_head(b: &[u8], magic: &[u8; 4]) -> Result<(), &'static str> { + if b.len() < 4 { + return Err(TRUNCATED); + } + if &b[..4] != magic { + return Err(MALFORMED); + } + match b.get(4) { + None => Err(TRUNCATED), + Some(&VERSION) => Ok(()), + Some(_) => Err(UNKNOWN_VERSION), + } +} + +fn ascending(v: &[T]) -> bool { + v.windows(2).all(|w| w[0] < w[1]) +} + +// --------------------------------------------------------------------------------------- +// §4 The tariff +// --------------------------------------------------------------------------------------- + +/// A tariff (§4.1). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Tariff { + pub seller: [u8; 32], + pub buyer: [u8; 32], + pub start: u64, + pub end: u64, + pub currency: [u8; 3], + /// Live-metering §4.1's thirteen prices, in its order. + pub room: [u32; ROOM_PRICES], + /// Settlement §7.1's four prices, in class order. + pub delivery: [u64; 4], + /// Per package receipt, per entry, per 10^9 stored bytes. + pub package: [u64; 3], + /// Per presented impression, per billable impression. + pub ads: [u64; 2], + /// (kind, id), strictly ascending. + pub scope: Vec<(u8, [u8; 32])>, + pub seller_sig: [u8; 64], + pub buyer_sig: [u8; 64], +} + +impl Tariff { + /// The tariff without its signatures: the body (§4.2). + pub fn body(&self) -> Vec { + let mut o = Vec::with_capacity(256 + 33 * self.scope.len()); + o.extend_from_slice(&TARIFF_MAGIC); + o.push(VERSION); + o.extend_from_slice(&self.seller); + o.extend_from_slice(&self.buyer); + o.extend_from_slice(&self.start.to_le_bytes()); + o.extend_from_slice(&self.end.to_le_bytes()); + o.extend_from_slice(&self.currency); + for p in self.room { + o.extend_from_slice(&p.to_le_bytes()); + } + for p in self.delivery.iter().chain(&self.package).chain(&self.ads) { + o.extend_from_slice(&p.to_le_bytes()); + } + o.extend_from_slice(&u16::try_from(self.scope.len()).expect("at most 65535 scope entries").to_le_bytes()); + for (k, id) in &self.scope { + o.push(*k); + o.extend_from_slice(id); + } + o + } + + pub fn to_bytes(&self) -> Vec { + let mut o = self.body(); + o.extend_from_slice(&self.seller_sig); + o.extend_from_slice(&self.buyer_sig); + o + } + + /// Its digest (§4.2). + pub fn digest(&self) -> [u8; 32] { + sha256(&[&self.body()]) + } + + /// Signed by the seller and accepted by the buyer. + pub fn sign(mut self, seller: &SigningKey, buyer: &SigningKey) -> Tariff { + let body = self.body(); + self.seller_sig = sign(seller, TARIFF_DOMAIN, &body); + self.buyer_sig = sign(buyer, ACCEPT_DOMAIN, &body); + self + } + + /// Read a tariff (§4.3): its rules, then the seller's signature, then the buyer's. A + /// refusal names `tariff`, or `tariff` and the key whose signature failed. + pub fn parse(b: &[u8]) -> Result { + let at = || json!(["tariff"]); + read_head(b, &TARIFF_MAGIC).map_err(|r| (r, at()))?; + let mut r = Rd { b, p: 5 }; + let read = |r: &mut Rd| -> Result { + let (seller, buyer) = (r.arr()?, r.arr()?); + let (start, end) = (r.u64()?, r.u64()?); + let currency = r.arr()?; + let mut room = [0u32; ROOM_PRICES]; + for p in &mut room { + *p = r.u32()?; + } + let mut delivery = [0u64; 4]; + for p in &mut delivery { + *p = r.u64()?; + } + let mut package = [0u64; 3]; + for p in &mut package { + *p = r.u64()?; + } + let ads = [r.u64()?, r.u64()?]; + let n = r.u16()? as usize; + let mut scope = Vec::with_capacity(n.min(b.len() / 33)); + for _ in 0..n { + scope.push((r.u8()?, r.arr()?)); + } + let (seller_sig, buyer_sig) = (r.arr()?, r.arr()?); + if !r.done() { + return Err(MALFORMED); + } + Ok(Tariff { seller, buyer, start, end, currency, room, delivery, package, ads, scope, seller_sig, buyer_sig }) + }; + let t = read(&mut r).map_err(|e| (e, at()))?; + let rules = t.start < t.end + && capitals(&t.currency) + && t.seller != [0; 32] + && t.buyer != [0; 32] + && t.seller != t.buyer + && !t.scope.is_empty() + && ascending(&t.scope) + && t.scope.iter().all(|(k, id)| (1..=4).contains(k) && *id != [0; 32]); + if !rules { + return Err((Reason::TariffInvalid.code(), at())); + } + let body = t.body(); + check_sig(&t.seller, TARIFF_DOMAIN, &body, &t.seller_sig).map_err(|e| (e, json!(["tariff", "seller"])))?; + check_sig(&t.buyer, ACCEPT_DOMAIN, &body, &t.buyer_sig).map_err(|e| (e, json!(["tariff", "buyer"])))?; + Ok(t) + } + + /// Whether a scope entry names `id` of `kind` (§4.4). + pub fn in_scope(&self, kind: u8, id: &[u8; 32]) -> bool { + self.scope.binary_search(&(kind, *id)).is_ok() + } + + /// The price of a unit (§6.3). + pub fn price(&self, kind: u8, unit: u8) -> u64 { + let i = unit as usize - 1; + match kind { + source_kind::ROOM => self.room[i] as u64, + source_kind::DELIVERY => self.delivery[i], + source_kind::PACKAGE => self.package[i], + _ => self.ads[i], + } + } +} + +// --------------------------------------------------------------------------------------- +// §6 The invoice +// --------------------------------------------------------------------------------------- + +/// A line (§6.3). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Line { + pub source_kind: u8, + pub source: [u8; 32], + pub unit: u8, + pub quantity: u64, + pub price: u64, + pub amount: u64, + pub cited: u32, + pub cites: [u8; 32], +} + +impl Line { + fn key(&self) -> (u8, [u8; 32], u8) { + (self.source_kind, self.source, self.unit) + } +} + +/// An energy row (§6.6). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Row { + pub scope: u8, + pub tag: u8, + pub figures: u32, + pub joules: u64, + pub max_ppm: u32, +} + +/// An invoice (§6.1). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Invoice { + pub seller: [u8; 32], + pub buyer: [u8; 32], + pub tariff: [u8; 32], + pub prev: [u8; 32], + pub start: u64, + pub end: u64, + pub currency: [u8; 3], + pub records: Vec<(u8, [u8; 32])>, + pub lines: Vec, + pub energy: Vec, + pub unmetered: [u32; 4], + pub amount: u64, + pub sig: [u8; 64], +} + +impl Invoice { + /// The invoice without `sig`. + pub fn body(&self) -> Vec { + let mut o = Vec::with_capacity(200 + 33 * self.records.len() + LINE_LEN * self.lines.len()); + o.extend_from_slice(&INVOICE_MAGIC); + o.push(VERSION); + for k in [&self.seller, &self.buyer, &self.tariff, &self.prev] { + o.extend_from_slice(k); + } + o.extend_from_slice(&self.start.to_le_bytes()); + o.extend_from_slice(&self.end.to_le_bytes()); + o.extend_from_slice(&self.currency); + o.extend_from_slice(&u32::try_from(self.records.len()).expect("at most 2^32 - 1 records").to_le_bytes()); + for (k, id) in &self.records { + o.push(*k); + o.extend_from_slice(id); + } + o.extend_from_slice(&u16::try_from(self.lines.len()).expect("at most 65535 lines").to_le_bytes()); + for l in &self.lines { + o.push(l.source_kind); + o.extend_from_slice(&l.source); + o.push(l.unit); + o.extend_from_slice(&l.quantity.to_le_bytes()); + o.extend_from_slice(&l.price.to_le_bytes()); + o.extend_from_slice(&l.amount.to_le_bytes()); + o.extend_from_slice(&l.cited.to_le_bytes()); + o.extend_from_slice(&l.cites); + } + o.push(u8::try_from(self.energy.len()).expect("at most 255 rows")); + for e in &self.energy { + o.push(e.scope); + o.push(e.tag); + o.extend_from_slice(&e.figures.to_le_bytes()); + o.extend_from_slice(&e.joules.to_le_bytes()); + o.extend_from_slice(&e.max_ppm.to_le_bytes()); + } + for u in self.unmetered { + o.extend_from_slice(&u.to_le_bytes()); + } + o.extend_from_slice(&self.amount.to_le_bytes()); + o + } + + pub fn to_bytes(&self) -> Vec { + let mut o = self.body(); + o.extend_from_slice(&self.sig); + o + } + + /// Its digest (§6.7). + pub fn digest(&self) -> [u8; 32] { + sha256(&[&self.body()]) + } + + /// Signed under `key`; `seller` is left as it is. + pub fn sign(mut self, key: &SigningKey) -> Invoice { + self.sig = sign(key, INVOICE_DOMAIN, &self.body()); + self + } + + /// Read an invoice (§6.8): its rules, then its signature. + pub fn parse(b: &[u8]) -> Result { + read_head(b, &INVOICE_MAGIC)?; + let mut r = Rd { b, p: 5 }; + let (seller, buyer, tariff, prev) = (r.arr()?, r.arr()?, r.arr()?, r.arr()?); + let (start, end) = (r.u64()?, r.u64()?); + let currency = r.arr()?; + let n = r.u32()? as usize; + let mut records = Vec::with_capacity(n.min(b.len() / 33)); + for _ in 0..n { + records.push((r.u8()?, r.arr()?)); + } + let n = r.u16()? as usize; + let mut lines = Vec::with_capacity(n.min(b.len() / LINE_LEN)); + for _ in 0..n { + lines.push(Line { + source_kind: r.u8()?, + source: r.arr()?, + unit: r.u8()?, + quantity: r.u64()?, + price: r.u64()?, + amount: r.u64()?, + cited: r.u32()?, + cites: r.arr()?, + }); + } + let n = r.u8()? as usize; + let mut energy = Vec::with_capacity(n); + for _ in 0..n { + energy.push(Row { scope: r.u8()?, tag: r.u8()?, figures: r.u32()?, joules: r.u64()?, max_ppm: r.u32()? }); + } + let unmetered = [r.u32()?, r.u32()?, r.u32()?, r.u32()?]; + let amount = r.u64()?; + let sig = r.arr()?; + if !r.done() { + return Err(MALFORMED); + } + let i = Invoice { seller, buyer, tariff, prev, start, end, currency, records, lines, energy, unmetered, amount, sig }; + if i.energy.iter().any(|e| e.tag == 0) { + return Err(CLASSES_SUMMED); + } + let keys: Vec<_> = i.lines.iter().map(Line::key).collect(); + let rows: Vec<_> = i.energy.iter().map(|e| (e.scope, e.tag)).collect(); + let rules = i.start < i.end + && capitals(&i.currency) + && ascending(&i.records) + && i.records.iter().all(|(k, _)| (1..=7).contains(k)) + && ascending(&keys) + && i.lines.iter().all(|l| unit_name(l.source_kind, l.unit).is_some() && l.quantity > 0 && l.cited > 0) + && ascending(&rows) + && i.energy.iter().all(|e| (1..=4).contains(&e.scope) && (2..=5).contains(&e.tag) && e.figures > 0); + if !rules { + return Err(Reason::InvoiceInvalid.code()); + } + check_sig(&i.seller, INVOICE_DOMAIN, &i.body(), &i.sig)?; + Ok(i) + } +} + +/// A line's citation digest (§6.5) over its cited records, in ascending order. +pub fn cites_digest(cited: &BTreeSet<(u8, [u8; 32])>) -> [u8; 32] { + let mut h = Sha256::new(); + h.update(CITES_DOMAIN); + for (k, id) in cited { + h.update([*k]); + h.update(id); + } + h.finalize().into() +} + +// --------------------------------------------------------------------------------------- +// §5 Sources +// --------------------------------------------------------------------------------------- + +/// An energy figure as a record states it. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Figure { + pub joules: u64, + pub class: Option, +} + +impl Figure { + /// Its tag, or `None` when it is the unmetered marker. + pub fn tag(&self) -> Option { + self.class.as_ref().filter(|_| self.joules > 0).map(EnergyProvenance::tag_byte) + } + + /// Its declared relative uncertainty, 0 for a class that declares none (§6.6). + pub fn ppm(&self) -> u32 { + self.class.as_ref().and_then(EnergyProvenance::uncertainty).map_or(0, |u| u.relative_ppm) + } +} + +/// A verified room source (§5.1). +#[derive(Clone, Debug)] +pub struct RoomFacts { + pub digest: [u8; 32], + pub room: [u8; 32], + pub t0: u64, + pub start: u64, + pub end: u64, + pub units: metering::Units, + pub tariff: metering::Tariff, + /// Each hop entry: its record's digest and figure. + pub hops: Vec<([u8; 32], Figure)>, +} + +impl RoomFacts { + /// The receipt's figure of a room unit (§6.3). + pub fn unit(&self, unit: u8) -> u64 { + let u = &self.units; + match unit { + 1 => u.publisher_us, + 2..=10 => u.subscriber_us[unit as usize - 2], + 11 => u.composition_us, + 12 => u.recorded_us, + _ => u.events_sent, + } + } +} + +/// A verified delivery source (§5.1). +#[derive(Clone, Debug)] +pub struct DeliveryFacts { + pub digest: [u8; 32], + pub statement: settlement::Statement, + /// Each record it names, by digest, with its item lists. + pub acks: Vec<([u8; 32], Ack, Vec>)>, + /// Each per-hop record it names, by digest, with its figure. + pub hops: Vec<([u8; 32], Figure)>, +} + +/// A verified package source (§5.1). +#[derive(Clone, Debug)] +pub struct PackageFacts { + pub receipt_hash: [u8; 32], + pub signer: [u8; 32], + pub total_entries: u64, + pub stored_bytes: u64, + pub figure: Figure, +} + +/// One impression of an ad receipt, as the report states it (§8.1). +#[derive(Clone, Debug)] +pub struct ImpressionFacts { + pub slot: u64, + pub ad_id: String, + pub price_micros: u64, + pub quartiles_reached: u8, + pub billable: bool, + pub counts: bool, + /// `presented`, a claim's reason, or `beacons`. + pub verdict: &'static str, + /// The digest of its own claim, when it counts under claims. + pub claim: Option<[u8; 32]>, +} + +/// One ad receipt of an ads source. +#[derive(Clone, Debug)] +pub struct AdReceiptFacts { + pub identity: [u8; 32], + pub decider: [u8; 32], + pub cue: [u8; 32], + pub session: [u8; 16], + /// The room-clock `t` of the break it names. + pub t: u64, + pub spend_micros: u64, + pub counted_spend_micros: u64, + pub impressions: Vec, + pub figure: Figure, +} + +/// A verified ads source (§5.1). +#[derive(Clone, Debug)] +pub struct AdsFacts { + pub room: [u8; 32], + pub t0: u64, + pub claims_required: bool, + pub receipts: Vec, + /// The digests of the claims that read. + pub claims: Vec<[u8; 32]>, +} + +/// What a verified source gives (§5.2). +#[derive(Clone, Debug)] +pub enum Facts { + Room(RoomFacts), + Delivery(DeliveryFacts), + Package(PackageFacts), + Ads(AdsFacts), +} + +impl Facts { + /// Its heads (§5.2). + pub fn heads(&self) -> Vec<(u8, [u8; 32])> { + match self { + Facts::Room(r) => vec![(record_kind::SESSION, r.digest)], + Facts::Delivery(d) => vec![(record_kind::STATEMENT, d.digest)], + Facts::Package(p) => vec![(record_kind::PACKAGE, p.receipt_hash)], + Facts::Ads(a) => a.receipts.iter().map(|r| (record_kind::AD_RECEIPT, r.identity)).collect(), + } + } + + /// Every record it contributes (§5.2). + pub fn records(&self) -> BTreeSet<(u8, [u8; 32])> { + let mut s: BTreeSet<(u8, [u8; 32])> = self.heads().into_iter().collect(); + match self { + Facts::Room(r) => s.extend(r.hops.iter().map(|(d, _)| (record_kind::HOP, *d))), + Facts::Delivery(d) => { + s.extend(d.statement.records.iter().map(|x| (record_kind::ACK, *x))); + s.extend(d.statement.hops.iter().map(|x| (record_kind::HOP, *x))); + } + Facts::Package(_) => {} + Facts::Ads(a) => s.extend(a.claims.iter().map(|x| (record_kind::CLAIM, *x))), + } + s + } +} + +fn hexes(v: &Value) -> Option>> { + v.as_array()?.iter().map(|x| unhex(x.as_str()?)).collect() +} + +fn bytes_of(v: &Value) -> Option> { + unhex(v.as_str()?) +} + +fn opt_str(v: &Value) -> Option> { + match v { + Value::Null => Some(None), + Value::String(s) => Some(Some(s.clone())), + _ => None, + } +} + +fn has_exactly(o: &Map, keys: &[&str]) -> bool { + o.len() == keys.len() && keys.iter().all(|k| o.contains_key(*k)) +} + +fn bad_source() -> Refusal { + (MALFORMED, json!([])) +} + +/// The place of a live-metering refusal (§5.1), as its corpus writes it. +fn metering_place(at: &Place) -> Value { + use room::At; + match at { + Place::Room(None) => json!(["room"]), + Place::Room(Some(At::Descriptor)) => json!(["room", "descriptor"]), + Place::Room(Some(At::KeyLog)) => json!(["room", "key_log"]), + Place::Room(Some(At::Grant(i))) => json!(["room", "grant", i]), + Place::Room(Some(At::Record { chain, record })) => json!(["room", "record", chain, record]), + Place::Receipt => json!(["receipt"]), + Place::Hop(i) => json!(["hop", i]), + Place::Take(i) => json!(["take", i]), + Place::Compose(None) => json!(["compose"]), + Place::Compose(Some(i)) => json!(["compose", i]), + Place::Qoe => json!(["qoe"]), + Place::Units => json!(["units"]), + Place::Energy => json!(["energy"]), + Place::Ceilings => json!(["ceilings"]), + } +} + +/// The place of a live-ads refusal (§5.1), as its corpus writes it. +fn ads_place(at: &ads::At) -> Value { + use room::At as R; + match at { + ads::At::Room(R::Descriptor) => json!(["room", "descriptor"]), + ads::At::Room(R::KeyLog) => json!(["room", "key_log"]), + ads::At::Room(R::Grant(i)) => json!(["room", "grant", i]), + ads::At::Room(R::Record { chain, record }) => json!(["room", "record", chain, record]), + ads::At::Policy => json!(["policy"]), + ads::At::Record { chain, record } => json!(["record", chain, record]), + ads::At::Receipt(k) => json!(["receipt", k]), + } +} + +fn room_source(o: &Map) -> Result { + if !has_exactly(o, &["kind", "room", "receipt", "hops", "descriptors", "compositions", "takes", "logs"]) { + return Err(bad_source()); + } + let read = || -> Option<_> { + let rm = o["room"].as_object()?; + if !has_exactly(rm, &["descriptor", "grants", "key_log_head", "chains"]) { + return None; + } + let descriptor = bytes_of(&rm["descriptor"])?; + let grants = hexes(&rm["grants"])?; + let head = opt_str(&rm["key_log_head"])?; + let chains: Vec>> = rm["chains"].as_array()?.iter().map(hexes).collect::>()?; + let receipt = bytes_of(&o["receipt"])?; + let hops: Vec<(Vec, Vec)> = o["hops"] + .as_array()? + .iter() + .map(|h| { + let h = h.as_object()?; + if !has_exactly(h, &["record", "ledger"]) { + return None; + } + Some((bytes_of(&h["record"])?, bytes_of(&h["ledger"])?)) + }) + .collect::>()?; + let logs = opt_str(&o["logs"])?; + Some((descriptor, grants, head, chains, receipt, hops, hexes(&o["descriptors"])?, hexes(&o["compositions"])?, hexes(&o["takes"])?, logs)) + }; + let (descriptor, grants, head, chains, receipt, hops, descriptors, compositions, takes, logs) = read().ok_or_else(bad_source)?; + let input = SessionInputs { + room: ComposeRoom { descriptor: &descriptor, grants: &grants, head: head.as_deref(), chains: &chains }, + receipt: &receipt, + hops: &hops, + descriptors: &descriptors, + compositions: &compositions, + takes: &takes, + logs: logs.as_deref().map(str::as_bytes), + }; + let digest = match metering::verify_session(&input) { + SessionOutcome::Accept { digest, .. } => digest, + SessionOutcome::Refuse { at, reason } => return Err((reason.code(), metering_place(&at))), + }; + // The receipt verified, so its room's descriptor and the receipt read. + let d = Descriptor::parse(&descriptor).expect("a verified room's descriptor reads"); + let room_id = d.room_id(); + let rc = SessionReceipt::parse(&receipt, &room_id).expect("a verified receipt reads"); + Ok(Facts::Room(RoomFacts { + digest, + room: room_id, + t0: d.t0_us, + start: rc.start, + end: rc.end, + units: rc.units, + tariff: rc.tariff, + hops: rc.hops.iter().map(|h| (h.record, Figure { joules: h.joules, class: h.class.clone() })).collect(), + })) +} + +fn delivery_source(o: &Map) -> Result { + if !has_exactly(o, &["kind", "statement", "records", "proofs", "hops", "rooms"]) { + return Err(bad_source()); + } + let read = || -> Option<(Vec, settlement::Inputs)> { + let mut i = settlement::Inputs::default(); + for r in o["records"].as_array()? { + let r = r.as_object()?; + if !has_exactly(r, &["record", "items"]) { + return None; + } + let b = bytes_of(&r["record"])?; + let d = settlement::given_digest(&b); + i.items.insert(d, hexes(&r["items"])?); + i.records.insert(d, b); + } + for p in hexes(&o["proofs"])? { + i.proofs.insert(settlement::proof_digest(&p), p); + } + for h in hexes(&o["hops"])? { + i.hops.insert(settlement::given_digest(&h), h); + } + for r in o["rooms"].as_array()? { + let r = r.as_object()?; + if !has_exactly(r, &["descriptor", "grants"]) { + return None; + } + i.rooms.push(settlement::RoomInput { descriptor: bytes_of(&r["descriptor"])?, grants: hexes(&r["grants"])? }); + } + Some((bytes_of(&o["statement"])?, i)) + }; + let (statement, inputs) = read().ok_or_else(bad_source)?; + let accepted = settlement::verify(&statement, &inputs).map_err(|(r, at)| (r.code(), at))?; + let s = accepted.statement; + // Every record and hop record it names was given and verified. + let acks = s + .records + .iter() + .map(|d| { + let a = Ack::parse(&inputs.records[d]).expect("a verified acknowledgement reads"); + let items = inputs.items[d].iter().map(|l| settlement::read_items(l).expect("a verified item list reads")).collect(); + (*d, a, items) + }) + .collect(); + let hops = s + .hops + .iter() + .map(|d| { + let b = &inputs.hops[d]; + let room_id: [u8; 32] = b[5..37].try_into().expect("a verified hop record names its room"); + let h = metering::HopRecord::parse(b, &room_id).expect("a verified hop record reads"); + (*d, Figure { joules: h.joules, class: h.class }) + }) + .collect(); + Ok(Facts::Delivery(DeliveryFacts { digest: accepted.digest, statement: s, acks, hops })) +} + +fn package_source(o: &Map) -> Result { + if !has_exactly(o, &["kind", "receipt", "indexes"]) { + return Err(bad_source()); + } + let (Some(text), Some(indexes)) = (o["receipt"].as_str(), hexes(&o["indexes"])) else { + return Err(bad_source()); + }; + let r = PackageReceiptV2::from_json(text).map_err(|e| (e.code, json!(["receipt"])))?; + let ix: Vec<&[u8]> = indexes.iter().map(Vec::as_slice).collect(); + r.verify_detailed(&ix).map_err(|e| (e.code, json!(["receipt"])))?; + Ok(Facts::Package(PackageFacts { + receipt_hash: r.receipt_hash(), + signer: r.signer_pubkey, + total_entries: r.total_entries, + stored_bytes: r.stored_bytes, + figure: Figure { joules: r.joules_micro, class: r.energy_provenance.clone() }, + })) +} + +fn creative_of(v: &Value) -> Option { + let o = v.as_object()?; + if !has_exactly(o, &["objects"]) { + return None; + } + let objects = o["objects"] + .as_array()? + .iter() + .map(|x| { + let x = x.as_object()?; + if !has_exactly(x, &["content_hash", "duration_ms"]) { + return None; + } + Some(pr::CreativeObject { content_hash: bytes_of(&x["content_hash"])?.try_into().ok()?, duration_ms: u32::try_from(x["duration_ms"].as_u64()?).ok()? }) + }) + .collect::>()?; + Some(pr::Creative { objects }) +} + +fn issuer_of(v: &Value) -> Option { + let o = v.as_object()?; + if !has_exactly(o, &["name", "token_type", "keys"]) { + return None; + } + Some(pr::Issuer { name: o["name"].as_str()?.as_bytes().to_vec(), token_type: u16::try_from(o["token_type"].as_u64()?).ok()?, keys: hexes(&o["keys"])? }) +} + +fn pod_of(v: &Value) -> Option> { + v.as_array()? + .iter() + .map(|c| { + let c = c.as_object()?; + if !has_exactly(c, &["ad_id", "creative_hash", "duration_ms", "price_micros"]) { + return None; + } + Some(AdCandidate { + ad_id: c["ad_id"].as_str()?.into(), + creative_hash: bytes_of(&c["creative_hash"])?.try_into().ok()?, + duration_ms: c["duration_ms"].as_u64()?, + price_micros: c["price_micros"].as_u64()?, + }) + }) + .collect() +} + +fn ads_source(o: &Map) -> Result { + if !has_exactly(o, &["kind", "descriptor", "grants", "key_log", "chains", "policy", "pt0_ms", "receipts", "pods", "claims", "creatives", "issuers"]) { + return Err(bad_source()); + } + let read = || -> Option<_> { + let chains: Vec>> = o["chains"].as_array()?.iter().map(hexes).collect::>()?; + let pods: Vec> = o["pods"].as_array()?.iter().map(pod_of).collect::>()?; + let creatives: Vec = o["creatives"].as_array()?.iter().map(creative_of).collect::>()?; + let issuers: Vec = o["issuers"].as_array()?.iter().map(issuer_of).collect::>()?; + Some(( + bytes_of(&o["descriptor"])?, + hexes(&o["grants"])?, + opt_str(&o["key_log"])?, + chains, + bytes_of(&o["policy"])?, + o["pt0_ms"].as_u64()?, + o["receipts"].as_array()?.clone(), + pods, + hexes(&o["claims"])?, + creatives, + issuers, + )) + }; + let (descriptor, grants, head, chains, policy, pt0_ms, receipts, pods, claims, creatives, issuers) = read().ok_or_else(bad_source)?; + let input = ads::Input { + descriptor: &descriptor, + grants: &grants, + head: head.as_deref(), + chains: &chains, + policy: &policy, + pt0_ms, + receipts: &receipts, + pods: &pods, + claims: &claims, + creatives: &creatives, + issuers: &issuers, + }; + let run = ads::verify(&input); + match run.outcome() { + ads::Outcome::Accept => {} + ads::Outcome::Gap(_) => return Err((Reason::SourceGap.code(), json!([]))), + ads::Outcome::Refuse { at, reason } => return Err((reason.code(), ads_place(at))), + } + let claims_required = run.policy.as_ref().and_then(|p| p.presentation.as_ref()).is_some_and(pr::Requirement::claims); + let claim_digests: Vec> = run.claims.iter().map(|c| c.digest).collect(); + let mut out = Vec::with_capacity(receipts.len()); + for (j, facts) in receipts.iter().zip(&run.receipts) { + let r = SsaiBreakReceipt::from_json(j).expect("a verified receipt reads"); + let b = run.schedule.breaks.iter().find(|b| b.digest == r.binding.cue).expect("a verified receipt names a break"); + let impressions = r + .impressions + .iter() + .enumerate() + .map(|(i, imp)| { + let billable = imp.is_billable(); + let (counts, verdict, claim) = if claims_required { + let c = &facts.impressions[i]; + match c.verdict { + Ok(()) => (true, "presented", c.claim.and_then(|x| claim_digests[x])), + Err(e) => (false, e.code(), None), + } + } else { + (billable, "beacons", None) + }; + ImpressionFacts { slot: imp.slot, ad_id: imp.ad_id.clone(), price_micros: imp.price_micros, quartiles_reached: imp.quartiles_reached, billable, counts, verdict, claim } + }) + .collect(); + out.push(AdReceiptFacts { + identity: facts.identity, + decider: r.signer_pubkey, + cue: r.binding.cue, + session: r.binding.session, + t: b.t, + spend_micros: r.budget_spent_micros, + counted_spend_micros: facts.presented_spend_micros.unwrap_or(r.budget_spent_micros), + impressions, + figure: Figure { joules: r.joules_micro, class: r.energy_provenance.clone() }, + }); + } + Ok(Facts::Ads(AdsFacts { room: run.room.expect("an accepted run names its room"), t0: run.t0_us, claims_required, receipts: out, claims: claim_digests.into_iter().flatten().collect() })) +} + +/// §5.1: verify one source by its extension. A refusal's place is the extension's own, +/// relative to the source. +pub fn verify_source(v: &Value) -> Result { + let o = v.as_object().ok_or_else(bad_source)?; + match o.get("kind").and_then(Value::as_str) { + Some("room") => room_source(o), + Some("delivery") => delivery_source(o), + Some("package") => package_source(o), + Some("ads") => ads_source(o), + _ => Err(bad_source()), + } +} + +// --------------------------------------------------------------------------------------- +// §7.2 steps 5 and 6: scope, tariff, period, and across sources +// --------------------------------------------------------------------------------------- + +/// §7.2 step 5 after the source's own verification: scope, one tariff, period. +pub fn check_source(t: &Tariff, start: u64, end: u64, f: &Facts) -> Result<(), Reason> { + let within = |a: u128, b: u128| start as u128 <= a && b <= end as u128; + match f { + Facts::Room(r) => { + if !t.in_scope(source_kind::ROOM, &r.room) { + return Err(Reason::OutsideScope); + } + let rt = &r.tariff; + if rt.currency != [0; 3] { + let prices: Vec = std::iter::once(rt.publisher).chain(rt.subscriber).chain([rt.composition, rt.recorded, rt.event]).collect(); + if rt.currency != t.currency || prices != t.room { + return Err(Reason::TariffConflict); + } + } + if !within(r.t0 as u128 + r.start as u128, r.t0 as u128 + r.end as u128) { + return Err(Reason::OutsidePeriod); + } + } + Facts::Delivery(d) => { + let s = &d.statement; + if !t.in_scope(source_kind::DELIVERY, &s.party) { + return Err(Reason::OutsideScope); + } + if s.tariff.currency != [0; 3] && (s.tariff.currency != t.currency || s.tariff.prices != t.delivery) { + return Err(Reason::TariffConflict); + } + if !within(s.start as u128, s.end as u128) { + return Err(Reason::OutsidePeriod); + } + } + Facts::Package(p) => { + if !t.in_scope(source_kind::PACKAGE, &p.signer) { + return Err(Reason::OutsideScope); + } + } + Facts::Ads(a) => { + if !a.receipts.iter().all(|r| t.in_scope(source_kind::ADS, &r.decider)) { + return Err(Reason::OutsideScope); + } + if !a.receipts.iter().all(|r| { + let x = a.t0 as u128 + r.t as u128; + start as u128 <= x && x < end as u128 + }) { + return Err(Reason::OutsidePeriod); + } + } + } + Ok(()) +} + +fn overlaps(a: (u64, u64), b: (u64, u64)) -> bool { + a.0 < b.1 && b.0 < a.1 +} + +/// Whether source `f` bills again what `g`, an earlier source, billed (§5.3). +fn overlap(f: &Facts, g: &Facts) -> bool { + match (f, g) { + (Facts::Room(a), Facts::Room(b)) => a.room == b.room && overlaps((a.start, a.end), (b.start, b.end)), + (Facts::Delivery(a), Facts::Delivery(b)) => { + a.statement.party == b.statement.party && overlaps((a.statement.start, a.statement.end), (b.statement.start, b.statement.end)) + } + (Facts::Ads(a), Facts::Ads(b)) => a.room == b.room && a.receipts.iter().any(|x| b.receipts.iter().any(|y| x.cue == y.cue && x.session == y.session)), + _ => false, + } +} + +/// §7.2 step 6: the index of the source refused and the reason, or none. +pub fn check_across(facts: &[Facts], previous: Option<&Invoice>) -> Result<(), (usize, Reason)> { + let billed: BTreeSet<(u8, [u8; 32])> = previous.map(|p| p.records.iter().copied().collect()).unwrap_or_default(); + let mut heads: BTreeSet<(u8, [u8; 32])> = BTreeSet::new(); + for (k, f) in facts.iter().enumerate() { + let hs = f.heads(); + if hs.iter().any(|h| heads.contains(h)) || hs.iter().collect::>().len() != hs.len() { + return Err((k, Reason::RecordDuplicate)); + } + if facts[..k].iter().any(|g| overlap(f, g)) { + return Err((k, Reason::RecordOverlap)); + } + if f.records().iter().any(|r| billed.contains(r)) { + return Err((k, Reason::RecordRebilled)); + } + heads.extend(hs); + } + Ok(()) +} + +// --------------------------------------------------------------------------------------- +// §6.2–§6.6 and §8: the figures and the reports +// --------------------------------------------------------------------------------------- + +/// A line as computed, with its cited records. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Computed { + pub records: Vec<(u8, [u8; 32])>, + /// Each line, and the records it cites. + pub lines: Vec<(Line, BTreeSet<(u8, [u8; 32])>)>, + pub energy: Vec, + pub unmetered: [u32; 4], + pub amount: u64, + /// Per scope and tag, the sum of its figures, for the summed-classes check. + pub sums: BTreeMap<(u8, u8), u128>, +} + +/// Why figures cannot be stated: a quantity, an amount or a sum past a u64. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Overflow; + +fn is_free(class: u8, grant: &[u8; 32]) -> bool { + settlement::class::is_retrieval(class) && *grant == [0; 32] +} + +/// The figures of §6.2 to §6.6 over verified sources. +pub fn compute(t: &Tariff, facts: &[Facts]) -> Result { + let mut records: BTreeSet<(u8, [u8; 32])> = BTreeSet::new(); + for f in facts { + records.extend(f.records()); + } + // Quantities and citations by (source kind, source, unit). + let mut q: BTreeMap<(u8, [u8; 32], u8), (u128, BTreeSet<(u8, [u8; 32])>)> = BTreeMap::new(); + let mut add = |kind: u8, source: [u8; 32], unit: u8, n: u128, cites: &[(u8, [u8; 32])]| { + if n == 0 { + return; + } + let e = q.entry((kind, source, unit)).or_default(); + e.0 += n; + e.1.extend(cites.iter().copied()); + }; + for f in facts { + match f { + Facts::Room(r) => { + for unit in 1..=13u8 { + add(source_kind::ROOM, r.room, unit, r.unit(unit) as u128, &[(record_kind::SESSION, r.digest)]); + } + } + Facts::Delivery(d) => { + let s = &d.statement; + for c in 1..=4u8 { + let bytes: u128 = s.lines.iter().filter(|l| !l.is_attribution() && l.class == c && !l.is_free()).map(|l| l.bytes as u128).sum(); + if bytes == 0 { + continue; + } + let mut cites = vec![(record_kind::STATEMENT, d.digest)]; + for (digest, a, _) in &d.acks { + if a.mode == settlement::mode::ACK && a.entries.iter().any(|e| e.class == c && !is_free(e.class, &e.grant)) { + cites.push((record_kind::ACK, *digest)); + } + } + add(source_kind::DELIVERY, s.party, c, bytes, &cites); + } + } + Facts::Package(p) => { + let cite = [(record_kind::PACKAGE, p.receipt_hash)]; + add(source_kind::PACKAGE, p.signer, 1, 1, &cite); + add(source_kind::PACKAGE, p.signer, 2, p.total_entries as u128, &cite); + add(source_kind::PACKAGE, p.signer, 3, p.stored_bytes as u128, &cite); + } + Facts::Ads(a) => { + let unit = if a.claims_required { 1 } else { 2 }; + for r in &a.receipts { + let counted: Vec<&ImpressionFacts> = r.impressions.iter().filter(|i| i.counts).collect(); + let mut cites = vec![(record_kind::AD_RECEIPT, r.identity)]; + cites.extend(counted.iter().filter_map(|i| i.claim).map(|c| (record_kind::CLAIM, c))); + add(source_kind::ADS, r.decider, unit, counted.len() as u128, &cites); + } + } + } + } + let mut lines = Vec::with_capacity(q.len()); + let mut amount: u128 = 0; + for ((kind, source, unit), (n, cited)) in q { + let quantity = u64::try_from(n).map_err(|_| Overflow)?; + let price = t.price(kind, unit); + let a = n * price as u128 / divisor(kind, unit); + let line_amount = u64::try_from(a).map_err(|_| Overflow)?; + amount += a; + let line = Line { source_kind: kind, source, unit, quantity, price, amount: line_amount, cited: u32::try_from(cited.len()).map_err(|_| Overflow)?, cites: cites_digest(&cited) }; + lines.push((line, cited)); + } + // Energy (§6.6): each figure once, in one scope. + let mut figures: Vec<(u8, Figure)> = Vec::new(); + let mut seen: BTreeSet<[u8; 32]> = BTreeSet::new(); + for f in facts { + if let Facts::Room(r) = f { + for (d, fig) in &r.hops { + if seen.insert(*d) { + figures.push((scope::ROOM_HOPS, fig.clone())); + } + } + } + } + for f in facts { + if let Facts::Delivery(d) = f { + for (dg, fig) in &d.hops { + if seen.insert(*dg) { + figures.push((scope::DELIVERY_HOPS, fig.clone())); + } + } + } + } + for f in facts { + match f { + Facts::Package(p) => figures.push((scope::PACKAGING, p.figure.clone())), + Facts::Ads(a) => figures.extend(a.receipts.iter().map(|r| (scope::AD_DECISIONS, r.figure.clone()))), + _ => {} + } + } + let (energy, unmetered, sums) = rows_of(&figures)?; + Ok(Computed { records: records.into_iter().collect(), lines, energy, unmetered, amount: u64::try_from(amount).map_err(|_| Overflow)?, sums }) +} + +type RowsOf = (Vec, [u32; 4], BTreeMap<(u8, u8), u128>); + +/// Rows, unmetered counts and sums per (scope, tag) of figures. +fn rows_of(figures: &[(u8, Figure)]) -> Result { + let mut unmetered = [0u32; 4]; + let mut acc: BTreeMap<(u8, u8), (u32, u128, u32)> = BTreeMap::new(); + for (s, f) in figures { + match f.tag() { + None => unmetered[*s as usize - 1] += 1, + Some(tag) => { + let e = acc.entry((*s, tag)).or_default(); + e.0 += 1; + e.1 += f.joules as u128; + e.2 = e.2.max(f.ppm()); + } + } + } + let mut rows = Vec::with_capacity(acc.len()); + for (&(scope, tag), &(n, j, ppm)) in &acc { + rows.push(Row { scope, tag, figures: n, joules: u64::try_from(j).map_err(|_| Overflow)?, max_ppm: ppm }); + } + let sums = acc.into_iter().map(|(k, (_, j, _))| (k, j)).collect(); + Ok((rows, unmetered, sums)) +} + +/// Whether a row's joules sum its class with another of its scope (§6.6). +fn summed_across(row: &Row, sums: &BTreeMap<(u8, u8), u128>) -> bool { + let own = sums.get(&(row.scope, row.tag)).copied(); + if own == Some(row.joules as u128) { + return false; + } + let others: Vec = sums.iter().filter(|((s, t), _)| *s == row.scope && *t != row.tag).map(|(_, j)| *j).collect(); + let Some(own) = own else { return false }; + (1u32..(1 << others.len())).any(|m| { + let extra: u128 = others.iter().enumerate().filter(|(i, _)| m & (1 << i) != 0).map(|(_, j)| *j).sum(); + own + extra == row.joules as u128 + }) +} + +/// The invoice of `computed`, unsigned. +pub fn invoice_of(t: &Tariff, prev: [u8; 32], start: u64, end: u64, c: &Computed) -> Invoice { + Invoice { + seller: t.seller, + buyer: t.buyer, + tariff: t.digest(), + prev, + start, + end, + currency: t.currency, + records: c.records.clone(), + lines: c.lines.iter().map(|(l, _)| *l).collect(), + energy: c.energy.clone(), + unmetered: c.unmetered, + amount: c.amount, + sig: [0; 64], + } +} + +/// §7.2 step 7: compare a stated invoice with the computed figures. +pub fn check_figures(i: &Invoice, c: &Computed) -> Result<(), &'static str> { + let by_key: BTreeMap<(u8, [u8; 32], u8), &(Line, BTreeSet<(u8, [u8; 32])>)> = c.lines.iter().map(|x| (x.0.key(), x)).collect(); + for l in &i.lines { + match by_key.get(&l.key()) { + Some((got, _)) if l.quantity <= got.quantity => {} + _ => return Err(Reason::QuantityNotRecorded.code()), + } + } + if i.energy.iter().any(|r| summed_across(r, &c.sums)) { + return Err(CLASSES_SUMMED); + } + for l in &i.lines { + let (got, _) = by_key[&l.key()]; + if l.cited != got.cited || l.cites != got.cites { + return Err(Reason::CitationMismatch.code()); + } + } + let lines: Vec = c.lines.iter().map(|(l, _)| *l).collect(); + if i.records != c.records || i.lines != lines || i.energy != c.energy || i.unmetered != c.unmetered || i.amount != c.amount { + return Err(Reason::InvoiceMismatch.code()); + } + Ok(()) +} + +fn class_label(tag: u8) -> &'static str { + ["", "HwShunt", "ModelBased", "Estimator", "OnChipCounter", "CalibratedInstrument"].get(tag as usize).copied().unwrap_or("") +} + +fn rows_json(rows: &[Row]) -> Value { + Value::Array(rows.iter().map(|r| json!({"class": class_label(r.tag), "figures": r.figures, "joules": r.joules, "max_ppm": r.max_ppm})).collect()) +} + +/// §8.1 for one ads source. +fn ads_report(a: &AdsFacts) -> Value { + let (mut billable, mut counted, mut billable_spend, mut counted_spend) = (0u64, 0u64, 0u64, 0u64); + let mut not_counted: BTreeMap<&str, u64> = BTreeMap::new(); + let receipts: Vec = a + .receipts + .iter() + .map(|r| { + let b = r.impressions.iter().filter(|i| i.billable).count() as u64; + let c = r.impressions.iter().filter(|i| i.counts).count() as u64; + for i in r.impressions.iter().filter(|i| i.billable && !i.counts) { + *not_counted.entry(i.verdict).or_default() += 1; + } + billable += b; + counted += c; + billable_spend = billable_spend.saturating_add(r.spend_micros); + counted_spend = counted_spend.saturating_add(r.counted_spend_micros); + let imps: Vec = r + .impressions + .iter() + .map(|i| { + json!({"slot": i.slot, "ad_id": i.ad_id, "price_micros": i.price_micros, "quartiles_reached": i.quartiles_reached, + "billable": i.billable, "counts": i.counts, "verdict": i.verdict}) + }) + .collect(); + json!({ + "identity": hex(&r.identity), "decider": hex(&r.decider), "cue": hex(&r.cue), "session": hex(&r.session), + "impressions": imps, "billable_impressions": b, "billable_spend_micros": r.spend_micros, + "counted_impressions": c, "counted_spend_micros": r.counted_spend_micros, + }) + }) + .collect(); + let mut nc = Map::new(); + for (k, n) in not_counted { + nc.insert(k.into(), n.into()); + } + json!({ + "room": hex(&a.room), "claims_required": a.claims_required, "receipts": receipts, + "billable_impressions": billable, "counted_impressions": counted, + "billable_spend_micros": billable_spend, "counted_spend_micros": counted_spend, "not_counted": nc, + }) +} + +/// §8.2's overlaps: by (content hash, downstream, first party, first record, second party, +/// second record), the bytes their items share. +pub fn overlaps_of(facts: &[Facts]) -> BTreeMap<([u8; 32], [u8; 32], [u8; 32], [u8; 32], [u8; 32], [u8; 32]), u64> { + let acks: Vec<(&[u8; 32], &Ack, &Vec>)> = facts + .iter() + .filter_map(|f| if let Facts::Delivery(d) = f { Some(d) } else { None }) + .flat_map(|d| d.acks.iter()) + .filter(|(_, a, _)| a.mode == settlement::mode::ACK) + .map(|(d, a, i)| (d, a, i)) + .collect(); + let mut out = BTreeMap::new(); + for (dx, x, ix) in &acks { + for (dy, y, iy) in &acks { + if !(x.upstream < y.upstream && x.downstream == y.downstream && overlaps((x.start, x.end), (y.start, y.end))) { + continue; + } + for a in ix.iter().flatten() { + for b in iy.iter().flatten() { + if a.content_hash == b.content_hash && overlaps((a.start, a.end), (b.start, b.end)) { + let shared = a.end.min(b.end) - a.start.max(b.start); + let e: &mut u64 = out.entry((a.content_hash, x.downstream, x.upstream, **dx, y.upstream, **dy)).or_default(); + *e = e.saturating_add(shared); + } + } + } + } + } + out +} + +/// §8.2 over the delivery sources. +fn delivery_report(facts: &[Facts], c: &Computed) -> Value { + let mut parties: BTreeMap<[u8; 32], Vec<&DeliveryFacts>> = BTreeMap::new(); + for f in facts { + if let Facts::Delivery(d) = f { + parties.entry(d.statement.party).or_default().push(d); + } + } + let rows: Vec = parties + .iter() + .map(|(party, ds)| { + let mut classes = [(0u64, 0u64); 4]; + let (mut free, mut stated) = (0u64, 0u64); + let mut declared = (0u64, 0u64, 0u64); + let mut figures: Vec<(u8, Figure)> = Vec::new(); + let mut seen = BTreeSet::new(); + let currency = ds.iter().find(|d| d.statement.tariff.currency != [0; 3]).map(|d| d.statement.tariff.currency); + for d in ds { + let s = &d.statement; + for (k, (o, b)) in s.classes.iter().enumerate() { + classes[k].0 = classes[k].0.saturating_add(*o); + classes[k].1 = classes[k].1.saturating_add(*b); + } + free = free.saturating_add(s.lines.iter().filter(|l| l.is_free()).map(|l| l.bytes).sum()); + stated = stated.saturating_add(s.amount); + declared.0 += s.unacknowledged.0 as u64; + declared.1 = declared.1.saturating_add(s.unacknowledged.1); + declared.2 = declared.2.saturating_add(s.unacknowledged.2); + for (dg, fig) in &d.hops { + if seen.insert(*dg) { + figures.push((scope::DELIVERY_HOPS, fig.clone())); + } + } + } + let invoiced: u64 = c.lines.iter().filter(|(l, _)| l.source_kind == source_kind::DELIVERY && l.source == *party).map(|(l, _)| l.amount).sum(); + let (energy, unmetered, _) = rows_of(&figures).unwrap_or_default(); + let mut cl = Map::new(); + for (k, (o, b)) in classes.iter().enumerate() { + cl.insert(unit_name(source_kind::DELIVERY, k as u8 + 1).expect("four classes").into(), json!({"objects": o, "bytes": b})); + } + json!({ + "party": hex(party), + "statements": ds.iter().map(|d| hex(&d.digest)).collect::>(), + "currency": currency.map(|c| String::from_utf8_lossy(&c).into_owned()), + "classes": cl, + "free_bytes": free, + "declared": {"declarations": declared.0, "objects": declared.1, "bytes": declared.2}, + "stated_amount": stated, + "invoiced_amount": invoiced, + "energy": rows_json(&energy), + "unmetered": unmetered[scope::DELIVERY_HOPS as usize - 1], + }) + }) + .collect(); + let overlaps: Vec = overlaps_of(facts) + .into_iter() + .map(|((h, down, p1, r1, p2, r2), bytes)| { + json!({"content_hash": hex(&h), "downstream": hex(&down), "bytes": bytes, + "first": {"party": hex(&p1), "record": hex(&r1)}, "second": {"party": hex(&p2), "record": hex(&r2)}}) + }) + .collect(); + json!({"parties": rows, "overlaps": overlaps}) +} + +/// The reports of §8. +pub fn reports(facts: &[Facts], c: &Computed) -> Value { + let ads: Vec = facts.iter().filter_map(|f| if let Facts::Ads(a) = f { Some(ads_report(a)) } else { None }).collect(); + json!({"ads": ads, "delivery": delivery_report(facts, c)}) +} + +// --------------------------------------------------------------------------------------- +// §7 Verification +// --------------------------------------------------------------------------------------- + +/// An accepted invoice (§7.3). +#[derive(Clone, Debug)] +pub struct Accepted { + pub digest: [u8; 32], + pub tariff: Tariff, + pub invoice: Invoice, + pub facts: Vec, + pub computed: Computed, +} + +/// The digest of given invoice bytes: SHA-256 over all but their last 64. +pub fn given_digest(b: &[u8]) -> [u8; 32] { + sha256(&[&b[..b.len().saturating_sub(64)]]) +} + +/// §7.2 step 4: the previous invoice, read and checked against `inv`. +fn check_previous(inv: &Invoice, previous: Option<&[u8]>) -> Result, Refusal> { + let at = || json!(["previous"]); + if inv.prev == [0; 32] { + return Ok(None); + } + let b = previous.filter(|b| given_digest(b) == inv.prev).ok_or((RECORD_MISSING, at()))?; + let p = Invoice::parse(b).map_err(|r| (r, at()))?; + if p.tariff != inv.tariff || p.seller != inv.seller || p.buyer != inv.buyer { + return Err((Reason::TariffMismatch.code(), at())); + } + if p.end > inv.start { + return Err((Reason::InvoiceOverlap.code(), at())); + } + Ok(Some(p)) +} + +fn place(prefix: Value, rest: Value) -> Value { + let mut v = prefix.as_array().cloned().unwrap_or_default(); + v.extend(rest.as_array().cloned().unwrap_or_default()); + Value::Array(v) +} + +/// §7.2 steps 5 and 6 over a list of sources, for a tariff and a period. +pub fn verify_sources(t: &Tariff, start: u64, end: u64, sources: &[Value], previous: Option<&Invoice>) -> Result, Refusal> { + let mut facts = Vec::with_capacity(sources.len()); + for (k, s) in sources.iter().enumerate() { + let f = verify_source(s).map_err(|(r, at)| (r, place(json!(["source", k]), at)))?; + check_source(t, start, end, &f).map_err(|r| (r.code(), json!(["source", k])))?; + facts.push(f); + } + check_across(&facts, previous).map_err(|(k, r)| (r.code(), json!(["source", k])))?; + Ok(facts) +} + +/// §7.2: verify a verification document (§7.1). +pub fn verify(doc: &Value) -> Result { + let field = |k: &str| doc.get(k).and_then(Value::as_str).and_then(unhex).ok_or((MALFORMED, json!([k]))); + let tb = field("tariff")?; + let ib = field("invoice")?; + let pb = match doc.get("previous") { + Some(Value::Null) => None, + Some(Value::String(s)) => Some(unhex(s).ok_or((MALFORMED, json!(["previous"])))?), + _ => return Err((MALFORMED, json!(["previous"]))), + }; + let sources = doc.get("sources").and_then(Value::as_array).ok_or((MALFORMED, json!(["source"])))?; + // Step 1. + let t = Tariff::parse(&tb)?; + // Step 2. + let inv = Invoice::parse(&ib).map_err(|r| (r, json!(["invoice"])))?; + // Step 3. + if inv.tariff != t.digest() || inv.seller != t.seller || inv.buyer != t.buyer || inv.currency != t.currency { + return Err((Reason::TariffMismatch.code(), json!(["invoice"]))); + } + if !(t.start <= inv.start && inv.end <= t.end) { + return Err((Reason::OutsideTariff.code(), json!(["invoice"]))); + } + // Step 4. + let prev = check_previous(&inv, pb.as_deref())?; + // Steps 5 and 6. + let facts = verify_sources(&t, inv.start, inv.end, sources, prev.as_ref())?; + // Step 7. + let computed = compute(&t, &facts).map_err(|_| (Reason::InvoiceMismatch.code(), json!(["invoice"])))?; + check_figures(&inv, &computed).map_err(|r| (r, json!(["invoice"])))?; + Ok(Accepted { digest: inv.digest(), tariff: t, invoice: inv, facts, computed }) +} + +/// Compute and sign the invoice of `sources` for a period under `t`, chained after +/// `previous` (none for the first): what the ledger service does on `POST /v1/invoices`. +pub fn make_invoice(seller: &SigningKey, t: &Tariff, previous: Option<&Invoice>, start: u64, end: u64, sources: &[Value]) -> Result { + if seller.verifying_key().to_bytes() != t.seller { + return Err((Reason::TariffMismatch.code(), json!(["invoice"]))); + } + if !(start < end && t.start <= start && end <= t.end) { + return Err((Reason::OutsideTariff.code(), json!(["invoice"]))); + } + if let Some(p) = previous + && p.end > start + { + return Err((Reason::InvoiceOverlap.code(), json!(["previous"]))); + } + let facts = verify_sources(t, start, end, sources, previous)?; + let c = compute(t, &facts).map_err(|_| (Reason::InvoiceMismatch.code(), json!(["invoice"])))?; + let prev = previous.map_or([0; 32], Invoice::digest); + Ok(invoice_of(t, prev, start, end, &c).sign(seller)) +} + +/// What §7.3 states of an accepted invoice. +pub fn accepted_json(a: &Accepted) -> Value { + let i = &a.invoice; + let lines: Vec = a + .computed + .lines + .iter() + .map(|(l, cited)| { + json!({ + "source_kind": source_kind::name(l.source_kind), "source": hex(&l.source), + "unit": unit_name(l.source_kind, l.unit).unwrap_or(""), "quantity": l.quantity, "price": l.price, "amount": l.amount, + "cites": cited.iter().map(|(k, id)| json!([k, hex(id)])).collect::>(), + }) + }) + .collect(); + let energy: Vec = i.energy.iter().map(|r| json!({"scope": scope::name(r.scope), "class": class_label(r.tag), "figures": r.figures, "joules": r.joules, "max_ppm": r.max_ppm})).collect(); + let (mut unmetered, mut coverage) = (Map::new(), Map::new()); + for s in 1..=4u8 { + let n = i.unmetered[s as usize - 1]; + let has_row = i.energy.iter().any(|r| r.scope == s); + unmetered.insert(scope::name(s).into(), n.into()); + coverage.insert(scope::name(s).into(), json!(if n > 0 { "partial" } else if has_row { "whole" } else { "none" })); + } + json!({ + "outcome": "accept", + "digest": hex(&a.digest), + "tariff": hex(&i.tariff), + "currency": String::from_utf8_lossy(&i.currency), + "start": i.start, + "end": i.end, + "records": i.records.len(), + "lines": lines, + "energy": energy, + "unmetered": unmetered, + "coverage": coverage, + "amount": i.amount, + "reports": reports(&a.facts, &a.computed), + }) +} + +/// A verification's outcome as the corpus states it. +pub fn outcome_json(r: &Result) -> Value { + match r { + Ok(a) => accepted_json(a), + Err((reason, at)) => json!({"outcome": "refuse", "reason": reason, "at": at}), + } +} diff --git a/wai/wai-rs/src/receipt_ledger/corpus.rs b/wai/wai-rs/src/receipt_ledger/corpus.rs new file mode 100644 index 000000000..4e2a1dcdf --- /dev/null +++ b/wai/wai-rs/src/receipt_ledger/corpus.rs @@ -0,0 +1,492 @@ +//! The receipt-ledger corpus (`live-conformance/ledger/`, extensions/receipt-ledger.md §11). +//! +//! Its sources come from the corpora of the extensions it reads, each taken whole as that +//! corpus states it: rooms and their receipts from `live-conformance/metering/`, a statement of +//! a viewer that forwarded sealed objects from `live-conformance/settlement/`, ad rooms from +//! `live-conformance/ads/` and `live-conformance/presentation/`, and packages from +//! `pack-conformance/packs/`. The statements of two delivering parties, with the +//! acknowledgements their viewers signed and their per-hop records, are written here from +//! fixed seeds, as are the tariffs and the invoices. [`generate`] reads those files from a +//! `wai/` directory; [`verify`] checks every vector against the outcome it states. + +use std::collections::BTreeMap; +use std::path::Path; + +use ed25519_dalek::SigningKey; +use serde_json::{json, Map, Value}; + +use super::*; +use crate::energy_class::{CounterFiltering, Uncertainty}; +use crate::jwp_hash::content_hash; +use crate::live_metering::HopRecord; +use crate::settlement::{class, item_list, Entry, Statement}; + +/// 2026-10-01T00:00:00Z, 2026-11-01T00:00:00Z: the tariffs' period. +const MONTH: (u64, u64) = (1_790_812_800_000_000, 1_793_491_200_000_000); +/// 2026-10-16T00:00:00Z: the first invoice's day, and the rooms' `t0`. +const DAY: u64 = 1_792_108_800_000_000; +const DAY_US: u64 = 86_400_000_000; +/// The delivering parties' statement period: an hour into the day, one minute. +const HOUR: u64 = 3_600_000_000; +const MINUTE: u64 = 60_000_000; +const SECOND: u64 = 1_000_000; + +fn key(label: &str) -> SigningKey { + SigningKey::from_bytes(&sha256(&[b"wai:ledger-corpus\x01", label.as_bytes()])) +} + +fn pk(label: &str) -> [u8; 32] { + key(label).verifying_key().to_bytes() +} + +fn read_json(wai: &Path, rel: &str) -> Result { + let t = std::fs::read_to_string(wai.join(rel)).map_err(|e| format!("{rel}: {e}"))?; + serde_json::from_str(&t).map_err(|e| format!("{rel}: {e}")) +} + +fn named<'a>(list: &'a Value, name: &str) -> Result<&'a Value, String> { + list.as_array().and_then(|l| l.iter().find(|v| v["name"] == name)).ok_or_else(|| format!("no vector {name}")) +} + +// --------------------------------------------------------------------------------------- +// Sources taken from the other corpora +// --------------------------------------------------------------------------------------- + +/// A room source of a metering vector (live-metering §5.1 inputs). +fn room_of(metering: &Value, name: &str) -> Result { + let v = named(&metering["vectors"], name)?; + let r = &metering["rooms"][v["room"].as_str().ok_or("a vector names its room")?]; + Ok(json!({ + "kind": "room", + "room": {"descriptor": r["descriptor"], "grants": r["grants"], "key_log_head": r["key_log_head"], "chains": r["chains"]}, + "receipt": v["receipt"], "hops": v["hops"], "descriptors": v["descriptors"], "compositions": v["compositions"], + "takes": v["takes"], "logs": v["logs"], + })) +} + +/// A delivery source of a settlement statement (settlement §8.1 inputs): the statement, the +/// records and hop records it names, the proofs their entries name, and its rooms. +fn statement_of(settle: &Value, name: &str) -> Result { + let v = named(&settle["statements"], name)?; + let sb = unhex(v["statement"].as_str().ok_or("a statement")?).ok_or("statement hex")?; + let s = Statement::parse(&sb).map_err(|e| format!("{name}: {e}"))?; + let mut records = Vec::new(); + let mut proofs = Vec::new(); + for d in &s.records { + let r = settle["records"].as_array().and_then(|l| l.iter().find(|r| r["digest"] == hex(d))).ok_or("a named record")?; + let a = Ack::parse(&unhex(r["record"].as_str().ok_or("record")?).ok_or("record hex")?).map_err(|e| e.to_string())?; + for e in a.entries.iter().filter(|e| e.proof != [0; 32]) { + let p = settle["proofs"].as_array().and_then(|l| l.iter().find(|p| p["digest"] == hex(&e.proof))).ok_or("a named proof")?; + if !proofs.contains(&p["proof"]) { + proofs.push(p["proof"].clone()); + } + } + records.push(json!({"record": r["record"], "items": r["items"]})); + } + let hops: Vec = s + .hops + .iter() + .map(|d| settle["hops"].as_array().and_then(|l| l.iter().find(|h| h["digest"] == hex(d))).map(|h| h["hop"].clone()).ok_or("a named hop record")) + .collect::>()?; + let rooms: Vec = v["rooms"] + .as_array() + .ok_or("rooms")? + .iter() + .map(|n| { + let r = &settle["rooms"][n.as_str().unwrap_or("")]; + json!({"descriptor": r["descriptor"], "grants": r["grants"]}) + }) + .collect(); + Ok(json!({"kind": "delivery", "statement": v["statement"], "records": records, "proofs": proofs, "hops": hops, "rooms": rooms})) +} + +/// An ads source of an ads or presentation vector (live-ads §9.1 inputs). +fn ads_of(doc: &Value, name: &str, claims: bool) -> Result { + let v = named(&doc["vectors"], name)?; + let (claims, creatives, issuers) = if claims { (v["claims"].clone(), doc["creatives"].clone(), doc["issuers"].clone()) } else { (json!([]), json!([]), json!([])) }; + Ok(json!({ + "kind": "ads", "descriptor": v["descriptor"], "grants": v["grants"], "key_log": v["key_log"], "chains": v["chains"], + "policy": v["policy"], "pt0_ms": v["pt0_ms"], "receipts": v["receipts"], "pods": v["pods"], + "claims": claims, "creatives": creatives, "issuers": issuers, + })) +} + +/// A package source of a pack (packaging §9.1 inputs). +fn package_of(wai: &Path, pack: &str) -> Result { + let dir = wai.join("pack-conformance/packs").join(pack).join("package"); + let text = std::fs::read_to_string(dir.join("package.receipt.json")).map_err(|e| format!("{pack}: {e}"))?; + let r = PackageReceiptV2::from_json(&text).map_err(|e| e.to_string())?; + let indexes: Vec = r + .components + .iter() + .map(|c| std::fs::read(dir.join(c.index_file())).map(|b| json!(hex(&b))).map_err(|e| format!("{pack}: {e}"))) + .collect::>()?; + Ok(json!({"kind": "package", "receipt": text, "indexes": indexes})) +} + +// --------------------------------------------------------------------------------------- +// Two delivering parties, written here +// --------------------------------------------------------------------------------------- + +/// An object of `len` bytes: a deterministic envelope. +fn object(n: u8, len: usize) -> Vec { + let mut o = Vec::with_capacity(len + 32); + let mut block = sha256(&[b"wai:ledger-corpus-object\x01", &[n]]); + while o.len() < len { + o.extend_from_slice(&block); + block = sha256(&[&block]); + } + o.truncate(len); + o +} + +fn whole(o: &[u8]) -> Item { + Item::whole(o) +} + +fn range(o: &[u8], start: u64, end: u64) -> Item { + Item { content_hash: content_hash(o), start, end } +} + +/// An acknowledgement (or declaration) and its item lists. +struct Signed { + ack: Ack, + items: Vec>, +} + +#[allow(clippy::too_many_arguments)] +fn ack(mode: u8, up: &str, down: Option<&str>, start: u64, end: u64, prev: [u8; 32], entries: Vec<(u8, Vec)>) -> Signed { + let downstream = down.map_or([0; 32], pk); + let es: Vec = entries.iter().map(|(c, items)| Entry::of(*c, [0; 32], [0; 32], items)).collect(); + let a = Ack { mode, role: settlement::role::SINK, upstream: pk(up), downstream, start, end, prev, entries: es, sig: [0; 64] }; + let signer = if mode == settlement::mode::ACK { key(down.expect("an acknowledgement's signer")) } else { key(up) }; + Signed { ack: a.sign(&signer), items: entries.into_iter().map(|(_, i)| i).collect() } +} + +fn hop_record(room: [u8; 32], party: &str, joules: u64, class: EnergyProvenance) -> HopRecord { + HopRecord { + room, + hop: pk(party), + role: 1, + start: HOUR, + end: HOUR + MINUTE, + joules, + class: Some(class), + tracks: Vec::new(), + sessions: 0, + subscriber_us: [0; 9], + events: 0, + grants: 0, + ledger: [0; 32], + dvr: None, + sig: [0; 64], + } + .sign(&key(party)) +} + +fn delivery_json(statement: &Statement, acks: &[Signed], hop: &HopRecord, room: &Value) -> Value { + let records: Vec = acks.iter().map(|s| json!({"record": hex(&s.ack.to_bytes()), "items": s.items.iter().map(|l| hex(&item_list(l))).collect::>()})).collect(); + json!({"kind": "delivery", "statement": hex(&statement.to_bytes()), "records": records, "proofs": [], "hops": [hex(&hop.to_bytes())], "rooms": [room]}) +} + +/// The statements of `cdn-a` and `cdn-b` under `tariff`: one viewer acknowledges one object +/// from both, in windows that overlap. +fn parties(room: &Value, tariff: settlement::Tariff) -> Result<(Value, Value, Statement), String> { + let d = Descriptor::parse(&unhex(room["descriptor"].as_str().ok_or("descriptor")?).ok_or("descriptor hex")?).map_err(|e| e.to_string())?; + let room_id = d.room_id(); + let t = DAY + HOUR; + let o: Vec> = (1..=5).map(|n| object(n, 1500 + 700 * n as usize)).collect(); + use settlement::mode::{ACK as A, DECLARATION as D}; + // cdn-a: two viewers, one of them twice, a fetch served free. + let a1 = ack(A, "cdn-a", Some("viewer-1"), t, t + 20 * SECOND, [0; 32], vec![(class::LIVE, vec![whole(&o[0]), whole(&o[1])]), (class::FETCH, vec![whole(&o[2])])]); + let a2 = ack(A, "cdn-a", Some("viewer-2"), t, t + 20 * SECOND, [0; 32], vec![(class::LIVE, vec![whole(&o[0]), whole(&o[1])])]); + let a3 = ack(A, "cdn-a", Some("viewer-2"), t + 20 * SECOND, t + 40 * SECOND, a2.ack.digest(), vec![(class::LIVE, vec![whole(&o[3])])]); + // cdn-b: viewer-1 again, part of an object cdn-a delivered it, in a window that overlaps; + // a third viewer with an ancillary object; and viewers with no key, declared. + let b1 = ack(A, "cdn-b", Some("viewer-1"), t + 10 * SECOND, t + 30 * SECOND, [0; 32], vec![(class::LIVE, vec![range(&o[0], 0, 600), whole(&o[3])])]); + let b2 = ack(A, "cdn-b", Some("viewer-3"), t, t + 30 * SECOND, [0; 32], vec![(class::LIVE, vec![whole(&o[1]), whole(&o[3])]), (class::ANCILLARY, vec![whole(&o[4])])]); + let b3 = ack(D, "cdn-b", None, t, t + MINUTE, [0; 32], vec![(class::LIVE, vec![whole(&o[0]), whole(&o[1])])]); + let chip = EnergyProvenance::OnChipCounter { uncertainty: Uncertainty { relative_ppm: 25_000, window_us: MINUTE }, filtering: CounterFiltering::Unknown }; + let ha = hop_record(room_id, "cdn-a", 9_000_000, chip); + let hb = hop_record(room_id, "cdn-b", 12_500_000, EnergyProvenance::ModelBased); + let stmt = |party: &str, acks: &[&Signed], hop: &HopRecord, tariff: settlement::Tariff| { + let records: Vec<([u8; 32], Ack)> = acks.iter().map(|s| (s.ack.digest(), s.ack.clone())).collect(); + settlement::compute(pk(party), t, t + MINUTE, &records, &[(hop.digest(), hop.clone())], tariff).sign(&key(party)) + }; + let sa = stmt("cdn-a", &[&a1, &a2, &a3], &ha, tariff); + let sb = stmt("cdn-b", &[&b1, &b2, &b3], &hb, tariff); + let ordered = |s: &Statement, acks: Vec| -> Vec { + let mut v = acks; + v.sort_by_key(|x| s.records.iter().position(|d| *d == x.ack.digest())); + v + }; + let ja = delivery_json(&sa, &ordered(&sa, vec![a1, a2, a3]), &ha, room); + let jb = delivery_json(&sb, &ordered(&sb, vec![b1, b2, b3]), &hb, room); + Ok((ja, jb, sa)) +} + +/// cdn-a's statement over the same acknowledgements under another tariff, and the same +/// statement with an amount that does not recompute. +fn cdn_a_variants(room: &Value, eur: settlement::Tariff) -> Result<(Value, Value), String> { + let usd = settlement::Tariff { currency: *b"USD", prices: eur.prices }; + let (ja_usd, _, _) = parties(room, usd)?; + let (mut ja, _, sa) = parties(room, eur)?; + let mut bad = sa; + bad.amount += 1; + ja["statement"] = json!(hex(&bad.sign(&key("cdn-a")).to_bytes())); + Ok((ja_usd, ja)) +} + +// --------------------------------------------------------------------------------------- +// Tariffs and invoices +// --------------------------------------------------------------------------------------- + +/// The scope ids of a source. +fn scope_of(f: &Facts) -> Vec<(u8, [u8; 32])> { + match f { + Facts::Room(r) => vec![(source_kind::ROOM, r.room)], + Facts::Delivery(d) => vec![(source_kind::DELIVERY, d.statement.party)], + Facts::Package(p) => vec![(source_kind::PACKAGE, p.signer)], + Facts::Ads(a) => a.receipts.iter().map(|r| (source_kind::ADS, r.decider)).collect(), + } +} + +fn tariff_of(buyer: &str, room: [u32; ROOM_PRICES], scope: Vec<(u8, [u8; 32])>) -> Tariff { + let mut scope = scope; + scope.sort(); + scope.dedup(); + Tariff { + seller: pk("seller"), + buyer: pk(buyer), + start: MONTH.0, + end: MONTH.1, + currency: *b"EUR", + room, + delivery: DELIVERY_PRICES, + package: [500_000, 1_000, 2_000_000], + ads: [1_500, 1_000], + scope, + seller_sig: [0; 64], + buyer_sig: [0; 64], + } + .sign(&key("seller"), &key(buyer)) +} + +/// The delivery prices: millionths of a euro cent per 10^9 bytes, by object class. +const DELIVERY_PRICES: [u64; 4] = [20_000_000, 50_000_000, 80_000_000, 5_000_000]; + +/// The invoice of `sources`, computed and signed without the checks of §7.2 steps 3 to 6, so a +/// control can carry a defect those steps catch. +fn sealed(t: &Tariff, prev: [u8; 32], start: u64, end: u64, sources: &[&Value], f: impl FnOnce(&mut Invoice)) -> Invoice { + let facts: Vec = sources.iter().map(|s| verify_source(s).expect("a corpus source verifies")).collect(); + let c = compute(t, &facts).expect("figures that fit"); + let mut i = invoice_of(t, prev, start, end, &c); + f(&mut i); + i.sign(&key("seller")) +} + +struct Vector { + name: &'static str, + about: &'static str, + tariff: Vec, + invoice: Vec, + previous: Option>, + sources: Vec<&'static str>, +} + +fn resolve(pool: &BTreeMap<&'static str, Value>, v: &Vector) -> Value { + json!({ + "tariff": hex(&v.tariff), + "invoice": hex(&v.invoice), + "previous": v.previous.as_ref().map(|p| hex(p)), + "sources": v.sources.iter().map(|n| pool[n].clone()).collect::>(), + }) +} + +fn flip(mut b: Vec, at: usize) -> Vec { + b[at] ^= 0x01; + b +} + +/// The corpus, as the text of `vectors.json`. +pub fn generate(wai: &Path) -> Result { + let metering = read_json(wai, "live-conformance/metering/vectors.json")?; + let settle = read_json(wai, "live-conformance/settlement/vectors.json")?; + let adsdoc = read_json(wai, "live-conformance/ads/vectors.json")?; + let present = read_json(wai, "live-conformance/presentation/vectors.json")?; + let lesson_room = { + let r = &settle["rooms"]["lesson"]; + json!({"descriptor": r["descriptor"], "grants": r["grants"]}) + }; + let eur = settlement::Tariff { currency: *b"EUR", prices: DELIVERY_PRICES }; + let (cdn_a, cdn_b, _) = parties(&lesson_room, eur)?; + let (cdn_a_usd, cdn_a_bad) = cdn_a_variants(&lesson_room, eur)?; + + let mut pool: BTreeMap<&'static str, Value> = BTreeMap::new(); + pool.insert("lesson", room_of(&metering, "lesson")?); + pool.insert("lesson_mixed_classes", room_of(&metering, "mixed_classes")?); + pool.insert("lesson_bad_signature", room_of(&metering, "receipt_bad_signature")?); + pool.insert("relay", room_of(&metering, "relay_recorded")?); + pool.insert("cdn_a", cdn_a); + pool.insert("cdn_b", cdn_b); + pool.insert("cdn_a_usd", cdn_a_usd); + pool.insert("cdn_a_amount", cdn_a_bad); + pool.insert("forwarder", statement_of(&settle, "viewer_forwards_sealed_objects")?); + pool.insert("package_base", package_of(wai, "video-base-01")?); + pool.insert("package_staged", package_of(wai, "video-staged-01")?); + let mut no_index = package_of(wai, "video-base-01")?; + no_index["indexes"] = json!([]); + pool.insert("package_no_index", no_index); + pool.insert("ads_live_break", ads_of(&adsdoc, "live_break", false)?); + pool.insert("ads_gap", ads_of(&adsdoc, "gap", false)?); + pool.insert("ads_presented", ads_of(&present, "video_rule", true)?); + pool.insert("ads_past_ceiling", ads_of(&present, "ceiling_exceeded_by_presented", true)?); + pool.insert("not_a_source", json!({"kind": "statement"})); + + // The rooms' prices are the lesson receipts'; the tariff's scope is every party of the + // accepted invoices. + let room_prices = { + let Facts::Room(r) = verify_source(&pool["lesson"]).map_err(|e| format!("lesson: {e:?}"))? else { unreachable!() }; + let t = r.tariff; + let mut p = [0u32; ROOM_PRICES]; + p[0] = t.publisher; + p[1..10].copy_from_slice(&t.subscriber); + p[10] = t.composition; + p[11] = t.recorded; + p[12] = t.event; + p + }; + let day_a = ["lesson", "cdn_a", "cdn_b", "forwarder", "package_base", "ads_presented", "ads_live_break"]; + let day_b = ["relay", "package_staged"]; + let mut scope = Vec::new(); + for n in day_a.iter().chain(&day_b) { + scope.extend(scope_of(&verify_source(&pool[n]).map_err(|e| format!("{n}: {e:?}"))?)); + } + let t = tariff_of("buyer", room_prices, scope.clone()); + let lesson_id = scope.iter().find(|(k, _)| *k == source_kind::ROOM).expect("the lesson room").1; + let t_m = tariff_of("buyer-m", room_prices, vec![(source_kind::ROOM, lesson_id)]); + let seller = key("seller"); + let src = |names: &[&'static str]| -> Vec { names.iter().map(|n| pool[n].clone()).collect() }; + + let (a0, a1) = (DAY, DAY + DAY_US); + let (b0, b1) = (DAY + DAY_US, DAY + 3 * DAY_US); + let inv_a = make_invoice(&seller, &t, None, a0, a1, &src(&day_a)).map_err(|e| format!("invoice a: {e:?}"))?; + let inv_b = make_invoice(&seller, &t, Some(&inv_a), b0, b1, &src(&day_b)).map_err(|e| format!("invoice b: {e:?}"))?; + let inv_m = make_invoice(&seller, &t_m, None, a0, a1, &src(&["lesson_mixed_classes"])).map_err(|e| format!("invoice m: {e:?}"))?; + let (tb, ab, bb, mb) = (t.to_bytes(), inv_a.to_bytes(), inv_b.to_bytes(), inv_m.to_bytes()); + let refs = |names: &[&'static str]| -> Vec<&Value> { names.iter().map(|n| &pool[n]).collect() }; + + let mut tariff_period = t.clone(); + tariff_period.end = tariff_period.start; + let tariff_period = tariff_period.sign(&seller, &key("buyer")); + let mut small = t.clone(); + // The identity point, of order 1: a key of small order. + small.seller = { + let mut k = [0u8; 32]; + k[0] = 1; + k + }; + let mut narrow = t.clone(); + narrow.scope.retain(|(k, id)| !(*k == source_kind::ROOM && *id == lesson_id)); + let narrow = narrow.sign(&seller, &key("buyer")); + let inv_narrow = sealed(&narrow, [0; 32], a0, a1, &refs(&day_a), |_| {}); + + let vectors = vec![ + Vector { name: "a_day", about: "one day: a lesson room, two delivering parties and a viewer that forwarded sealed objects under no tariff of its own, a package, ads under a policy that requires claims and under one that does not", tariff: tb.clone(), invoice: ab.clone(), previous: None, sources: day_a.to_vec() }, + Vector { name: "the_next_invoice", about: "the next invoice of the chain: a relay's session whose energy is unmetered, and a second package", tariff: tb.clone(), invoice: bb.clone(), previous: Some(ab.clone()), sources: day_b.to_vec() }, + Vector { name: "room_hops_of_three_classes", about: "a room whose hops are a model, a counter and an estimate: one row each", tariff: t_m.to_bytes(), invoice: mb.clone(), previous: None, sources: vec!["lesson_mixed_classes"] }, + // The tariff. + Vector { name: "tariff_truncated", about: "a tariff one byte short", tariff: tb[..tb.len() - 1].to_vec(), invoice: ab.clone(), previous: None, sources: day_a.to_vec() }, + Vector { name: "tariff_buyer_signature", about: "a tariff whose buyer's signature does not verify", tariff: flip(tb.clone(), tb.len() - 1), invoice: ab.clone(), previous: None, sources: day_a.to_vec() }, + Vector { name: "tariff_seller_small_order", about: "a tariff whose seller is a key of small order", tariff: small.to_bytes(), invoice: ab.clone(), previous: None, sources: day_a.to_vec() }, + Vector { name: "tariff_period_empty", about: "a tariff whose period is empty, signed by both", tariff: tariff_period.to_bytes(), invoice: ab.clone(), previous: None, sources: day_a.to_vec() }, + // The invoice. + Vector { name: "invoice_trailing_byte", about: "an invoice with a byte after its signature", tariff: tb.clone(), invoice: [ab.clone(), vec![0]].concat(), previous: None, sources: day_a.to_vec() }, + Vector { name: "invoice_version", about: "an invoice of version 2", tariff: tb.clone(), invoice: { let mut b = ab.clone(); b[4] = 2; b }, previous: None, sources: day_a.to_vec() }, + Vector { name: "invoice_signature", about: "an invoice whose signature does not verify", tariff: tb.clone(), invoice: flip(ab.clone(), ab.len() - 1), previous: None, sources: day_a.to_vec() }, + Vector { name: "invoice_lines_unordered", about: "an invoice whose first two lines are swapped", tariff: tb.clone(), invoice: sealed(&t, [0; 32], a0, a1, &refs(&day_a), |i| i.lines.swap(0, 1)).to_bytes(), previous: None, sources: day_a.to_vec() }, + Vector { name: "invoice_row_of_no_class", about: "an energy row of tag 0: joules of no one class", tariff: tb.clone(), invoice: sealed(&t, [0; 32], a0, a1, &refs(&day_a), |i| i.energy.insert(0, Row { scope: 1, tag: 0, figures: 1, joules: 1, max_ppm: 0 })).to_bytes(), previous: None, sources: day_a.to_vec() }, + Vector { name: "invoice_of_another_tariff", about: "an invoice naming another tariff", tariff: tb.clone(), invoice: sealed(&t, [0; 32], a0, a1, &refs(&day_a), |i| i.tariff = t_m.digest()).to_bytes(), previous: None, sources: day_a.to_vec() }, + Vector { name: "invoice_past_its_tariff", about: "an invoice whose period runs past the tariff's", tariff: tb.clone(), invoice: sealed(&t, [0; 32], a0, MONTH.1 + DAY_US, &refs(&day_a), |_| {}).to_bytes(), previous: None, sources: day_a.to_vec() }, + // The chain. + Vector { name: "previous_not_given", about: "the next invoice without the invoice it names", tariff: tb.clone(), invoice: bb.clone(), previous: None, sources: day_b.to_vec() }, + Vector { name: "previous_overlaps", about: "the next invoice, its period starting before the previous one ends", tariff: tb.clone(), invoice: sealed(&t, inv_a.digest(), a1 - HOUR, b1, &refs(&day_b), |_| {}).to_bytes(), previous: Some(ab.clone()), sources: day_b.to_vec() }, + Vector { name: "package_billed_again", about: "the next invoice billing the first invoice's package again", tariff: tb.clone(), invoice: sealed(&t, inv_a.digest(), b0, b1, &refs(&["relay", "package_staged", "package_base"]), |_| {}).to_bytes(), previous: Some(ab.clone()), sources: vec!["relay", "package_staged", "package_base"] }, + // The sources. + Vector { name: "room_outside_scope", about: "a tariff whose scope does not name the lesson room", tariff: narrow.to_bytes(), invoice: inv_narrow.to_bytes(), previous: None, sources: day_a.to_vec() }, + Vector { name: "statement_under_another_tariff", about: "a delivering party's statement under prices in another currency", tariff: tb.clone(), invoice: ab.clone(), previous: None, sources: ["lesson", "cdn_a_usd", "cdn_b", "forwarder", "package_base", "ads_presented", "ads_live_break"].to_vec() }, + Vector { name: "room_outside_period", about: "a relay's session of another day in the first invoice", tariff: tb.clone(), invoice: ab.clone(), previous: None, sources: [&day_a[..], &["relay"][..]].concat() }, + Vector { name: "ads_room_with_a_gap", about: "an ad room whose events have a gap", tariff: tb.clone(), invoice: ab.clone(), previous: None, sources: [&day_a[..], &["ads_gap"][..]].concat() }, + Vector { name: "package_given_twice", about: "one package given as two sources", tariff: tb.clone(), invoice: ab.clone(), previous: None, sources: [&day_a[..], &["package_base"][..]].concat() }, + Vector { name: "room_session_billed_twice", about: "a second receipt of the lesson room's session", tariff: tb.clone(), invoice: ab.clone(), previous: None, sources: [&day_a[..], &["lesson_mixed_classes"][..]].concat() }, + Vector { name: "source_not_a_source", about: "a source of a kind this extension does not read", tariff: tb.clone(), invoice: ab.clone(), previous: None, sources: [&day_a[..], &["not_a_source"][..]].concat() }, + Vector { name: "room_receipt_refused", about: "a room-session receipt whose signature does not verify, refused by live-metering", tariff: tb.clone(), invoice: ab.clone(), previous: None, sources: ["lesson_bad_signature", "cdn_a", "cdn_b", "forwarder", "package_base", "ads_presented", "ads_live_break"].to_vec() }, + Vector { name: "statement_refused", about: "a statement whose amount does not recompute, refused by settlement", tariff: tb.clone(), invoice: ab.clone(), previous: None, sources: ["lesson", "cdn_a_amount", "cdn_b", "forwarder", "package_base", "ads_presented", "ads_live_break"].to_vec() }, + Vector { name: "package_refused", about: "a package receipt given without its index, refused by packaging", tariff: tb.clone(), invoice: ab.clone(), previous: None, sources: ["lesson", "cdn_a", "cdn_b", "forwarder", "package_no_index", "ads_presented", "ads_live_break"].to_vec() }, + Vector { name: "ads_refused", about: "presented spend past the decider's ceiling, refused by live-ads", tariff: tb.clone(), invoice: ab.clone(), previous: None, sources: ["lesson", "cdn_a", "cdn_b", "forwarder", "package_base", "ads_past_ceiling", "ads_live_break"].to_vec() }, + // The figures. + Vector { name: "line_bills_more_than_recorded", about: "a line one unit above what its records state", tariff: tb.clone(), invoice: sealed(&t, [0; 32], a0, a1, &refs(&day_a), |i| { i.lines[0].quantity += 1; }).to_bytes(), previous: None, sources: day_a.to_vec() }, + Vector { name: "line_cites_other_records", about: "a room line whose citation digest is an ad line's", tariff: tb.clone(), invoice: sealed(&t, [0; 32], a0, a1, &refs(&day_a), |i| { let c = i.lines[i.lines.len() - 1].cites; i.lines[0].cites = c; }).to_bytes(), previous: None, sources: day_a.to_vec() }, + Vector { name: "line_amount", about: "a line whose amount is one above its quantity at its price, and the total with it", tariff: tb.clone(), invoice: sealed(&t, [0; 32], a0, a1, &refs(&day_a), |i| { i.lines[0].amount += 1; i.amount += 1; }).to_bytes(), previous: None, sources: day_a.to_vec() }, + Vector { name: "records_short", about: "an invoice that leaves one of its records out", tariff: tb.clone(), invoice: sealed(&t, [0; 32], a0, a1, &refs(&day_a), |i| { i.records.pop(); }).to_bytes(), previous: None, sources: day_a.to_vec() }, + Vector { name: "energy_summed_across_classes", about: "a counter row that adds the model's joules to its own", tariff: t_m.to_bytes(), invoice: { + let i = sealed(&t_m, [0; 32], a0, a1, &refs(&["lesson_mixed_classes"]), |i| { + let model = i.energy.iter().find(|r| r.scope == 1 && r.tag == 2).map_or(0, |r| r.joules); + if let Some(r) = i.energy.iter_mut().find(|r| r.scope == 1 && r.tag == 4) { + r.joules += model; + } + }); + i.to_bytes() + }, previous: None, sources: vec!["lesson_mixed_classes"] }, + ]; + + let mut sources = Map::new(); + for (n, v) in &pool { + sources.insert((*n).into(), v.clone()); + } + let mut out = Vec::with_capacity(vectors.len()); + for v in &vectors { + let doc = resolve(&pool, v); + let outcome = outcome_json(&super::verify(&doc)); + out.push(json!({ + "name": v.name, "about": v.about, "tariff": doc["tariff"], "invoice": doc["invoice"], "previous": doc["previous"], + "sources": v.sources, "outcome": outcome, + })); + } + let keys = json!({"seller": hex(&pk("seller")), "buyer": hex(&pk("buyer")), "buyer_m": hex(&pk("buyer-m")), "cdn_a": hex(&pk("cdn-a")), "cdn_b": hex(&pk("cdn-b"))}); + let doc = json!({ + "corpus": CAPABILITY, + "extension": "extensions/receipt-ledger.md", + "keys": keys, + "sources": sources, + "vectors": out, + }); + Ok(serde_json::to_string_pretty(&doc).expect("JSON") + "\n") +} + +/// A vector of the corpus as a verification document (§7.1), its sources resolved by name. +pub fn document(doc: &Value, v: &Value) -> Option { + let sources: Vec = v["sources"].as_array()?.iter().map(|n| doc["sources"].get(n.as_str()?).cloned()).collect::>()?; + Some(json!({"tariff": v["tariff"], "invoice": v["invoice"], "previous": v["previous"], "sources": sources})) +} + +/// Every vector against the outcome it states: the failures. +pub fn verify(text: &str) -> Vec { + let doc: Value = match serde_json::from_str(text) { + Ok(d) => d, + Err(e) => return vec![format!("vectors.json: {e}")], + }; + let mut fails = Vec::new(); + for v in doc["vectors"].as_array().into_iter().flatten() { + let name = v["name"].as_str().unwrap_or("?"); + let Some(d) = document(&doc, v) else { + fails.push(format!("{name}: the vector does not read")); + continue; + }; + let got = outcome_json(&super::verify(&d)); + if got != v["outcome"] { + fails.push(format!("{name}: the outcome is {got}, the corpus states {}", v["outcome"])); + } + } + fails +} diff --git a/wai/wai-rs/tests/receipt_ledger.rs b/wai/wai-rs/tests/receipt_ledger.rs new file mode 100644 index 000000000..e8de6fe42 --- /dev/null +++ b/wai/wai-rs/tests/receipt_ledger.rs @@ -0,0 +1,97 @@ +//! `wai.delivery.ledger` (extensions/receipt-ledger.md): the committed corpus +//! `live-conformance/ledger/vectors.json` regenerates byte for byte from its seeds and the sources +//! of the corpora it reads, and is the only file there besides its README; every vector gets the +//! outcome it states; every reason the extension adds refuses a vector, and so does each source +//! kind's own extension; the accepted vectors are the cases §11 names; and §10 lists exactly the +//! reasons the reference reports. +#![cfg(feature = "receipt_ledger")] + +use std::collections::BTreeSet; +use std::path::PathBuf; +use wai::receipt_ledger::{corpus, Reason}; + +fn wai_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") +} + +fn read(p: &str) -> String { + std::fs::read_to_string(wai_dir().join(p)).unwrap_or_else(|e| panic!("{p}: {e}")) +} + +fn committed() -> String { + read("live-conformance/ledger/vectors.json") +} + +#[test] +fn the_committed_corpus_regenerates_from_its_seeds_and_sources() { + let generated = corpus::generate(&wai_dir()).expect("the corpus generates"); + assert!(committed() == generated, "live-conformance/ledger/vectors.json differs from `wai_receipt_ledger_vectors gen`"); + let mut files: Vec = std::fs::read_dir(wai_dir().join("live-conformance/ledger")).unwrap().flatten().map(|e| e.file_name().to_string_lossy().into_owned()).collect(); + files.sort(); + assert_eq!(files, ["README.md", "vectors.json"]); +} + +#[test] +fn every_vector_gets_its_stated_outcome() { + let fails = corpus::verify(&committed()); + assert!(fails.is_empty(), "{}", fails.join("\n")); +} + +/// Every reason this extension adds and every code it reuses refuses a vector; each source kind +/// is refused once by its own extension; the accepted invoices are §11's. +#[test] +fn the_corpus_covers_every_reason_and_case() { + let doc: serde_json::Value = serde_json::from_str(&committed()).unwrap(); + let vectors = doc["vectors"].as_array().unwrap(); + let seen: BTreeSet<&str> = vectors.iter().filter_map(|v| v["outcome"]["reason"].as_str()).collect(); + for r in Reason::ALL { + assert!(seen.contains(r.code()), "nothing in the corpus is refused {r}"); + } + for r in Reason::REUSED { + assert!(seen.contains(r), "nothing in the corpus is refused {r}"); + } + let get = |n: &str| vectors.iter().find(|v| v["name"] == n).unwrap_or_else(|| panic!("no vector {n}"))["outcome"].clone(); + // A refusal of each source kind by its own extension, at the source's place. + for (n, reason, at) in [ + ("room_receipt_refused", "bad_signature", serde_json::json!(["source", 0, "receipt"])), + ("statement_refused", "statement_mismatch", serde_json::json!(["source", 1, "statement"])), + ("package_refused", "receipt-index-count", serde_json::json!(["source", 4, "receipt"])), + ("ads_refused", "funds_exceeded", serde_json::json!(["source", 5, "receipt", 1])), + ] { + let o = get(n); + assert_eq!((o["reason"].as_str(), &o["at"]), (Some(reason), &at), "{n}"); + } + let a = get("a_day"); + assert_eq!(a["outcome"], "accept"); + let kinds: BTreeSet<&str> = a["lines"].as_array().unwrap().iter().map(|l| l["source_kind"].as_str().unwrap()).collect(); + assert_eq!(kinds, ["ads", "delivery", "package", "room"].into_iter().collect(), "a line of every source kind"); + let units: BTreeSet<&str> = a["lines"].as_array().unwrap().iter().map(|l| l["unit"].as_str().unwrap()).collect(); + assert!(units.contains("presented_impressions") && units.contains("billable_impressions"), "ads under both rules"); + assert!(a["lines"].as_array().unwrap().iter().all(|l| !l["cites"].as_array().unwrap().is_empty()), "every line cites its records"); + let delivery = &a["reports"]["delivery"]; + assert_eq!(delivery["parties"].as_array().unwrap().len(), 3, "two delivering parties and the forwarder"); + assert!(!delivery["overlaps"].as_array().unwrap().is_empty(), "one viewer acknowledged one object from both parties"); + assert!(delivery["parties"].as_array().unwrap().iter().any(|p| p["declared"]["bytes"].as_u64().unwrap() > 0), "declared bytes, never billed"); + assert!(delivery["parties"].as_array().unwrap().iter().any(|p| p["free_bytes"].as_u64().unwrap() > 0), "a retrieval served free"); + let ads = a["reports"]["ads"].as_array().unwrap(); + assert!(ads.iter().any(|r| r["claims_required"] == true && !r["not_counted"].as_object().unwrap().is_empty()), "billed impressions the players' claims did not count"); + let scopes: BTreeSet<&str> = a["energy"].as_array().unwrap().iter().map(|r| r["scope"].as_str().unwrap()).collect(); + assert!(scopes.contains("room_hops") && scopes.contains("delivery_hops") && scopes.contains("ad_decisions"), "{scopes:?}"); + let b = get("the_next_invoice"); + assert_eq!(b["outcome"], "accept"); + assert_eq!(b["coverage"]["room_hops"], "partial", "a relay's unmetered hop"); + let m = get("room_hops_of_three_classes"); + let rows: Vec<&str> = m["energy"].as_array().unwrap().iter().filter(|r| r["scope"] == "room_hops").map(|r| r["class"].as_str().unwrap()).collect(); + assert_eq!(rows, ["ModelBased", "Estimator", "OnChipCounter"], "one row per class, never one across them"); +} + +/// §10 of the extension lists exactly the codes `Reason::ALL` has, each once, in order. +#[test] +fn the_extension_lists_the_reasons() { + let text = read("extensions/receipt-ledger.md"); + let at = text.find("\n## 10. Reasons").expect("a §10"); + let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); + let listed: Vec = text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect(); + let want: Vec = Reason::ALL.iter().map(|r| r.code().to_string()).collect(); + assert_eq!(listed, want, "§10's table and Reason::ALL differ"); +} -- 2.54.0 (Apple Git-157) From eec5d8dfa0e861f90352d28d8d2d079d73babd92 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 25 Oct 2026 21:28:20 -0400 Subject: [PATCH 083/242] wai: key release, the proxy form of wai.video.keys (extensions/key-release.md) The scheme (cenc, cbc1, cens, cbcs; the HLS methods AES-128, SAMPLE-AES, AES-256-GCM) and the key delivery (HLS key URI, Clear Key, a content protection system by its system ID) of each released key; the commitment list WKL1 derived from a CPIX document, so the proxy copies each key_commit and holds no key; the entitlement WKE1 bound to a device key and the device's proof over each request; the release decision as a pure function of signed inputs (freshness, replay, key periods, offline licences and renewals); the release ticket WKT1 for a clear delivery, so the proxy never relays a key; verification of a proxy's log. The key_release module of wai-rs (feature key_release), its unit tests, and the corpus live-conformance/keys/, generated from fixed seeds by wai_key_release_vectors and checked by tests/key_release.rs. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/key-release.md | 646 +++ wai/live-conformance/keys/README.md | 56 + wai/live-conformance/keys/broken.cpix.xml | 25 + wai/live-conformance/keys/channel.cpix.xml | 47 + wai/live-conformance/keys/encrypted.cpix.xml | 17 + wai/live-conformance/keys/episode.cpix.xml | 12 + wai/live-conformance/keys/film.cpix.xml | 25 + wai/live-conformance/keys/vectors.json | 4279 +++++++++++++++++ wai/wai-rs/Cargo.toml | 15 + wai/wai-rs/src/bin/wai_key_release_vectors.rs | 75 + wai/wai-rs/src/key_release.rs | 1739 +++++++ wai/wai-rs/src/key_release/corpus.rs | 730 +++ wai/wai-rs/src/key_release/cpix.rs | 576 +++ wai/wai-rs/src/key_release/tests.rs | 331 ++ wai/wai-rs/src/lib.rs | 9 + wai/wai-rs/tests/key_release.rs | 83 + 16 files changed, 8665 insertions(+) create mode 100644 wai/extensions/key-release.md create mode 100644 wai/live-conformance/keys/README.md create mode 100644 wai/live-conformance/keys/broken.cpix.xml create mode 100644 wai/live-conformance/keys/channel.cpix.xml create mode 100644 wai/live-conformance/keys/encrypted.cpix.xml create mode 100644 wai/live-conformance/keys/episode.cpix.xml create mode 100644 wai/live-conformance/keys/film.cpix.xml create mode 100644 wai/live-conformance/keys/vectors.json create mode 100644 wai/wai-rs/src/bin/wai_key_release_vectors.rs create mode 100644 wai/wai-rs/src/key_release.rs create mode 100644 wai/wai-rs/src/key_release/corpus.rs create mode 100644 wai/wai-rs/src/key_release/cpix.rs create mode 100644 wai/wai-rs/src/key_release/tests.rs create mode 100644 wai/wai-rs/tests/key_release.rs diff --git a/wai/extensions/key-release.md b/wai/extensions/key-release.md new file mode 100644 index 000000000..d8a5cab73 --- /dev/null +++ b/wai/extensions/key-release.md @@ -0,0 +1,646 @@ +# WAI Extension: Key Release + +> Status: Draft. Adds a second form to the record `wai.video.keys` (SPEC §5): the +> **proxy form**, signed by a **signing proxy** that sits in front of a licence server and +> signs one receipt for every licence request it answers, a release or a refusal. The receipt +> names the encryption **scheme** of each key (ISO/IEC 23001-7's `cenc`, `cbc1`, `cens` and +> `cbcs`; the HLS methods `AES-128`, `SAMPLE-AES` and `AES-256-GCM`), the **key delivery** +> (an HLS key URI, W3C Clear Key, or a content protection system by its system ID) and, +> optionally, the SHA-256 of the **CPIX document** (DASH-IF CPIX 2.3, ETSI TS 103 799) the +> keys came in. The key holder signs a **commitment list**, `WKL1`, derived from the CPIX key +> material: each key's identifier, scheme, key period and commitment. The proxy copies the +> commitments from that list, so it never holds a key. The viewer's entitlement is a record +> its **entitlement authority** signs, `WKE1`, bound to a key the viewer's device holds, and +> every request carries the device's signature over what it asks for. The decision is a pure +> function of signed inputs that a verifier recomputes: entitlement, freshness, replay, key +> period (key rotation), offline licences and their renewals. A key the client receives in +> the clear (an HLS key URI, Clear Key) is never relayed by the proxy: it signs a **release +> ticket**, `WKT1`, which a key server redeems once. The form-1 receipt is unchanged. No new +> transport, and WAI still speaks no licence protocol: a licence travels as its system +> defines it. The reference implementation is the `key_release` module of `wai-rs` (feature +> `key_release`) and the service `wai/wai-key-proxy`; the corpus is +> [`live-conformance/keys/`](../live-conformance/keys/), and its second implementation is +> `live-conformance/key_release_verify.py`. +> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. +> +> Texts read for this revision: the WAI texts this one cites; the DASH-IF CPIX +> Implementation Guidelines in the commit snapshot of 30 September 2024, which states its +> changes against version 2.3.1, for its §6.4.4, §7.4.2, §7.4.7, §7.4.10, §7.4.13 to +> §7.4.17, §8.1 and §8.2 (every element and attribute §4.4 reads is in version 2.3, which ETSI +> publishes as TS 103 799; the per-key `@contentId` that snapshot adds is not read); +> `draft-pantos-hls-rfc8216bis-22` §4.4.4.4 (`EXT-X-KEY`) and §5 (key files); W3C Encrypted +> Media Extensions, Working Draft of 13 October 2026, §9.1 (Clear Key) and its note on +> `encryptionScheme`; the DASH-IF registry of content protection system identifiers; RFC +> 8032; RFC 4648 §5; RFC 9110 §15. The text of ISO/IEC 23001-7 was not available to this +> revision: its scheme types are named here as CPIX §7.4.7, the EME note and the HLS draft +> name them. A section number of one of these texts is that revision's. + +## 1. Scope + +In scope: the encryption schemes and key deliveries a receipt names (§3); the commitment list +and its derivation from a CPIX document (§4); the entitlement (§5); the licence request and +the device's proof over it (§6); the release decision, its offline licences, renewals and key +periods (§7); the proxy form of the `wai.video.keys` receipt (§8); the signing proxy, the +release ticket and the key server (§9); verification of a proxy's log (§10). + +Out of scope: a licence protocol, and the licence a content protection system issues, which +the proxy forwards and never reads (§9.2); how an entitlement authority decides whom to +entitle (its subscription or purchase records); a CPIX document whose content keys are +encrypted (CPIX §8.1), which this revision does not read (§4.4); and a device limit, which is +a separate rule. + +Relationship to the core spec: `wai.video.keys` stays a **record** (SPEC §5). Form 1, the +receipt SPEC §5 registers, is unchanged: its signed bytes, its identity and every receipt of +it stay as they were. This extension adds form 2 (§8). Both are records of Lane A (SPEC §6.5): +they cover the release decision, never a frame the key opens. + +Relationship to [energy-measurement](energy-measurement.md): a form-2 receipt carries the +decision's energy as the OTT receipts do, a figure and its acquisition class, and its identity +is under the identity domain energy-measurement §6.1 names for `wai.video.keys`. + +## 2. Terms + +- **Content key**: a key that encrypts media. Its **KID** is its 16-byte key identifier, in a + CPIX document the UUID of `ContentKey@kid` (CPIX §7.4.7), its 16 bytes in the UUID's order. +- **Key holder**: the party that holds the content keys: the packager or encryptor, or the key + server it exchanges CPIX documents with. It signs the commitment list (§4). +- **Entitlement authority**: the operator's system that decides who may watch what. It signs + entitlements (§5). +- **Holder key**: an Ed25519 key a viewer's device holds. An entitlement names it, and the + device signs each request with it (§6). +- **Licence server**: any server that issues licences for a content protection system. It + is not changed by this extension. +- **Signing proxy**, or **proxy**: the service that decides each licence request, signs its + receipt, and forwards a granted request to the licence server or issues a release ticket + (§9). It holds no content key. +- **Key server**: the service that redeems a release ticket for a key delivered in the clear + (§9.4). It is run by the key holder. +- **Log**: every receipt one proxy signed, in the order it signed them (§9.5). +- **Wrapped delivery**: a delivery whose licence carries the key encrypted for the client's + decryption module (`drm`). **Clear delivery**: a delivery whose response is the key itself + (`hls-key-uri`, `clear-key`). +- Byte fields are big-endian where they are integers; a **string** in a binary layout is its + length as a u32 and its UTF-8 bytes; **base64url** is RFC 4648 §5 without padding. + +## 3. Encryption schemes and key deliveries + +### 3.1 Schemes + +The scheme is what was applied to the media's bytes: + +| code | label | defined by | what it encrypts | key octets | +|---|---|---|---|---| +| 1 | `cenc` | ISO/IEC 23001-7 | AES-CTR, full samples and video subsamples; under HLS, `SAMPLE-AES-CTR` of fMP4 segments | 16 | +| 2 | `cbc1` | ISO/IEC 23001-7 | AES-CBC, full samples and video subsamples | 16 | +| 3 | `cens` | ISO/IEC 23001-7 | AES-CTR, a pattern of video subsamples | 16 | +| 4 | `cbcs` | ISO/IEC 23001-7 | AES-CBC, a pattern of video subsamples with a constant IV; under HLS, `SAMPLE-AES` of fMP4 segments | 16 | +| 5 | `AES-128` | `draft-pantos-hls-rfc8216bis-22` §4.4.4.4 | whole segments, AES-128-CBC with PKCS7 padding | 16 | +| 6 | `SAMPLE-AES` | `draft-pantos-hls-rfc8216bis-22` §4.4.4.4 | samples of segments other than fMP4 (MPEG-2 transport streams, packed audio) | 16 | +| 7 | `AES-256-GCM` | `draft-pantos-hls-rfc8216bis-22` §4.4.4.4 | whole segments, AES-256-GCM | 32 | + +The label is the value CPIX's `ContentKey@commonEncryptionScheme` takes for that scheme (CPIX +§7.4.7: a four-character Common Encryption scheme, or an HLS method). An fMP4 segment that HLS +encrypts with `SAMPLE-AES` is `cbcs`, and with `SAMPLE-AES-CTR` is `cenc`; `SAMPLE-AES` here +names only the other segment formats, so one scheme never has two labels. + +### 3.2 Key deliveries + +| code | kind | how the client gets the key | system ID | +|---|---|---|---| +| 1 | `hls-key-uri` | the key file of an `EXT-X-KEY` URI with `KEYFORMAT` `"identity"` (or none): the key's octets, packed (`draft-pantos-hls-rfc8216bis-22` §5.1). Clear | none | +| 2 | `clear-key` | the JSON Web Key Set of the `org.w3.clearkey` key system (EME §9.1.4). Clear | none | +| 3 | `drm` | a licence of the content protection system the system ID names, which carries the key encrypted for the client's decryption module. Wrapped | 16 bytes: a system ID of the DASH-IF registry, not all zero | + +### 3.3 Which deliveries carry which schemes + +| delivery | schemes | +|---|---| +| `hls-key-uri` | `cenc`, `cbcs`, `AES-128`, `SAMPLE-AES`, `AES-256-GCM`: the methods HLS defines with an identity key file | +| `clear-key` | `cenc`, `cbc1`, `cens`, `cbcs`: ISO/IEC 23001-7, which is what EME decrypts | +| `drm` | all seven | + +A receipt that names a key under a delivery this table does not give its scheme is refused +`scheme_delivery_mismatch` (§8.4). A proxy MUST NOT serve such a key on such a route (§9.1). + +## 4. The commitment list `WKL1` + +### 4.1 Layout + +A commitment list is one content's keys, as the key holder states them. It is these bytes in +this order: + +| field | bytes | +|---|---| +| magic | `WKL1` | +| version | u8, 1 | +| `content_id` | string, not empty | +| `required_entitlement` | string, not empty: the entitlement class the content demands (§7.2 check 5) | +| `cpix` | u8 0, or u8 1 and 32 bytes: the SHA-256 of the CPIX document the keys came in, its exact bytes | +| `issued` | u64: when the list was signed, in seconds since 1970-01-01T00:00:00Z | +| key count | u32, from 1 to 4096 | +| each key | `kid` (16), `scheme` (u8, §3.1), `key_commit` (32), then its period (§4.2) | +| `signer_pubkey` | 32: the key holder's Ed25519 key | +| `signer_id` | string | +| `sig` | 64 | + +`key_commit` is the commitment of form 1, unchanged: BLAKE3 over `wai:video-keys-commit` 0x01, +the key's length (u32) and its octets. A key's period is one byte, then: + +| tag | then | means | +|---|---|---| +| 0 | nothing | the key has no key period | +| 1 | `index` (u32) | a period known by its index only (CPIX §7.4.14) | +| 2 | `start` (u64), `end` (u64) | a period of wall-clock seconds, from `start` up to but excluding `end` | +| 3 | `index` (u32), `start` (u64), `end` (u64) | both | + +### 4.2 Signature and identity + +`sig` is Ed25519 by `signer_pubkey`, verified as SPEC §7 *Signatures* has it, over +`wai:video-keys-list` 0x01 and every byte of the list before `sig`. The list's **identity** is +BLAKE3 over `wai:video-keys-list-id` 0x01 and every byte of the list, `sig` included. + +### 4.3 Rules + +A list holds: its magic and version; each string UTF-8, `content_id` and +`required_entitlement` not empty; the KIDs in strictly ascending byte order; every scheme a +code of §3.1; no `key_commit` all zero; every period tag of §4.1, and `start` below `end`; no +byte after `sig`; and its signature verifies. A list that fails any of these is refused +`list_malformed`, or `list_bad_signature` when only its signature fails. + +### 4.4 A list derived from a CPIX document + +A list whose `cpix` is present states that it is derived from that CPIX document. A verifier +given the document derives a list from it by these steps, refusing the document at the first +that fails, and then refuses the list `cpix_mismatch` when its `content_id`, its `cpix`, or +its keys (each `kid`, `scheme`, `key_commit` and period) differ from the derived ones. Element +names are in the namespace `urn:dashif:org:cpix`, except `Secret`, `PlainValue` and +`EncryptedValue`, which are in `urn:ietf:params:xml:ns:keyprov:pskc`; attributes have no +namespace. + +1. The document is well-formed XML 1.0 in UTF-8, has no document type declaration, and its + root is `CPIX`; otherwise `cpix_malformed`. `cpix` is the SHA-256 of its bytes, as + exchanged. +2. The root has `@contentId`, which is `content_id`; otherwise `cpix_unsupported`. +3. The keys are the `ContentKey` children of the root's `ContentKeyList` child, in document + order; none at all is `cpix_malformed`. For each, in turn: + 1. its `@kid` is a UUID in its canonical text form (8-4-4-4-12 hexadecimal digits, either + case), read as 16 bytes in that order, and no earlier key has it; otherwise + `cpix_malformed`; + 2. it has neither `@contentId` nor `@dependsOnKey`, and its `@commonEncryptionScheme` is a + label of §3.1, which is its `scheme`; otherwise `cpix_unsupported`; + 3. its `Data` child holds a `Secret` child holding a `PlainValue` child; otherwise (an + `EncryptedValue` among them, CPIX §8.1's encrypted key, which this revision does not + read) `cpix_unsupported`; + 4. that `PlainValue`'s text, without its ASCII whitespace, is base64 (RFC 4648 §4) in its + canonical form, and decodes to the scheme's key octets, which are the key; otherwise + `cpix_malformed`. +4. The periods: each `ContentKeyPeriod` child of the root's `ContentKeyPeriodList` child is a + period, known by its `@id`. Each `KeyPeriodFilter` child of each `ContentKeyUsageRule` child + of the root's `ContentKeyUsageRuleList` child, in document order, names a period by + `@periodId` for the key the rule's `@kid` names. A rule whose `@kid` names no key, or a + filter whose `@periodId` names no period, is `cpix_malformed`; a key named for two different + periods is `cpix_unsupported`. +5. Each key named for a period, in ascending KID order, has that period. A period with + `@startOffset`, `@endOffset` or `@duration`, with `@start` and no `@end` or the reverse, with + neither `@index` nor `@start`, with an `@index` that is not a decimal integer from 0 to + 2^32 − 1, or with a time not of the form `YYYY-MM-DDThh:mm:ssZ`, is `cpix_unsupported`. A + period whose `@start` is not below its `@end` is `cpix_malformed`. Otherwise `index` is its + `@index` and `start` and `end` its `@start` and `@end` in seconds since + 1970-01-01T00:00:00Z, each where present. A key named for no period has none. + +The list's keys are the document's, in ascending KID order, each with the `key_commit` of its +key. `required_entitlement`, `issued` and the signer are the key holder's and are not in the +document. A `DRMSystem` element (CPIX §7.4.10) is not read: the delivery is the route's +(§9.1). + +## 5. The entitlement `WKE1` + +### 5.1 Layout + +| field | bytes | +|---|---| +| magic | `WKE1` | +| version | u8, 1 | +| `session` | 16: the playback session or purchase it entitles | +| `holder` | 32: the holder key, an Ed25519 key the device holds | +| `entitlement` | string, not empty: the class it grants, compared byte for byte with a list's `required_entitlement` | +| `not_before` | u64, seconds | +| `not_after` | u64, seconds: the entitlement holds from `not_before` to `not_after`, both included | +| `offline_s` | u64: the longest offline licence it permits, in seconds; 0 for none | +| `renewals` | u32: how many times an offline licence may be renewed; 0 when `offline_s` is 0 | +| `authority_pubkey` | 32: the entitlement authority's Ed25519 key | +| `authority_id` | string | +| `sig` | 64 | + +### 5.2 Signature, digest and rules + +`sig` is Ed25519 by `authority_pubkey` over `wai:video-keys-entitlement` 0x01 and every byte +before `sig`. An entitlement's **digest** is the SHA-256 of all its bytes. An entitlement +holds when its magic and version are these, its strings are UTF-8 and `entitlement` is not +empty, `not_before` is at most `not_after`, `renewals` is 0 when `offline_s` is 0, no byte +follows `sig`, and its signature verifies. One that does not hold, or whose authority the +proxy does not trust, entitles nothing (§7.2 check 1). It is not a refusal of the receipt +that carries it: the receipt is a signed refusal of the request. + +## 6. The licence request + +### 6.1 What a request asks for, and its proof + +A request names: the content (by the proxy's route, §9.1, which gives its `content_id` and its +delivery); its KIDs, from 1 to 64, distinct; its **kind**, `streaming` (1), `offline` (2) or +`renewal` (3); for a renewal, `renews`, the identity of the receipt that released the licence +it renews; the SHA-256 of the request body as the proxy received it (`request_sha256`; of no +bytes for a request without a body); and `request_t`, the device's clock, in seconds. With it +come the entitlement and the **proof**: Ed25519 by the entitlement's `holder` over + +1. `wai:video-keys-request` (ASCII) and 0x01; +2. the entitlement's digest (32); +3. `content_id` (string); +4. the delivery's code (u8), then 1 and its system ID (16), or 0 for none; +5. the KIDs' count (u32) and each KID, in ascending order; +6. the kind's code (u8), then 1 and `renews` (32), or 0 for none; +7. `request_sha256` (32) and `request_t` (u64). + +The proof makes a copied entitlement useless without the device's key, and binds the request +body, the KIDs and the kind to it. Every hash here is SHA-256 so that a browser computes the +proof with the Web Cryptography API. + +### 6.2 HTTP carriage + +A request is an HTTP request to the route's URI (§9.6), with these header fields: + +| field | value | +|---|---| +| `WAI-Key-Ids` | the KIDs, each 32 lowercase hexadecimal digits, separated by commas | +| `WAI-Key-Kind` | `streaming`, `offline` or `renewal` | +| `WAI-Key-Renews` | for a renewal, `renews` in 64 lowercase hexadecimal digits | +| `WAI-Key-Time` | `request_t`, a decimal integer | +| `WAI-Key-Entitlement` | the entitlement's bytes, base64url | +| `WAI-Key-Proof` | the proof's 64 bytes, base64url | + +The body is what the client's decryption module or player sends: a licence challenge, an EME +Clear Key licence request (EME §9.1.3), or nothing for an HLS key file. The proxy answers with +the receipt's identity in `WAI-Key-Receipt`, 64 lowercase hexadecimal digits, and makes the +receipt itself readable at its identity (§9.6). A request with no `WAI-Key-Ids`, or one that +names no KID, more than 64, a KID twice or a KID that is not 32 hexadecimal digits, or a route +the proxy does not have, names nothing to decide: it is answered 400 or 404 and has no +receipt. Every other request has one, whatever else it lacks. + +## 7. The release decision + +### 7.1 Inputs + +The decision of a request is a function of: the entitlement as presented, or none; the +**authorities**, the entitlement authority keys the proxy trusts; the proof; the request's +fields (§6.1); the content's commitment list, or none; `issued`, the proxy's clock when it +decided, in seconds; and the receipts earlier in the proxy's log. Every input is in the +receipt (§8.2) or is a signed record a verifier is given (§10.1), so a verifier recomputes the +decision. + +### 7.2 The checks, in order + +The decision is the first of these that applies: + +| check | decision | code | when | +|---|---|---|---| +| 1 | `DeniedEntitlementInvalid` | 4 | no entitlement was presented; or it does not hold (§5.2); or its `authority_pubkey` is not one of the authorities; or the proof does not verify under its `holder` over the request (§6.1) | +| 2 | `DeniedStale` | 5 | `issued` and `request_t` differ by more than 300 | +| 3 | `DeniedReplayed` | 6 | a receipt earlier in the log carries the same proof | +| 4 | `DeniedUnknownKey` | 7 | the proxy has no commitment list for the content, or a requested KID is not in it | +| 5 | `DeniedNoEntitlement` | 2 | the entitlement's `entitlement` is not the list's `required_entitlement` | +| 6 | `DeniedNotYetValid` | 8 | `issued` is below `not_before` | +| 7 | `DeniedExpired` | 3 | `issued` is above `not_after` | +| 8 | `DeniedPeriodOutside` | 9 | a requested key's period has `start` and `end`, and the period does not meet the entitlement: `start` above `not_after`, or `end` at most `not_before` | +| 9 | `DeniedOfflineNotEntitled` | 10 | the kind is `offline` or `renewal`, and `offline_s` is 0 | +| 10 | `DeniedRenewalUnknown` | 11 | the kind is `renewal`, and no receipt earlier in the log has the identity `renews`, is released, is of kind `offline` or `renewal`, names the same `content_id` and the same KIDs, and carries an entitlement with the same `session` and `holder` | +| 11 | `DeniedRenewalsExhausted` | 12 | the kind is `renewal`, and that receipt's `renewal` plus 1 is above `renewals` | +| 12 | `DeniedRenewalLapsed` | 13 | the kind is `renewal`, and `issued` is above that receipt's `licence_expiry` | +| — | `Granted` | 1 | none of these applies | + +Codes 1 to 3 and their labels are form 1's (SPEC §5): `Granted`, `DeniedNoEntitlement` and +`DeniedExpired` mean in form 2 what they mean there. Check 2 bounds how long a captured request +is good for; check 3 makes it good once. A client that retries a request signs it again. A +refused request is refused whole: a request for several keys, one of which fails a check, is +refused for all of them. + +### 7.3 The licence a release permits + +A `Granted` request of kind `streaming` permits a licence with no stated expiry: its duration +is the licence server's. A `Granted` request of kind `offline` or `renewal` permits an +**offline licence** that expires at `licence_expiry`, the lesser of `issued` + `offline_s` and +`not_after`, and is renewal number `renewal`: 0 for kind `offline`, and the renewed receipt's +`renewal` plus 1 for kind `renewal`. So a licence never outlives the entitlement, a renewal +must come before the licence it renews lapses, and an entitlement bounds how many renewals +there are. A licence that lapsed is not renewed; the device asks for a new offline licence, +which the entitlement must still permit. + +### 7.4 Key rotation + +A key whose list entry has a period is one key of a rotation: the CPIX document holds one key +per period (CPIX §8.2). Check 8 releases the key of a period only to an entitlement that holds +during some of it, so a viewer whose entitlement ends gets no key of a later period, and one +whose entitlement begins gets none of an earlier one. A period known by its index alone has no +time, and check 8 does not apply to it. + +## 8. The proxy form of the `wai.video.keys` receipt + +### 8.1 Forms + +A `wai.video.keys` receipt has two forms: **form 1**, SPEC §5's, unchanged; and **form 2**, the +proxy form, defined here. They sign under different domains (§8.3), so no signature of one is +a signature of the other; they share the key commitment (§4.1) and the identity domain, so a +figure profile of energy-measurement §6.1, `wai:video-keys-receipt-id`, names either. + +### 8.2 Fields and their JSON carriage + +A form-2 receipt is one JSON object (RFC 8259) with exactly these members. A byte field is +lowercase hexadecimal of its exact length, an integer a JSON integer from 0 to 2^64 − 1, and +an absent value `null`: + +| member | value | +|---|---| +| `form` | 2 | +| `content_id` | a string: the route's content | +| `list` | 32 bytes: the identity of the content's commitment list (§4.2), or `null` when the proxy has none | +| `cpix_sha256` | 32 bytes: the list's `cpix`, or `null` | +| `required_entitlement` | a string: the list's, or `null` | +| `delivery` | `{"kind", "system_id"}`: the kind's label (§3.2), and its system ID, 16 bytes, or `null` | +| `keys` | an array, in ascending KID order: each requested key, `{"kid", "scheme", "period", "key_commit"}`, `kid` 16 bytes; `scheme` its label (§3.1), or `null` for a KID not in the list; `period` `null` or `{"index", "start", "end"}`, each `null` where the period has none; `key_commit` 32 bytes or `null` | +| `kind` | `streaming`, `offline` or `renewal` | +| `renews` | 32 bytes, or `null` | +| `entitlement` | the entitlement's bytes, or `null` when none was presented | +| `request_sha256` | 32 bytes | +| `request_t` | an integer | +| `proof` | 64 bytes, or `null` when no entitlement was presented | +| `issued` | an integer | +| `decision` | its label (§7.2) | +| `licence_expiry` | an integer, or `null` | +| `renewal` | an integer from 0 to 2^32 − 1, or `null` | +| `upstream_status` | the licence server's HTTP status, from 100 to 599, or `null` when the request was not forwarded | +| `response_sha256` | 32 bytes: the SHA-256 of the response body the licence server returned and the proxy forwarded, or `null` | +| `released` | `true` when the key was released (§9.2, §9.3), `false` otherwise | +| `joules_micro` | the decision's energy, or 0 unmetered | +| `energy_provenance` | its class's label (energy-measurement §2.7: `ModelBased`, `Estimator`, `OnChipCounter`, `CalibratedInstrument`), or `null` unmetered | +| `energy_uncertainty`, `energy_counter_filtering`, `energy_calibration_ref` | only for a class that carries them, as energy-measurement §2.7 names and writes them | +| `signer_pubkey` | 32 bytes: the proxy's Ed25519 key | +| `signer_id` | a string | +| `sig` | 64 bytes | + +A receipt that does not read so, whose `entitlement` is not hexadecimal, or that carries the +legacy class label `HwShunt` (energy-measurement §2.6), is refused `receipt_malformed`. + +### 8.3 Signing payload and identity + +The **signing payload** is the concatenation of: + +1. `wai:video-keys-proxy-receipt` (ASCII) and 0x01; +2. `content_id` (string); +3. `list`, `cpix_sha256`: each 0 for `null`, or 1 and its 32 bytes; +4. `required_entitlement`: 0, or 1 and the string; +5. the delivery's code (u8), then `system_id`: 0, or 1 and its 16 bytes; +6. the keys' count (u32), then each key: `kid`; its scheme's code (u8), 0 for `null`; its + period, as §4.1 writes one (tag 0 for `null`; tag 1, 2 or 3 as `index`, `start` and `end` + are present); and `key_commit`: 0, or 1 and its 32 bytes; +7. the kind's code (u8); `renews`: 0, or 1 and its 32 bytes; +8. `entitlement`: 0, or 1, its length (u32) and its bytes; +9. `request_sha256`; `request_t` (u64); `proof`: 0, or 1 and its 64 bytes; +10. `issued` (u64); the decision's code (u8); `licence_expiry`: 0, or 1 and u64; `renewal`: 0, + or 1 and u32; +11. `upstream_status`: 0, or 1 and u16; `response_sha256`: 0, or 1 and its 32 bytes; + `released`: u8, 1 or 0; +12. `joules_micro` (u64), then the class in its signed encoding (energy-measurement §2.7), the + tag byte 0 for none; +13. `signer_pubkey`, then `signer_id` (string). + +`sig` is Ed25519 by `signer_pubkey` over the signing payload. The receipt's **identity** is +BLAKE3 over `wai:video-keys-receipt-id` 0x01, the signing payload and `sig`: form 1's identity +domain, over a payload whose first bytes no form-1 payload has. + +### 8.4 Its own rules + +A form-2 receipt holds, on its own: + +1. its signature verifies (`bad_key`, `bad_signature`); +2. `keys` has from 1 to 64 entries, their KIDs strictly ascending; +3. `system_id` is present when, and only when, the delivery is `drm`, and is not all zero; an + `hls-key-uri` receipt has one key; +4. every key whose `scheme` is present has a scheme its delivery carries (§3.3), or the + receipt is refused `scheme_delivery_mismatch`; +5. when `list` is `null`, so are `cpix_sha256`, `required_entitlement` and every key's + `scheme`, `period` and `key_commit`; when it is present, `required_entitlement` is too; +6. `entitlement` and `proof` are both present or both `null`, and when both are `null` the + decision is `DeniedEntitlementInvalid`; `renews` is present when, and only when, the kind is + `renewal`; +7. `released` only with the decision `Granted`, and every key's `key_commit` present when, + and only when, `released`; +8. for a `drm` delivery, `upstream_status` and `response_sha256` are present when, and only + when, the decision is `Granted`, and `released` is `true` when, and only when, + `upstream_status` is from 200 to 299; for a clear delivery both are `null`, and `released` + is `true` when the decision is `Granted`; +9. `licence_expiry` and `renewal` are present when, and only when, `released` and the kind is + not `streaming`; then `licence_expiry` is at least `issued`, and `renewal` is 0 when, and + only when, the kind is `offline`; +10. the figure and its class cohere as energy-measurement §2 has them: `joules_micro` 0 with + no class, or above 0 with a class, a hardware class's declared uncertainty with + `relative_ppm` and `window_us` above 0 and at the resolution floor (`joules_micro` × + `relative_ppm` at least 288 676), and a calibrated instrument's reference holding a + character that is not Unicode White_Space. + +A rule of 2, 3 or 5 to 10 that fails is `receipt_invalid`. Rule 8 means a refused request +was never forwarded, and a released key was released by the licence server's answer or by a +ticket, never by the proxy's word alone. These rules are what a licensor holding one receipt +checks alone; §10 adds what the lists, the authorities and the log let a verifier check. + +## 9. The signing proxy + +### 9.1 What it holds, and what it never holds + +A proxy holds: its Ed25519 signing key; the authorities (§7.1); the commitment lists of the +content it serves; its **routes**, each a name, a `content_id`, a delivery (§3.2) and, for a +`drm` delivery, the licence server's URI, for a clear delivery the key server's; and its log. +It holds no content key and needs none: every `key_commit` it signs is copied from a list the +key holder signed. A proxy MUST refuse to load a route whose list holds a key whose scheme the +route's delivery does not carry (§3.3). + +For each request with a receipt (§6.2) the proxy computes the decision (§7.2) with its clock as +`issued`, and then: + +- a refusal is answered 403 with the receipt as its body (`application/json`), and the request + goes no further; +- a `Granted` request of a `drm` delivery is forwarded (§9.2); +- a `Granted` request of a clear delivery is answered with a release ticket (§9.3). + +The proxy appends the receipt to its log before it answers (§9.5). + +### 9.2 Wrapped delivery: forwarding + +The proxy sends the request body, unchanged, to the route's licence server, with the +request's `Content-Type`, the field `WAI-Key-Ids` as received, and, for an offline licence, +`WAI-Key-Licence-Expiry`, `licence_expiry` as a decimal integer, so that a licence server +configured to read it issues no longer licence. It does not send the entitlement or the proof. +It answers the client with the licence server's status and body, unchanged, and +`WAI-Key-Receipt`. A licence server that gives no HTTP response is recorded as status 502 +with the SHA-256 of no bytes, and the client is answered 502. The receipt binds the status +and the SHA-256 of that body: `released` is `true` when the status is from 200 to 299 (§8.4 +rule 8). The licence carries the key encrypted for the client's decryption module, so the +proxy relays bytes it cannot open. + +### 9.3 Clear delivery: the release ticket `WKT1` + +The response to an `hls-key-uri` or `clear-key` request is the key itself, so the proxy does +not relay it. It answers 307 (RFC 9110 §15.4.8) with `WAI-Key-Receipt` and `Location` set to +the route's key server URI with the query parameter `wai-ticket`, the base64url of this +ticket: + +| field | bytes | +|---|---| +| magic | `WKT1` | +| version | u8, 1 | +| `receipt` | 32: the identity of the receipt that released it | +| `delivery` | u8: 1 or 2 (§3.2) | +| `session_type` | u8: 1, `temporary`, for kind `streaming`; 2, `persistent-license`, otherwise (EME §9.1.1) | +| KID count | u32, then each KID (16), in ascending order | +| `expires` | u64: `issued` plus from 1 to 300 seconds | +| `proxy_pubkey` | 32: the receipt's `signer_pubkey` | +| `sig` | 64: Ed25519 by `proxy_pubkey` over `wai:video-keys-ticket` 0x01 and every byte before `sig` | + +A 307 keeps the method and the body, so a Clear Key licence request reaches the key server as +it was sent, and an HLS key file request is a GET. + +### 9.4 The key server + +A key server holds the content keys of its content (it is the key holder's), the proxy keys it +trusts, and the identities of the tickets it has redeemed. It answers a request carrying +`wai-ticket` with the keys the ticket names only when: the ticket reads as §9.3 has it and its +signature verifies; `proxy_pubkey` is one it trusts; its clock is at most `expires`; and no +ticket with the same `receipt` was redeemed before. It answers an `hls-key-uri` ticket with the +key's octets (`application/octet-stream`, `draft-pantos-hls-rfc8216bis-22` §5.1), and a +`clear-key` ticket with a JSON Web Key Set (EME §9.1.4): `{"keys": [{"kty": "oct", "k", "kid"}, +...], "type"}`, `k` and `kid` base64url and `type` the ticket's session type. Otherwise it +answers 403, or 409 for a ticket already redeemed, and releases nothing. A ticket is a +single-use bearer of one release for at most 300 seconds, carried over TLS. + +### 9.5 The log + +The proxy appends each receipt, as the JSON of §8.2 on one line, to its log, and makes the +append durable before it answers the request. The log is the input to checks 3 and 10 to 12 +(§7.2): a proxy that restarts reads its log back before it answers again, so a replay or a +renewal is decided against every receipt it ever signed. A proxy MUST hold one log, written by +one process at a time. + +### 9.6 HTTP interface + +| method and path | answer | +|---|---| +| `POST /v1/licence/{route}`, `GET /v1/licence/{route}` | a licence request (§6.2) | +| `GET /v1/receipts/{identity}` | the receipt with that identity, `application/json`, or 404 | +| `GET /v1/lists` | the identity, `content_id` and key count of each list the proxy holds | +| `GET /v1/health` | the proxy's key, its route count and its log's length | + +The whole log is an operator's record of sessions: a proxy serves it, as JSON lines, only on a +listener that is not public. + +## 10. Verification + +### 10.1 Inputs + +A key-release verifier is given: the **authorities** and the **proxies**, the Ed25519 keys +trusted to sign entitlements and receipts; the **lists**; for some lists, the **CPIX +document** each is derived from; the **log**, a proxy's receipts in the order it signed them, +from its first; and **tickets**, each with the receipt it names. + +### 10.2 Steps + +1. Each list: §4.3 (`list_malformed`, `list_bad_signature`); two lists for one `content_id` + are refused `list_malformed`. +2. Each CPIX document with its list: §4.4 (`cpix_malformed`, `cpix_unsupported`, + `cpix_mismatch`). +3. Each receipt, in the log's order: + 1. it reads as §8.2 (`receipt_malformed`); + 2. its own rules (§8.4: `bad_key`, `bad_signature`, `receipt_invalid`, + `scheme_delivery_mismatch`); + 3. its `signer_pubkey` is one of the proxies (`signer_unknown`); + 4. its identity is not one an earlier receipt has (`receipt_duplicate`); + 5. its `list`, when present, is the identity of a list given (`list_unknown`) whose + `content_id` is the receipt's; when `null`, no list given has the receipt's + `content_id`; `cpix_sha256` and `required_entitlement` are the list's; each key in the + list has the list's scheme and period, each key not in it has `null` ones, and each + present `key_commit` is the list's (`list_mismatch`); + 6. the decision is the one §7.2 computes from the receipt's fields, the authorities, the + list and the receipts before it, and when `released` and the kind is not `streaming`, + `licence_expiry` and `renewal` are the ones §7.3 computes (`decision_mismatch`). +4. Each ticket: it reads as §9.3 and its signature verifies; its `receipt` is the identity of + a receipt of the log that is released, of a clear delivery, whose `delivery`, kind and + KIDs the ticket's `delivery`, `session_type` and KIDs follow, whose `signer_pubkey` is + `proxy_pubkey`, and whose `issued` is from 1 to 300 seconds below `expires` + (`ticket_invalid`). + +The first step that fails refuses the log, with its reason and its place: `["list", i]`, +`["cpix", i]`, `["receipt", i]` or `["ticket", i]`. + +### 10.3 What a verification states + +An accepted log states, for each receipt: its identity, decision and `released`; for a +released one, each key's KID and `key_commit`, and `licence_expiry` and `renewal` when present. +A licensor holding a content key confirms that a release named it by computing the key's +commitment (§4.1), as form 1 lets it. + +## 11. Reasons + +The codes this extension adds, each with where it is raised: + +| reason | raised at | +|---|---| +| `receipt_malformed` | §8.2, §10.2 step 3.1 | +| `bad_key` | §8.4 rule 1 | +| `bad_signature` | §8.4 rule 1 | +| `receipt_invalid` | §8.4 | +| `scheme_delivery_mismatch` | §3.3, §8.4 rule 4 | +| `signer_unknown` | §10.2 step 3.3 | +| `receipt_duplicate` | §10.2 step 3.4 | +| `list_malformed` | §4.3, §10.2 step 1 | +| `list_bad_signature` | §4.3 | +| `list_unknown` | §10.2 step 3.5 | +| `list_mismatch` | §10.2 step 3.5 | +| `cpix_malformed` | §4.4 | +| `cpix_unsupported` | §4.4 | +| `cpix_mismatch` | §4.4 | +| `decision_mismatch` | §10.2 step 3.6 | +| `ticket_invalid` | §10.2 step 4 | + +A refused entitlement, a stale or replayed request and the other outcomes of §7.2 are not +reasons: each is a decision, signed in a valid receipt. + +## 12. Conformance + +The corpus is [`live-conformance/keys/`](../live-conformance/keys/): logs of a proxy over +CPIX-derived lists, each with the outcome its verification reaches (§10). It gives releases +over each delivery; refusals of each decision of §7.2; an expired entitlement; key rotation +over three periods; offline licences, their renewals and their lapse; a licence server that +refused a granted request; and negative controls that each verification refuses with a reason +of §11. Its second implementation, `live-conformance/key_release_verify.py`, is written from +this text and shares no code with `wai-rs`. + +A conforming proxy computes every decision §7.2 computes, signs every receipt §8 describes and +none it does not, forwards no refused request, relays no clear key (§9.3), and holds its log as +§9.5 has it. A conforming verifier reaches the outcome, reason and place the corpus states for +every log. + +## 13. What a receipt does not prove + +- **A licence's contents.** For a `drm` delivery the receipt binds the SHA-256 of the licence + the licence server returned, which the proxy cannot open. A licence server that puts in a + licence a key the request did not name, or a longer duration than `licence_expiry`, is not + caught by the receipt: the proxy forwards `WAI-Key-Ids` and `WAI-Key-Licence-Expiry` so that + a licence server can be configured to follow them. +- **The kind of licence.** The kind is the device's signed statement (§6.1). A licence + challenge asking for a persistent licence under a `streaming` request is the device's + misstatement, and an operator that issues persistent licences only through routes whose + licence server issues nothing else closes it. +- **The clock.** `issued` is the proxy's clock and `request_t` the device's. Check 2 bounds + their difference; neither is a trusted time. +- **The redemption.** A released clear-delivery receipt records that a ticket was issued. The + key server's record of what it redeemed is its own. +- **That the list is right.** The proxy copies its commitments from the key holder's list, and + a verifier with the CPIX document checks that list (§4.4). Without the document, the list is + the key holder's signed statement. diff --git a/wai/live-conformance/keys/README.md b/wai/live-conformance/keys/README.md new file mode 100644 index 000000000..6e5da8ec4 --- /dev/null +++ b/wai/live-conformance/keys/README.md @@ -0,0 +1,56 @@ +# live-conformance/keys — key release + +The corpus of [`extensions/key-release.md`](../../extensions/key-release.md) §12: logs of a +signing proxy, each over commitment lists (`WKL1`) derived from CPIX documents, with the +outcome its verification reaches (§10). + +**Log equivalence.** For each vector, a conforming key-release verifier given its +`authorities`, `proxies`, `lists` (each named in the top-level `lists`, as hexadecimal of its +bytes), its `cpix` documents (each the file `file`, with the index in `lists` of the list it is +derived from), its `log` (form-2 `wai.video.keys` receipts in the JSON of §8.2, in the order +the proxy signed them) and its `tickets` (`WKT1`, hexadecimal) reaches the outcome the vector +states: + +- `refuse`: the `reason` (§11) and where, `at`: `["list", i]`, `["cpix", i]`, `["receipt", i]` + or `["ticket", i]` (§10.2); +- `accept`: for each receipt (§10.3), its `identity`, `decision` (§7.2) and `released`; the + `keys` it released, each its `kid` and `key_commit`; and its `licence_expiry` and `renewal`. + +A verifier that reaches another outcome, reason, place or fact does not conform. + +The accepted logs give: streaming releases over two content protection systems, forwarded and +answered (`release_drm`, one receipt carrying a metered figure with its class); an HLS key +file and Clear Key releases by ticket, the key never relayed by the proxy +(`release_hls_key_uri`, `release_clear_key`); every refusal of §7.2 checks 1 to 7 (`refusals`, +`expired_entitlement`); key rotation over three periods (`rotation`); offline licences, their +renewal, their count, their lapse and their clamp to the entitlement (`offline`); and granted +requests the licence server refused or did not answer (`upstream_refused`). Each `n_` vector +is a negative control, refused with a reason of §11; together they give every reason. + +**Provenance.** Generated here: `wai_key_release_vectors gen` writes every file from logs +authored in code, from fixed seeds. Each principal's Ed25519 key (the key holder, the +entitlement authority, an untrusted authority, the proxy, an untrusted proxy, two devices) is +SHA-256 of a fixed label; each content key, KID and session is SHA-256 of a fixed label, +truncated. Every time is a constant (the corpus's `t0` is 2026-10-25T00:00:00Z), every origin +is an `.example` name (RFC 2606), the three system IDs are values of the DASH-IF registry of +content protection system identifiers, and Ed25519 signing is deterministic (RFC 8032), so +every file is the same bytes on every machine. The CPIX documents are written for the corpus +with their keys in the clear (CPIX §8.1 calls that not recommended outside a test); the +encrypted one holds a cipher value that is SHA-256 of a fixed label and decrypts to nothing. +The licence challenges and licences are fixed strings, never a content protection system's +messages. The generator asserts that each vector reaches the outcome it was written to have, +and that the corpus gives every reason of §11 and every decision of §7.2, before it writes. No +recorded data. + +## Layout + +- `vectors.json`: `t0`, `principals` (the keys, by name, informative), `lists` and `vectors`. +- `film.cpix.xml`, `episode.cpix.xml`, `channel.cpix.xml`: the CPIX documents the lists are + derived from (`channel` with three key periods); `encrypted.cpix.xml`, a document whose key + is encrypted (`cpix_unsupported`); `broken.cpix.xml`, one that is not well-formed + (`cpix_malformed`). + +The second implementation is [`../key_release_verify.py`](../key_release_verify.py), written +from the extension's text and sharing no code with `wai-rs`: + + python3 -I live-conformance/key_release_verify.py live-conformance/keys diff --git a/wai/live-conformance/keys/broken.cpix.xml b/wai/live-conformance/keys/broken.cpix.xml new file mode 100644 index 000000000..2fd8fb810 --- /dev/null +++ b/wai/live-conformance/keys/broken.cpix.xml @@ -0,0 +1,25 @@ + + + + + + + 0Q8zPAA6i4wgX+a5b7oUjw== + + + + + + + 5U1XQBhOhBWn81IZnv8BOg== + + + + + + + + + + + diff --git a/wai/live-conformance/keys/channel.cpix.xml b/wai/live-conformance/keys/channel.cpix.xml new file mode 100644 index 000000000..1c8790a13 --- /dev/null +++ b/wai/live-conformance/keys/channel.cpix.xml @@ -0,0 +1,47 @@ + + + + + + + sJTXjDHzwf+3WnBeszMSBg== + + + + + + + yAfs90wJAKLYnxYK+n+vaA== + + + + + + + bt8nqfo+UDux+Y9mQypQ2w== + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wai/live-conformance/keys/encrypted.cpix.xml b/wai/live-conformance/keys/encrypted.cpix.xml new file mode 100644 index 000000000..aeafbabe2 --- /dev/null +++ b/wai/live-conformance/keys/encrypted.cpix.xml @@ -0,0 +1,17 @@ + + + + + + + + + + mcX/fRozqUQSZMFtO7Cj6EIa8A8tdoAsxJaurN7c5XA= + + + + + + + diff --git a/wai/live-conformance/keys/episode.cpix.xml b/wai/live-conformance/keys/episode.cpix.xml new file mode 100644 index 000000000..96a400426 --- /dev/null +++ b/wai/live-conformance/keys/episode.cpix.xml @@ -0,0 +1,12 @@ + + + + + + + bn7NbKC3U3aWcQPoRd8pzQ== + + + + + diff --git a/wai/live-conformance/keys/film.cpix.xml b/wai/live-conformance/keys/film.cpix.xml new file mode 100644 index 000000000..75a4e69e8 --- /dev/null +++ b/wai/live-conformance/keys/film.cpix.xml @@ -0,0 +1,25 @@ + + + + + + + 0Q8zPAA6i4wgX+a5b7oUjw== + + + + + + + 5U1XQBhOhBWn81IZnv8BOg== + + + + + + + + + + + diff --git a/wai/live-conformance/keys/vectors.json b/wai/live-conformance/keys/vectors.json new file mode 100644 index 000000000..7fce53df0 --- /dev/null +++ b/wai/live-conformance/keys/vectors.json @@ -0,0 +1,4279 @@ +{ + "corpus": "wai key-release (extensions/key-release.md)", + "t0": 1792886400, + "principals": { + "key_holder": "49c98facbfab8b9f7118eff23e6a007e654c1950aa52a5db167ab198578c75bf", + "authority": "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9", + "rogue_authority": "3fbede6ff0cca9fd75b2007d071b21d21eec451d83903e3f7ed8a302a587e88c", + "proxy": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "rogue_proxy": "52bd078cfccda2bf71bd23625655bd700e5dd79294b2fc49b1493946502b17a6", + "device_a": "4a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c", + "device_b": "f978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c" + }, + "lists": { + "channel": "574b4c31010000000c6368616e6e656c2d303030330000000b6c6976653a73706f7274730189b2fdaed4db112bcdd95daeee74bd45c21c973ab29fc982e7c1c612d258b12d000000006adbf5000000000301e0a684e472167eb200c53a878e6f6f04b3a076ac6a926606ec7da35856276e5cb2f58d9743721339241a975ee2630c9b0300000003000000006add62a0000000006add70b08d1504c7e59d94629fed63ad7e27b44a047693c56d35ba7f1cd0f5448b76fe00a448b5646f0fda98925316195506c8d3cc0300000002000000006add5490000000006add62a0924698c359ad3e890d5be75bed61d6c204a039736b904f04172680956481844d7a0027423b117003e5c3f45a079da23b300300000001000000006add4680000000006add549049c98facbfab8b9f7118eff23e6a007e654c1950aa52a5db167ab198578c75bf0000001868747470733a2f2f7061636b616765722e6578616d706c65b630508f8215925c2eb8c45568c46669f7506bc23f7c7e97f2ab2343d6b357330e71e8bb77b4f046369e99ddd3987f3b0447afaef917568ae2aef9c981ed5c03", + "clip": "574b4c310100000009636c69702d303030340000000a73766f643a626173696300000000006adbf5000000000116e7de4b348c4707987e397d76edf209032b9b516a3a3146e4c166ae110eb1b2f000bfef1ac5e9b8b2b7b035f4229316320049c98facbfab8b9f7118eff23e6a007e654c1950aa52a5db167ab198578c75bf0000001868747470733a2f2f7061636b616765722e6578616d706c659c3de89b5d0ffa3cd311f595947cd913b751561e45f7d7c0c284d8906b0cb0ed14392c874d8a400c0e43c7b730bd48bb5f69d22532f24735ab62f18fb493830c", + "encrypted": "574b4c31010000000966696c6d2d303030350000000c73766f643a7072656d69756d01105c462595060c7e4e59cbb4eb17f4182b7dbacfb9711c8405ffe833cfba5e7d000000006adbf5000000000104b51c5b3f9abc207a9747eeb105b31701e61d475c81445bce27229b98960a661438c524221c15ba1f8ed23547af9acac90049c98facbfab8b9f7118eff23e6a007e654c1950aa52a5db167ab198578c75bf0000001868747470733a2f2f7061636b616765722e6578616d706c654b4fb09c00374687053c8d32bec47576360edf88d58e55a6df58b4a6cae343470d7fcbfff810137d9da775da9dfd85b2671e20a26e6e653af1a576fac059a409", + "episode": "574b4c31010000000c657069736f64652d303030320000000a73766f643a626173696301da0ad96e5be3cdb14613365ce2779c1cb890dda912952e9ff5cd28cb88cde475000000006adbf500000000016bd9096f4700ad0bd872027c08d6d44405468f4506e4bac9d7d02021c64ddec49f9e144fa6098515073e64e476b81e6b3e0049c98facbfab8b9f7118eff23e6a007e654c1950aa52a5db167ab198578c75bf0000001868747470733a2f2f7061636b616765722e6578616d706c6572531454b2700fa375a9bf4279454b9af1c753e49c4d02af11941d2bc54daa3a85e4b7a127c47e7902113556dadeee52ecd84c7f1e60f3b420891211bedb470b", + "film": "574b4c31010000000966696c6d2d303030310000000c73766f643a7072656d69756d011841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e000000006adbf500000000027f2f1512416114229ded99cecd52be7601e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74008c92350e5042656d2d7656d7afbdf00b0146ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d940049c98facbfab8b9f7118eff23e6a007e654c1950aa52a5db167ab198578c75bf0000001868747470733a2f2f7061636b616765722e6578616d706c653e38959ab889dbe449200b727c839755e86ab46d8d7a2f08685ae257fe80ec03a4160312423bc127c9a644f3e65faaf61f98db2e4b93be2412ad26c28434da03", + "film_forged": "574b4c31010000000966696c6d2d303030310000000c73766f643a7072656d69756d011841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e000000006adbf500000000027f2f1512416114229ded99cecd52be7601e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74008c92350e5042656d2d7656d7afbdf00b0146ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d940049c98facbfab8b9f7118eff23e6a007e654c1950aa52a5db167ab198578c75bf0000001868747470733a2f2f7061636b616765722e6578616d706c653e38959ab889dbe449200b727c839755e86ab46d8d7a2f08685ae257fe80ec03a4160312423bc127c9a644f3e65faaf61f98db2e4b93be2412ad26c28434da02", + "film_tampered_commit": "574b4c31010000000966696c6d2d303030310000000c73766f643a7072656d69756d011841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e000000006adbf500000000027f2f1512416114229ded99cecd52be7601ef0eb68444322836f7d190e56ac500466495b001f908290fb258193c9f5d4488008c92350e5042656d2d7656d7afbdf00b0146ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d940049c98facbfab8b9f7118eff23e6a007e654c1950aa52a5db167ab198578c75bf0000001868747470733a2f2f7061636b616765722e6578616d706c651f28f84dd2db6b87949d5f81e13e576d6a5f5db287ae8fb984781ae94dbd375ecd8a66f9b552aecb42f65fc7e29c018cdbeeaf67653d3dab93d406aacd220905", + "film_trailing": "574b4c31010000000966696c6d2d303030310000000c73766f643a7072656d69756d011841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e000000006adbf500000000027f2f1512416114229ded99cecd52be7601e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74008c92350e5042656d2d7656d7afbdf00b0146ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d940049c98facbfab8b9f7118eff23e6a007e654c1950aa52a5db167ab198578c75bf0000001868747470733a2f2f7061636b616765722e6578616d706c653e38959ab889dbe449200b727c839755e86ab46d8d7a2f08685ae257fe80ec03a4160312423bc127c9a644f3e65faaf61f98db2e4b93be2412ad26c28434da0300", + "film_wrong_digest": "574b4c31010000000966696c6d2d303030310000000c73766f643a7072656d69756d01da0ad96e5be3cdb14613365ce2779c1cb890dda912952e9ff5cd28cb88cde475000000006adbf500000000027f2f1512416114229ded99cecd52be7601e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74008c92350e5042656d2d7656d7afbdf00b0146ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d940049c98facbfab8b9f7118eff23e6a007e654c1950aa52a5db167ab198578c75bf0000001868747470733a2f2f7061636b616765722e6578616d706c656e97f4e54e96ec5a681165b107882e6817ceefa41eb12453898955c573db9cf67e79610d4bddf30de95bae364a843bacb89dc4c17e12021003a5efe0f8836108" + }, + "vectors": [ + { + "name": "release_drm", + "about": "streaming releases of the film's keys through two content protection systems, forwarded and answered 200; the second receipt carries a metered figure", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [ + { + "list": 0, + "file": "film.cpix.xml" + } + ], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "b851e0321933fce99a44c3aa7ea3fe5a2c7669e3c1e25c601003bc62da25aa21", + "request_t": 1792886500, + "proof": "ccb70cbdb080a589cf26c3fdbac2d21d0b195c12cab49c3a90c172527e6898f45d86393f8255e56943eebd28667871a518c2194d4301f9bdafd26beac06fe60d", + "issued": 1792886501, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "52321853be4abb3e25a435a95e54ddb91cebd8d200ff64c571147fef166b1e14", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "83c67eb4665baf92b4c6803c645ba14d778c87b35011e7e8328f074911ec9a8ffb30636d78cf1a0aa3b9cea057e3b4d4d95feb742534f1861c6ae5420a0a010c" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "9a04f07998404286ab92e65be0885f95" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "048bd957e1a53f8cc4d52257b623b0fdc0f1c080861a346b1654e6cc7cd1b253", + "request_t": 1792886600, + "proof": "934cf98612694049137feda3c56a8be90cd663a0c8a9ceabde0126e747ba26f8a79b1b857d4e6f85df482dc9f0cf7e6c8446aef58b19df416cf22377fc6d9009", + "issued": 1792886600, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "e3f0d5b30aeb42d9b848c3f426fe2eed3cdf154e16892b1ba566d4d8e7eb6dd3", + "released": true, + "joules_micro": 1200, + "energy_provenance": "OnChipCounter", + "energy_uncertainty": { + "relative_ppm": 250000, + "window_us": 1000 + }, + "energy_counter_filtering": "Off", + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "8e2136f4e59ad365a7a0bc761ab15e8e933f9e15810bd5d8c107ef87321bdfcd85416ed0ce4923643d3fd90be3c6c44dfc1149ad76765ec5e2d3a86347731c09" + } + ], + "tickets": [], + "outcome": "accept", + "receipts": [ + { + "identity": "880152c5d1aee6a790ce838250807b03234ec49251bca45ba33db95f67fc9df5", + "decision": "Granted", + "released": true, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "fe8abec5ac274d8ac31c150cf70784a619ce9fd7be7b6ad04a0a1dcb9f0ab9cc", + "decision": "Granted", + "released": true, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "licence_expiry": null, + "renewal": null + } + ] + }, + { + "name": "release_hls_key_uri", + "about": "an HLS AES-128 key file released by a ticket the key server redeems; the proxy never relays the key", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "episode" + ], + "cpix": [ + { + "list": 0, + "file": "episode.cpix.xml" + } + ], + "log": [ + { + "form": 2, + "content_id": "episode-0002", + "list": "7419666222736906d75b2e5da0968692ab56a4d0a457afe800bd6d2ef9866c36", + "cpix_sha256": "da0ad96e5be3cdb14613365ce2779c1cb890dda912952e9ff5cd28cb88cde475", + "required_entitlement": "svod:basic", + "delivery": { + "kind": "hls-key-uri", + "system_id": null + }, + "keys": [ + { + "kid": "6bd9096f4700ad0bd872027c08d6d444", + "scheme": "AES-128", + "period": null, + "key_commit": "468f4506e4bac9d7d02021c64ddec49f9e144fa6098515073e64e476b81e6b3e" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b4531010f6f2b67a9d9238b5bbdd09715028fdcf978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000a73766f643a6261736963000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c6534c9c72c6e06c075992ed9ec37d6ab705f30be5939c3ec21c96252c63fdb495e792e1a5627e5b2e80082a235c5b5900a685b20a99203e2e2af5f51357388030d", + "request_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "request_t": 1792886700, + "proof": "1d8b7fe581f75eec5efa604822ec7d607b2a5d5f847fcb864bc7eb8eeadb99cf96f8e8e841d787b4878dd54b3126df988e765900e6afbc1c71bc8f78be368b09", + "issued": 1792886701, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "4608631dd88e24aa83b097439fde628c164c1c2469994b18c0d6492364788b54fd1009aaa669362b9aa6934944e3499f4cb2124a8ac4159a61668262a5a9e107" + } + ], + "tickets": [ + "574b5431010480815f5436c8e8cbf85ec25a8f756fdf68c2331259c4a246f45f333e0c79910101000000016bd9096f4700ad0bd872027c08d6d444000000006add47e927ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1d6e23d1a7b80e0d3efe8ecf9d3a94a7455da89e7ca5d8f1aeda3cc811d8738554b8527eb1911e1b0bb6a4da6587f48d534fe32b82b6782e4fb780c8625166609" + ], + "outcome": "accept", + "receipts": [ + { + "identity": "0480815f5436c8e8cbf85ec25a8f756fdf68c2331259c4a246f45f333e0c7991", + "decision": "Granted", + "released": true, + "keys": [ + { + "kid": "6bd9096f4700ad0bd872027c08d6d444", + "key_commit": "468f4506e4bac9d7d02021c64ddec49f9e144fa6098515073e64e476b81e6b3e" + } + ], + "licence_expiry": null, + "renewal": null + } + ] + }, + { + "name": "release_clear_key", + "about": "Clear Key releases by ticket: a temporary licence of two keys and a persistent licence of one", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [ + { + "list": 0, + "file": "film.cpix.xml" + } + ], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "clear-key", + "system_id": null + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "1731fdf8e567e6bc473cd6eb2f4e91932ba435b3d29fd8cbac9202afeb7b8f7b", + "request_t": 1792886800, + "proof": "3b008a62d2acbee4183a55588fe1591ed90192c15713fb6796c86a3a060d1bc4594742336574b31e4a3e8e7a5edc62589b6f9d1b67e28f1b352dedbddc6faa0d", + "issued": 1792886800, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "56867c9042421cd21e2ee4ff4b826ed31b90aa7c135b7dbbe90e5d12943826d96a2577e565d9c41931563675c756e0f783a36445439b2b9303294e1e65ccd00c" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "clear-key", + "system_id": null + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "offline", + "renews": null, + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "42d28a128717e2af96a1b3d4586e08f8aa413439b13a3e35bc86557ef097aa4e", + "request_t": 1792886900, + "proof": "779f5cdb6176f8ba46bc41c6c8d5cb197e2848639c8685e7fab1ef3ada7fe719ae212f8f71b60a660aa6765acce44ba51e89068af565a1a399fd0deeb463e20d", + "issued": 1792886900, + "decision": "Granted", + "licence_expiry": 1793059700, + "renewal": 0, + "upstream_status": null, + "response_sha256": null, + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "c3290e3491da448147002dceec30bcdc1841a5c3541c9819dd4b548ed995521e5c47d8c8f938ed7f55257a23ec688fea0bf28683efe0ea7f49cae90ebb18b904" + } + ], + "tickets": [ + "574b54310167b68d9bc84f0d299d696848a9b8f78e65f42369fb320ab426bed7efc5cfca710201000000027f2f1512416114229ded99cecd52be768c92350e5042656d2d7656d7afbdf00b000000006add484c27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1cbf3ef317b429ea508ac956ff23b5effa864a714624af79e76b11c8f24a14eb4bd13c8083ae604ebacde178da77ad40fe31cd1dc370759fc1645ff68c390e90d", + "574b54310190e1e1fc6e8617aa3f1e9b327d526313ad49f899e5e44875f4ffdfae4f5d42e00202000000018c92350e5042656d2d7656d7afbdf00b000000006add49a027ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d15c3ffccbdf5dea05c0c8e48c16be395aa230bb7c22dbc3fa4a06e2ee2c911e183082a185ec180cf81d7babe7dacee90ebaa5d13b7a801aee62fedbaf8b38e80d" + ], + "outcome": "accept", + "receipts": [ + { + "identity": "67b68d9bc84f0d299d696848a9b8f78e65f42369fb320ab426bed7efc5cfca71", + "decision": "Granted", + "released": true, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "90e1e1fc6e8617aa3f1e9b327d526313ad49f899e5e44875f4ffdfae4f5d42e0", + "decision": "Granted", + "released": true, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "licence_expiry": 1793059700, + "renewal": 0 + } + ] + }, + { + "name": "refusals", + "about": "signed refusals: no entitlement, an untrusted authority, another device's proof, a stale request, a replay of a granted one, an unknown key, unknown content, the wrong class, and an entitlement not yet valid", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": null, + "request_sha256": "ba718264615af763af9d246f87e5cc8ea4600804a1719069744378b26f6fe5e1", + "request_t": 1792887000, + "proof": null, + "issued": 1792887000, + "decision": "DeniedEntitlementInvalid", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "ffe3772ca5cf0e5940fc05131b5a414775b47fef508f6355fda7d5cc5af0d4699e0bf096836be232688d6a7cbf31e38681a86bba2c94110560f8e6250e974b05" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b45310140334b75e54044627f196c5b9ead194e4a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d3800000000000000000000000003fbede6ff0cca9fd75b2007d071b21d21eec451d83903e3f7ed8a302a587e88c0000001568747470733a2f2f726f6775652e6578616d706c656970d746f081c7902d6009eec0f4b53f774e7e27c60ea6960095fc40f600027ff86649dc61795402f9ecd0c3543d7c8ebb3935c2065b5093ef91b1a1558bf90c", + "request_sha256": "d4a67cbd55810ba8bdfc05780e16d6d057640311d6ab2fbc7d1ae5425ea7e271", + "request_t": 1792887010, + "proof": "1de816bb3e431eed0c3a3745fedd373bb506492e232cee6206d2205bde50ddb1fbd40d0f1b8d67a48b978f28437aa723b92376d6bed0f5283986781d1be8de05", + "issued": 1792887010, + "decision": "DeniedEntitlementInvalid", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "a476ba1832f38540ad7d92bdb5c370c7cf1b2ac99d7e933b99abd1665dc6df177d2a43bdf7ed05fa1845fd15afe76b15a37381310a42b7f07ae7c68a950c8a06" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "9b2033f53d175649c69fbf23156f027856bac920cb8a470f83fdfc05d391ad67", + "request_t": 1792887020, + "proof": "56c38ad68fd25fca2ac258a1cbeede721e43056d845b844c7505015ba7b090515c8910e5144dd444c0d437a258f82d190f39bfdb70ca26a01c04b4a1e1e8e303", + "issued": 1792887020, + "decision": "DeniedEntitlementInvalid", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "7d6b569f66bb5c0d496ecc837e87693e027c157486cb12c129dc2b05e9812b0cd34f7042fb6ad5fc4ebda75bc7dd5bc13c7fb083140d31f5061f9f71a255380d" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "d0f5c0adda9675326ef3c0bd4c2839d8cf041e779d0fcd22063982bed06ad45f", + "request_t": 1792886730, + "proof": "8e6fb18da84e81528e92a657d6d4af1657ba8fbbd920992a36d57f1d161e66daf23bb9003890e2aa7a28cc46aaaa20c734b1b4059fcfe2bfd599309d1f5f9606", + "issued": 1792887031, + "decision": "DeniedStale", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "a6212386b2c21a909168ef56070ebbcd5a0524f10141c1c0079c0ecf6a642cbaf8d641f6aaf12b4115d8577a3db28c991ae1018e1d443c7f72e4e583bc768906" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "89bbc902799db02f04c18f5b0af5a8f8e06e4477e7087c08c961e7590411e789", + "request_t": 1792887040, + "proof": "f98211f6a41d6e54202f58b714450ae7467621c2e6cdae663c2b8c05fc0c121b87e6fc7af824d085f58321105c68c2056a9335a06e2326e47aacfab1d58bc701", + "issued": 1792887040, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "8178ac72b28d77fcb851fcd301583182bd05d9663ed2ec8131e60f057c2795f7", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "d4cef37183e7b501bb8605244c2746f5c03d0b2da65585dceb3511d1564618223fcacb083281bb2cb07b00246732a8e37bff439c3fdea57819fbcc7de1f93c05" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "89bbc902799db02f04c18f5b0af5a8f8e06e4477e7087c08c961e7590411e789", + "request_t": 1792887040, + "proof": "f98211f6a41d6e54202f58b714450ae7467621c2e6cdae663c2b8c05fc0c121b87e6fc7af824d085f58321105c68c2056a9335a06e2326e47aacfab1d58bc701", + "issued": 1792887045, + "decision": "DeniedReplayed", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "cc03a59184ca82f697f7d109e8b86039ebccaa9a45f015ca4131aa4a0d3a1ee03549d8607b3d2b7f2d2cb5844cef96ac86aadd46b069ae21fbf40ec16c52690a" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8bcc435d9d47cda1c3586404d1ec666b", + "scheme": null, + "period": null, + "key_commit": null + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "593c9a4fecb9d7e8866ecff74892eaa7768216409f83039642c16d0ce15c98f5", + "request_t": 1792887050, + "proof": "02276ca08ed41005ade7e03d1414b60b3d43fc3352b16af70066f3e16343e8c7a1ed187fb74b7b734c17923d560d0624d8da4a5268af380b161d5851105ce60e", + "issued": 1792887050, + "decision": "DeniedUnknownKey", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "0f001dac17873d235547e0bd2d9115c66c64cccb06de83c3e0e82f93c22b58c5f4397e122491089a9ec3d7c6da13d1dd68a1e16bda0c864987bf7df8efb5cf0a" + }, + { + "form": 2, + "content_id": "film-9999", + "list": null, + "cpix_sha256": null, + "required_entitlement": null, + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": null, + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "c8c32b7f2d8fb656bad75c031e0634ef95583c42eca2fc323bea76e551c3ae5c", + "request_t": 1792887060, + "proof": "9c0fa48a29b1ecabe053188087dba0828eedbd47837e09fe4b734ba9da6f07d685485fe56ac006960ae764c0bd0ad309d7a1343bc3fbb4d838720bcf2a935304", + "issued": 1792887060, + "decision": "DeniedUnknownKey", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "0e460c12e4746b8e824ce2ea70e9dba3469b859fb227c0c2b5e34a89bf7c86bdfadaf8773ec4df575f14b96438e08c0498b7b3a4a0fadbfa7864706d27ec7f0d" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b4531010f6f2b67a9d9238b5bbdd09715028fdcf978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000a73766f643a6261736963000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c6534c9c72c6e06c075992ed9ec37d6ab705f30be5939c3ec21c96252c63fdb495e792e1a5627e5b2e80082a235c5b5900a685b20a99203e2e2af5f51357388030d", + "request_sha256": "5fe71444dc9a62020d463d52b39070dc7266456887132dbe3e900255acbe18a4", + "request_t": 1792887070, + "proof": "3d8b211e6748ebc2f9bb27923609606e7a7fe1b23cd5de94149842f4a40a9e5940ed53e01b9cc1ebf653da2f1a2288193eeedee0ea5de0f08488a4ced746770e", + "issued": 1792887070, + "decision": "DeniedNoEntitlement", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "f304c8c21cb316d5eb9f5ea94ad9767ece78b21ab896de7d22a88dbdb123b8c9d36ce044fbc992dec5923a0adf7ab4659a9dc87554b0656c0edb3ebcfedd6f07" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b45310185ebd301c7a8ccda976e3a16ad3233a34a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006ade9800000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65004dcf60d14d5b0cf677b872dcf7dd7517d7a8d963ab78325293665b5de264c750984cabe75e3bc680eea0204118ee41b645544092f84af248a3c468f26f8f0a", + "request_sha256": "cae93d2b47d76f58e476bd2d447228038adb616d70a811f8873e8bdf7f26063d", + "request_t": 1792887080, + "proof": "70b3ff7dd50785e15319e3e0ed347e2babc065ebd3de8e01bb1c9cd7ef3c0ee245a253950b930ad1f1f51a075bac5aa37bd207bc50f4e08a900da65d5064750e", + "issued": 1792887080, + "decision": "DeniedNotYetValid", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "3b86490f48185bd147c922e063c5ee62e1bbe359db7750efe33c54b62f5d676659b5f8aeb0bf5aa9cb45e1b16e3d84246f56767181fb2a0963e598c0fd3e070a" + } + ], + "tickets": [], + "outcome": "accept", + "receipts": [ + { + "identity": "7b69dfd1d36a5d6ce0eacd310ebee0f0a40802fa625a48932c31d89de25a1bc6", + "decision": "DeniedEntitlementInvalid", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "40c9747db0b7426c88857dd31d1527f95981bfa3548e1309032b8c20903c7ebf", + "decision": "DeniedEntitlementInvalid", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "cebb539102cee7e6ff4bbdede18466f8f12555ffc77847386f8997785d2b9e3f", + "decision": "DeniedEntitlementInvalid", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "347337d4863907ba391b8626550e909a0cf8260b26a657661c6af9a3730e8e67", + "decision": "DeniedStale", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "26aae767cf1388bc8d1a87dc9b1eda942721e67c7bab4fc8385241b0afaeed7d", + "decision": "Granted", + "released": true, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "88c176241edffc72ab85218f3588394b67eba761b735855757a4456f15b4aaa8", + "decision": "DeniedReplayed", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "4a0a6e556085f12357e7f040314f628665dd32ad357b6732cf88b50256da8c7d", + "decision": "DeniedUnknownKey", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "5548faad8eb44207795e1b81de308b9c14554590ac5cb238f8da5bf53b0e4f05", + "decision": "DeniedUnknownKey", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "a459770e09e3e02d31645fae9b26581672c99442a76ec2af1b5a533b8a398659", + "decision": "DeniedNoEntitlement", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "92320a31d325ff12fd7ae56b8fdcf0823585fddae95dfa86f540106d9448bc93", + "decision": "DeniedNotYetValid", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + } + ] + }, + { + "name": "expired_entitlement", + "about": "a streaming request under an entitlement that has ended, and a renewal asked after the entitlement it renews under ended", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [ + { + "list": 0, + "file": "film.cpix.xml" + } + ], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b45310157c4cc51a11b544f7ce43855a2c44aa24a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006ab5b980000000006adbf50000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c651070f0ec4434abf22f64b59c4413fbc67fd7237049b8569ba8f9a50e412b93cd5c117121102b729fb6b85fd0bdd6b0f6d87b7616fe2ff37fee31fcff6c14e209", + "request_sha256": "c4610d25ccac38a3aa8761f933a44968a24a3ec95cab7bab535c57329c71a70a", + "request_t": 1792887100, + "proof": "369c89327da23f175948f87a01ccc3b2a2d80eeac28dc0cb1d869d468ab3c29c3e17bb56950d927e0c4aba08b1089f7eb91a132708e2eda3e33346eaff064109", + "issued": 1792887100, + "decision": "DeniedExpired", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "c419ab1f6710c80eb58d7087ae5fba8b008392559841ec4219e4ba0ea20b82ad3482e3f00528798ab86212f58c2be1b2105aaba6a5694121bf9dd5d38ba3780e" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "offline", + "renews": null, + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "671f9d0cebf6d9de7cb8f0a224ceb7276a342f09d8cac5282d6f6f41095d2347", + "request_t": 1792886460, + "proof": "e46c61e68c7438e23932274a405792b77b5182bf7a8a7bc31f08d76e86b2a35fb15b2f4076785d13e2e4f3da34678625a5550487ce0dc77dbcc441994051130a", + "issued": 1792886460, + "decision": "Granted", + "licence_expiry": 1793059260, + "renewal": 0, + "upstream_status": 200, + "response_sha256": "8178ac72b28d77fcb851fcd301583182bd05d9663ed2ec8131e60f057c2795f7", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "9faf225a50a2d60c2b299c0e456888bafccc79db2dadf31d9c2e0a4a868fcb5a382ba4f736c9547053b8440e99a60e372d46fc73fbe75d5622dacb5cb72e660d" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "renewal", + "renews": "cb3fa6386911272bbe6f0545211220c657193fb876b86dd4976f05ec60a25659", + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "64e3ce0143be1438e92c402a18082493515815d7ad80c1f538f166683b12d059", + "request_t": 1793577600, + "proof": "1aef7373b7529b654ffd3b81fceee7955a09de0c4cf4130e8cb9419447578175f3766a93e81d1623182e967bf8758738002e653e558dab09be229d87e98da609", + "issued": 1793577600, + "decision": "DeniedExpired", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "825c55e3091dddba9af45c08145f3b1f431248ccd967aecf584cdf99b81fa880ec9a14c05e5010de05e04e96cf9401c9b13bb83324a3022b3e3fc910b9508a09" + } + ], + "tickets": [], + "outcome": "accept", + "receipts": [ + { + "identity": "628529ca373d1216d48bffce0fe8307c147b9f98cd49e0e87995b2d1abac3edc", + "decision": "DeniedExpired", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "cb3fa6386911272bbe6f0545211220c657193fb876b86dd4976f05ec60a25659", + "decision": "Granted", + "released": true, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "licence_expiry": 1793059260, + "renewal": 0 + }, + { + "identity": "7b1038f3ee4cd752ea8f1cbd8eb47388dab04e3f8f1253c6dca0f354c7710987", + "decision": "DeniedExpired", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + } + ] + }, + { + "name": "rotation", + "about": "key rotation: the keys of the periods an entitlement meets are released, the next one ahead of its period; the key of a period after the entitlement ends is refused, alone or beside one that would be released", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "channel" + ], + "cpix": [ + { + "list": 0, + "file": "channel.cpix.xml" + } + ], + "log": [ + { + "form": 2, + "content_id": "channel-0003", + "list": "5b88af6630e02fe040a4ef49354b2536c0ee60d6279fe75ebf575a30193c3e3e", + "cpix_sha256": "89b2fdaed4db112bcdd95daeee74bd45c21c973ab29fc982e7c1c612d258b12d", + "required_entitlement": "live:sports", + "delivery": { + "kind": "drm", + "system_id": "94ce86fb07ff4f43adb893d2fa968ca2" + }, + "keys": [ + { + "kid": "924698c359ad3e890d5be75bed61d6c2", + "scheme": "cbcs", + "period": { + "index": 1, + "start": 1792886400, + "end": 1792890000 + }, + "key_commit": "a039736b904f04172680956481844d7a0027423b117003e5c3f45a079da23b30" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101fb8f6482a3d9bce49ac31eca0b6f5983f978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000b6c6976653a73706f727473000000006add4680000000006add5b9800000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65f80ffbc40cf98e4fd39ac773306681f7ec3c0bad9be1d3c0066020696c4ec4e3ae5b64d986a1279b28b354c82ac01d5453a361e4685e2e840d90376894d8d70c", + "request_sha256": "ed3d243f777044781ef9a28960ea6718840a2f7ba65eff9ac612ee8fa68bd0a1", + "request_t": 1792886460, + "proof": "5f27047fa39e2a2a35a843983db865ca82aaf74a10787fea9b832bce7d2ecc1c10e9038cb15da4996e46e6f72fca206ef1ebfa321b036707e1e0c8d9d2797508", + "issued": 1792886460, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "729b46b0e0aea5de750c8d60d4bb1c0a12b2bee1e795f0985c9be14e7edaa654", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "3447a98a6926609a2a97c14c6327765a1088d1320214fa17baa59823aecb920bd43be6fab214f45d2f8d12493e5f01d9d9579004b8ac925b0f1d9c88eafdd001" + }, + { + "form": 2, + "content_id": "channel-0003", + "list": "5b88af6630e02fe040a4ef49354b2536c0ee60d6279fe75ebf575a30193c3e3e", + "cpix_sha256": "89b2fdaed4db112bcdd95daeee74bd45c21c973ab29fc982e7c1c612d258b12d", + "required_entitlement": "live:sports", + "delivery": { + "kind": "drm", + "system_id": "94ce86fb07ff4f43adb893d2fa968ca2" + }, + "keys": [ + { + "kid": "8d1504c7e59d94629fed63ad7e27b44a", + "scheme": "cbcs", + "period": { + "index": 2, + "start": 1792890000, + "end": 1792893600 + }, + "key_commit": "7693c56d35ba7f1cd0f5448b76fe00a448b5646f0fda98925316195506c8d3cc" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101fb8f6482a3d9bce49ac31eca0b6f5983f978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000b6c6976653a73706f727473000000006add4680000000006add5b9800000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65f80ffbc40cf98e4fd39ac773306681f7ec3c0bad9be1d3c0066020696c4ec4e3ae5b64d986a1279b28b354c82ac01d5453a361e4685e2e840d90376894d8d70c", + "request_sha256": "f0dd710d643e056f61ac4932862e0fd6facf779d84ac1d54a23a8cab9c37deb0", + "request_t": 1792889900, + "proof": "f36d949407e523f0b6d98398250b48ee89e4177b7daffdc2fbaca76d0b3378933ebbc90091be1b30416b9e8ace4e0c44203bc4970dbc62bb04b3e04c7bf76d07", + "issued": 1792889900, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "729b46b0e0aea5de750c8d60d4bb1c0a12b2bee1e795f0985c9be14e7edaa654", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "12ce832e884d6e24d4cb7d030568510e669bc43858230eb0253352c6946e3130db3c94bfce4dd27108ef1489f2c2ac1afbbde691858fa829530719dc9497430e" + }, + { + "form": 2, + "content_id": "channel-0003", + "list": "5b88af6630e02fe040a4ef49354b2536c0ee60d6279fe75ebf575a30193c3e3e", + "cpix_sha256": "89b2fdaed4db112bcdd95daeee74bd45c21c973ab29fc982e7c1c612d258b12d", + "required_entitlement": "live:sports", + "delivery": { + "kind": "drm", + "system_id": "94ce86fb07ff4f43adb893d2fa968ca2" + }, + "keys": [ + { + "kid": "01e0a684e472167eb200c53a878e6f6f", + "scheme": "cbcs", + "period": { + "index": 3, + "start": 1792893600, + "end": 1792897200 + }, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101fb8f6482a3d9bce49ac31eca0b6f5983f978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000b6c6976653a73706f727473000000006add4680000000006add5b9800000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65f80ffbc40cf98e4fd39ac773306681f7ec3c0bad9be1d3c0066020696c4ec4e3ae5b64d986a1279b28b354c82ac01d5453a361e4685e2e840d90376894d8d70c", + "request_sha256": "3d7cd783a92d385464ca46ff449c023392f59f23fef896411c66e634fc91ae2d", + "request_t": 1792891400, + "proof": "02bc8c5ecf1b4de9f5c1e5bc423c0ad62cf618904192b57146c4f0e0c35c49749e9056116708de93dea4a7aead7734148ec4386db88c94746e0885fd5a1d5d0c", + "issued": 1792891400, + "decision": "DeniedPeriodOutside", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "7342694ab6ba2cea49baeb2535f268ecf0af042b35e345899f7afcfef294648942b180a93e06258ef6e214267f8bcf0820fe205996d93b35523ba1e231bafc02" + }, + { + "form": 2, + "content_id": "channel-0003", + "list": "5b88af6630e02fe040a4ef49354b2536c0ee60d6279fe75ebf575a30193c3e3e", + "cpix_sha256": "89b2fdaed4db112bcdd95daeee74bd45c21c973ab29fc982e7c1c612d258b12d", + "required_entitlement": "live:sports", + "delivery": { + "kind": "drm", + "system_id": "94ce86fb07ff4f43adb893d2fa968ca2" + }, + "keys": [ + { + "kid": "01e0a684e472167eb200c53a878e6f6f", + "scheme": "cbcs", + "period": { + "index": 3, + "start": 1792893600, + "end": 1792897200 + }, + "key_commit": null + }, + { + "kid": "8d1504c7e59d94629fed63ad7e27b44a", + "scheme": "cbcs", + "period": { + "index": 2, + "start": 1792890000, + "end": 1792893600 + }, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101fb8f6482a3d9bce49ac31eca0b6f5983f978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000b6c6976653a73706f727473000000006add4680000000006add5b9800000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65f80ffbc40cf98e4fd39ac773306681f7ec3c0bad9be1d3c0066020696c4ec4e3ae5b64d986a1279b28b354c82ac01d5453a361e4685e2e840d90376894d8d70c", + "request_sha256": "853f7192535823755c48c06f5b7601a229e09656773746013671ae53a5188030", + "request_t": 1792891410, + "proof": "60400e72b9cb4d5492c205b3db697a97a68da929f5b6af57d3d027f21da87e8f8af455f080a1538fd3d8d00c39d0de12ad63118b5f79ddedf6f0fed1c9905c00", + "issued": 1792891410, + "decision": "DeniedPeriodOutside", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "f989ed6f3f2cfcea300ce8af910f459aeda54ab50ded3b0cf58692d1bdbeb8f00c93617623d875cbf58f49d351f488a14604e1ac2db4bef14f94f37d9d6b9000" + } + ], + "tickets": [], + "outcome": "accept", + "receipts": [ + { + "identity": "b5fd1e32474953be75d4dee441c73b7a72ced79253ce20cb05bf52cf7047675e", + "decision": "Granted", + "released": true, + "keys": [ + { + "kid": "924698c359ad3e890d5be75bed61d6c2", + "key_commit": "a039736b904f04172680956481844d7a0027423b117003e5c3f45a079da23b30" + } + ], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "519d1fa7d5fc65fd1bd4ab1da175d0fbef6595182024a7c2b8563b36b429492c", + "decision": "Granted", + "released": true, + "keys": [ + { + "kid": "8d1504c7e59d94629fed63ad7e27b44a", + "key_commit": "7693c56d35ba7f1cd0f5448b76fe00a448b5646f0fda98925316195506c8d3cc" + } + ], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "ea319ee2044591c30264d1879775944568e33b9a30eb25fa6eb6a1b96aebbfdb", + "decision": "DeniedPeriodOutside", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "ac1f1976255635f0adfff3ddbf2179d7fead1776eb9d1eb4ace7de6fe9f225ce", + "decision": "DeniedPeriodOutside", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + } + ] + }, + { + "name": "offline", + "about": "offline licences: a release, its one permitted renewal, a renewal past the count, a renewal after the licence lapsed, a renewal of no licence, an entitlement with no offline allowance, and a licence clamped to the entitlement's end", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "offline", + "renews": null, + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "13c0a753a441586fc5f1fab10d63041bcff46f8b34e8e8a401b4371df4aea9ae", + "request_t": 1792886460, + "proof": "bc289183d950923d83035fd398910c2ab754459882ca86eb8b0483f675282011668d77e16f120c2f8d3bc12e61c66770f7e2530b1678d965527ae2cf3f1ef504", + "issued": 1792886460, + "decision": "Granted", + "licence_expiry": 1793059260, + "renewal": 0, + "upstream_status": 200, + "response_sha256": "0170e0e46d881b576cf4a84d74b00bf2ac66ac6d3543ff39fc473fe5a21a2364", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "8143cc6528dacc483d067b74a3424903c27a192ff194bb51a479cb99b2b796faaee7546f66bb50df29e836835f6d8cdd8359c2dbeb009f74faee5f381b642908" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "renewal", + "renews": "eb9accc322f5c9a840ec34a5e2f5e63fb80779bc5f425a638dc6476df87099db", + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "ae3231e79f90934d12a8f98e532a88587b8f11d5b122c1267a2f7421fc689c57", + "request_t": 1792972800, + "proof": "ceac605104b77cc884cd7fc494e748c4d8a39787ef2e2d8618f7b0fa43a7277bd34e689a7c950c3b8dc02466dd2971a845d54d4fc820556a89d318cf8e5e8a0c", + "issued": 1792972800, + "decision": "Granted", + "licence_expiry": 1793145600, + "renewal": 1, + "upstream_status": 200, + "response_sha256": "0170e0e46d881b576cf4a84d74b00bf2ac66ac6d3543ff39fc473fe5a21a2364", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "80b179867bf35862e8682036b6a70c4352e4ce2daf62eb48dbdf619cabbc199c9c931b6e03b5c72f867c530db019143044ead01fe38f963265ff5d832066ae07" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": null + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "renewal", + "renews": "ac064f6f4617015e0e633b42d41086dd75bd76165e1bb4098061dbf038358af3", + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "4e71f3158253f99a041108e1114c53cade7264fea953639c2fb3ade7e90b7c8e", + "request_t": 1793059200, + "proof": "4ef27b9be1efccd397914c67fec74cf808238cd849a47d4dc520027765fe6793cc9297f5dd8bc22c4e1caffed36c843e69b0eb537b4f3ff86a6fe143fcf2f305", + "issued": 1793059200, + "decision": "DeniedRenewalsExhausted", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "417050e1e1a91c9b63243ffee3129d2c055ca56d70958a8098689bd29c8aebca60c651cdac086921ab78dfed5fe90886617730438ffb2fba976b249375730d00" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "offline", + "renews": null, + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "a374308479cb979cb61138ea1bab854b4e3d030da0fdb6328bb58f7a60875b27", + "request_t": 1793145600, + "proof": "6dd52b5eab545d4097ca844c266f4021103932f29a85d42bc0586f15422e7a59946da7e7e656473b172e81ef430633419384bf5e8ef8e0a9e8765150d2602d07", + "issued": 1793145600, + "decision": "Granted", + "licence_expiry": 1793318400, + "renewal": 0, + "upstream_status": 200, + "response_sha256": "0170e0e46d881b576cf4a84d74b00bf2ac66ac6d3543ff39fc473fe5a21a2364", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "fceedec5f27b8e4d18830f78f9a7fefc437031068d6c3ec426139e7f0a5f7c8eebbad18507ae8b86a0a2530ba78b46e96b9617d553414376acc84956560c350d" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": null + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "renewal", + "renews": "ec4a840899b3ff650b935e4948103c9c26694850a77aa8ea35982c319ba46de3", + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "0618f82577fabe02d244fca58e5ce33b72eebd4df9b04d2dacffb9e98285eec8", + "request_t": 1793404800, + "proof": "c08daf30da10b3507f8c31e2901750591bb2db9d3e0c52ab26c5ac7e7eb0cd5459763ecd49bf8fd2413b671683d5ade088c582ba7aab9fe63a1d1bdbf08e3102", + "issued": 1793404800, + "decision": "DeniedRenewalLapsed", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "7b02592de5f8b7fb48d6df95e1822e77973fb05d3e5afafa29610c5b904dc6c3a20f3a1b9b2c2014c1c573e7d96a4b2afeb28dd6153162447f7bd5f583e40902" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": null + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "renewal", + "renews": "7777777777777777777777777777777777777777777777777777777777777777", + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "76c8460d7c7ca6c2c8d9b295924ee970f7cddac0b54165cf1f88643b893f7006", + "request_t": 1793404810, + "proof": "8075e0cbb4f4a775fafcef244995ee23b0bd231af735dba365aa4776435acd388adb54fea4163055afb5cab52ce5beac2dcd7eaa11e3f5bb8d4f646eef8d1a0e", + "issued": 1793404810, + "decision": "DeniedRenewalUnknown", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "249095e7d6c229aeb2e9b699d16777e09d78fa6505944d26ce577e711a0a949c7e347a96ae67873037556ed89c77dace448e28a9c7fbb9ebbb7c38ef6e34c002" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": null + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "offline", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "6304220a088613df62d8ab47e625039341dd090caaade2027325c4ce4b553e1f", + "request_t": 1793404820, + "proof": "f9fc8bcefa4f7fcd63ce13c565341513e71678a1b91b7ab71ee521eef69a601d0dbe2d5ab8ffb5a65df7cb2bedffdd660ab2a1dd430057bb82e57852efe3ab0d", + "issued": 1793404820, + "decision": "DeniedOfflineNotEntitled", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "c25a87daad70482ca52c1365277422b41c41a0460b4dc5f775af031665ce824ff427b68b852e02bec28b48a8909a4ee1ecebe8e89839f88e7139ac68d0572a00" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "offline", + "renews": null, + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "73bed362023dd34f3aa9a91103920cfce1324fdb68909098fe760bcf7cfe8594", + "request_t": 1793448000, + "proof": "568bb0fc0bd7942c14d71283e6371468783ed3288c85725e208094c84bfddaba089945805a2ebf04d04fa342331e78c07dd0ae1e3243969f5eddc21f47110a05", + "issued": 1793448000, + "decision": "Granted", + "licence_expiry": 1793491200, + "renewal": 0, + "upstream_status": 200, + "response_sha256": "0170e0e46d881b576cf4a84d74b00bf2ac66ac6d3543ff39fc473fe5a21a2364", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "be22fc4c01d8a701d9f2d653e89daaf6ec166310137f123ec7ad8a6ed40e8758cae7d61042bdbc405ee9d8ba27982c087e9f7724ceb0e38f278d7c4f06c99d05" + } + ], + "tickets": [], + "outcome": "accept", + "receipts": [ + { + "identity": "eb9accc322f5c9a840ec34a5e2f5e63fb80779bc5f425a638dc6476df87099db", + "decision": "Granted", + "released": true, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "licence_expiry": 1793059260, + "renewal": 0 + }, + { + "identity": "ac064f6f4617015e0e633b42d41086dd75bd76165e1bb4098061dbf038358af3", + "decision": "Granted", + "released": true, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "licence_expiry": 1793145600, + "renewal": 1 + }, + { + "identity": "954a9f02fd022e9b22f72974e7424d850fd377f61617fc0b0f75df74718c1580", + "decision": "DeniedRenewalsExhausted", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "ec4a840899b3ff650b935e4948103c9c26694850a77aa8ea35982c319ba46de3", + "decision": "Granted", + "released": true, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "licence_expiry": 1793318400, + "renewal": 0 + }, + { + "identity": "f7111a4b63af6224cbfd60c63700491f131849d1a7f91ed1bbc8a2a28e233325", + "decision": "DeniedRenewalLapsed", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "e2f3d59a1adb158175c54eee30f4b3fe1ad9cd5b8e960aab8596b0468a4920b3", + "decision": "DeniedRenewalUnknown", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "47f1161b2e6a9b46e07ffcbe35dea5f604dcf97c31d281f59752721b403fa073", + "decision": "DeniedOfflineNotEntitled", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "4aebbd11ae67bcdf16c176857ee2681c512ac88ce7673864dd7110d546d98bc5", + "decision": "Granted", + "released": true, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "licence_expiry": 1793491200, + "renewal": 0 + } + ] + }, + { + "name": "upstream_refused", + "about": "granted requests the licence server refused (403) or did not answer (502): nothing is released", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "2b13c0d9b5cd40f76d15f122e63af2619f037c6f910b8dfdd0958bbbdf5c8539", + "request_t": 1792887200, + "proof": "85cd5dc13b44c89cf3cee7294972243d18fe61b4492f9e0d63dc208b411c3d9db20435bac45501b9d16d59f0a324d06c880a04ce5a600a1213d05704cf04d30d", + "issued": 1792887200, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 403, + "response_sha256": "0fb9ad4749f0aa8f846125d67313c1c1f38d2570e1a575cd8f92392cbf98e20a", + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "6e4d7d5d09038e6ae67742ae7e4ac505266d8519c0ee3435ff427a222442ad3ce404cde024485d27b200014f363f22f45482edb07331bd12655ce27ab0411605" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "f756ef409588883cc82f5d055b381ab2858d3a473ee402fb37c0c3251004e391", + "request_t": 1792887210, + "proof": "048d56dae49dbd1e927d689367ac423e1b3bf6ee7d1ca5c50bc6bbfd2a1448a9068a0c30fe8e910459fb0530f24ddca1dc2b18aca4d96a33d33eb39dc81a960d", + "issued": 1792887210, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 502, + "response_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "04d34cea07b825371d02187ab1c26d4fc70e4b3dfad5d511f1235a76d14ed24d4bbd268df8f7003b8bdf74a2a40c95ab181aba4084b0838b825f7e4fb7371a09" + } + ], + "tickets": [], + "outcome": "accept", + "receipts": [ + { + "identity": "e7d86a82c4837ce8708d80db19d5147c9e60984a9102c08013131ba1d0642c88", + "decision": "Granted", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + }, + { + "identity": "ad11d9c643f970ac89303a4f64915b7cbc5851c4e2a46f61fedc7ccb7698a140", + "decision": "Granted", + "released": false, + "keys": [], + "licence_expiry": null, + "renewal": null + } + ] + }, + { + "name": "n_misreported_grant", + "about": "a refusal for the wrong class restated as a release and signed again", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b4531010f6f2b67a9d9238b5bbdd09715028fdcf978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000a73766f643a6261736963000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c6534c9c72c6e06c075992ed9ec37d6ab705f30be5939c3ec21c96252c63fdb495e792e1a5627e5b2e80082a235c5b5900a685b20a99203e2e2af5f51357388030d", + "request_sha256": "5fe71444dc9a62020d463d52b39070dc7266456887132dbe3e900255acbe18a4", + "request_t": 1792887070, + "proof": "3d8b211e6748ebc2f9bb27923609606e7a7fe1b23cd5de94149842f4a40a9e5940ed53e01b9cc1ebf653da2f1a2288193eeedee0ea5de0f08488a4ced746770e", + "issued": 1792887070, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "8178ac72b28d77fcb851fcd301583182bd05d9663ed2ec8131e60f057c2795f7", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "c652389a79162eee9d9f6d170457afc61811fed74a15171c196fc955366bed6abd06198047400afae1cf3e4eba08da12475720b7aa0b6dcfc700dfbb126eab02" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "decision_mismatch", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "n_forged_signature", + "about": "a release whose signature has one bit changed", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "b851e0321933fce99a44c3aa7ea3fe5a2c7669e3c1e25c601003bc62da25aa21", + "request_t": 1792886500, + "proof": "ccb70cbdb080a589cf26c3fdbac2d21d0b195c12cab49c3a90c172527e6898f45d86393f8255e56943eebd28667871a518c2194d4301f9bdafd26beac06fe60d", + "issued": 1792886501, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "52321853be4abb3e25a435a95e54ddb91cebd8d200ff64c571147fef166b1e14", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "82c67eb4665baf92b4c6803c645ba14d778c87b35011e7e8328f074911ec9a8ffb30636d78cf1a0aa3b9cea057e3b4d4d95feb742534f1861c6ae5420a0a010c" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "bad_signature", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "n_bad_key", + "about": "a release signed under the identity point, which a lenient verifier accepts for every message", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "b851e0321933fce99a44c3aa7ea3fe5a2c7669e3c1e25c601003bc62da25aa21", + "request_t": 1792886500, + "proof": "ccb70cbdb080a589cf26c3fdbac2d21d0b195c12cab49c3a90c172527e6898f45d86393f8255e56943eebd28667871a518c2194d4301f9bdafd26beac06fe60d", + "issued": 1792886501, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "52321853be4abb3e25a435a95e54ddb91cebd8d200ff64c571147fef166b1e14", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "0100000000000000000000000000000000000000000000000000000000000000", + "signer_id": "https://keys.proxy.example", + "sig": "58666666666666666666666666666666666666666666666666666666666666660100000000000000000000000000000000000000000000000000000000000000" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "bad_key", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "n_commit_not_listed", + "about": "a release whose commitment is not the list's", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "cabf38ae499331fdc1203b818099f79ea3bc2cd79beaa26f2d0b0b42286d9b52" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "b851e0321933fce99a44c3aa7ea3fe5a2c7669e3c1e25c601003bc62da25aa21", + "request_t": 1792886500, + "proof": "ccb70cbdb080a589cf26c3fdbac2d21d0b195c12cab49c3a90c172527e6898f45d86393f8255e56943eebd28667871a518c2194d4301f9bdafd26beac06fe60d", + "issued": 1792886501, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "52321853be4abb3e25a435a95e54ddb91cebd8d200ff64c571147fef166b1e14", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "505c1b061b45174f23578995e34a9eaa7a06c88e2d5793c608cd1e3ddcafcc86048611b81df403dad55d353e063592d4b3c1c0ced2bed8de5ca808c9cdabce07" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "list_mismatch", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "n_refusal_with_commit", + "about": "a refusal that commits to keys", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b4531010f6f2b67a9d9238b5bbdd09715028fdcf978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000a73766f643a6261736963000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c6534c9c72c6e06c075992ed9ec37d6ab705f30be5939c3ec21c96252c63fdb495e792e1a5627e5b2e80082a235c5b5900a685b20a99203e2e2af5f51357388030d", + "request_sha256": "5fe71444dc9a62020d463d52b39070dc7266456887132dbe3e900255acbe18a4", + "request_t": 1792887070, + "proof": "3d8b211e6748ebc2f9bb27923609606e7a7fe1b23cd5de94149842f4a40a9e5940ed53e01b9cc1ebf653da2f1a2288193eeedee0ea5de0f08488a4ced746770e", + "issued": 1792887070, + "decision": "DeniedNoEntitlement", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "4f657798f2800206358c1eae017fe9f3fa5b8eca6b5e369be94fa533ca96f504324da5c09efdf6489dbeb2671f8446eff97b67f6bde49ad9cc14bc1e98aef003" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "receipt_invalid", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "n_forwarded_refusal", + "about": "a refusal that records the licence server's answer, so was forwarded", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b4531010f6f2b67a9d9238b5bbdd09715028fdcf978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000a73766f643a6261736963000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c6534c9c72c6e06c075992ed9ec37d6ab705f30be5939c3ec21c96252c63fdb495e792e1a5627e5b2e80082a235c5b5900a685b20a99203e2e2af5f51357388030d", + "request_sha256": "5fe71444dc9a62020d463d52b39070dc7266456887132dbe3e900255acbe18a4", + "request_t": 1792887070, + "proof": "3d8b211e6748ebc2f9bb27923609606e7a7fe1b23cd5de94149842f4a40a9e5940ed53e01b9cc1ebf653da2f1a2288193eeedee0ea5de0f08488a4ced746770e", + "issued": 1792887070, + "decision": "DeniedNoEntitlement", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "8178ac72b28d77fcb851fcd301583182bd05d9663ed2ec8131e60f057c2795f7", + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "5f10d71f29264bc6c2d36411076c9703a4185887b705bd2afe4c880e2b1d117b3a38288ddf1da25c960db953865e32cc45c721aba98168c802b006d9acfeca01" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "receipt_invalid", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "n_released_on_upstream_error", + "about": "a release the licence server refused", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "2b13c0d9b5cd40f76d15f122e63af2619f037c6f910b8dfdd0958bbbdf5c8539", + "request_t": 1792887200, + "proof": "85cd5dc13b44c89cf3cee7294972243d18fe61b4492f9e0d63dc208b411c3d9db20435bac45501b9d16d59f0a324d06c880a04ce5a600a1213d05704cf04d30d", + "issued": 1792887200, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 403, + "response_sha256": "0fb9ad4749f0aa8f846125d67313c1c1f38d2570e1a575cd8f92392cbf98e20a", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "760e0f28791c33238d7136375c3e896aaeae834dd82d050ae74040349a6a8b39c387f75fb94c31e9cb45ebf6f6d28f3bdce404b5e14f28725695eefb37e39100" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "receipt_invalid", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "n_licence_outlives_entitlement", + "about": "an offline licence whose expiry is past the entitlement's end", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "offline", + "renews": null, + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "13c0a753a441586fc5f1fab10d63041bcff46f8b34e8e8a401b4371df4aea9ae", + "request_t": 1792886460, + "proof": "bc289183d950923d83035fd398910c2ab754459882ca86eb8b0483f675282011668d77e16f120c2f8d3bc12e61c66770f7e2530b1678d965527ae2cf3f1ef504", + "issued": 1792886460, + "decision": "Granted", + "licence_expiry": 1793059260, + "renewal": 0, + "upstream_status": 200, + "response_sha256": "0170e0e46d881b576cf4a84d74b00bf2ac66ac6d3543ff39fc473fe5a21a2364", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "8143cc6528dacc483d067b74a3424903c27a192ff194bb51a479cb99b2b796faaee7546f66bb50df29e836835f6d8cdd8359c2dbeb009f74faee5f381b642908" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "renewal", + "renews": "eb9accc322f5c9a840ec34a5e2f5e63fb80779bc5f425a638dc6476df87099db", + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "ae3231e79f90934d12a8f98e532a88587b8f11d5b122c1267a2f7421fc689c57", + "request_t": 1792972800, + "proof": "ceac605104b77cc884cd7fc494e748c4d8a39787ef2e2d8618f7b0fa43a7277bd34e689a7c950c3b8dc02466dd2971a845d54d4fc820556a89d318cf8e5e8a0c", + "issued": 1792972800, + "decision": "Granted", + "licence_expiry": 1793145600, + "renewal": 1, + "upstream_status": 200, + "response_sha256": "0170e0e46d881b576cf4a84d74b00bf2ac66ac6d3543ff39fc473fe5a21a2364", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "80b179867bf35862e8682036b6a70c4352e4ce2daf62eb48dbdf619cabbc199c9c931b6e03b5c72f867c530db019143044ead01fe38f963265ff5d832066ae07" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": null + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "renewal", + "renews": "ac064f6f4617015e0e633b42d41086dd75bd76165e1bb4098061dbf038358af3", + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "4e71f3158253f99a041108e1114c53cade7264fea953639c2fb3ade7e90b7c8e", + "request_t": 1793059200, + "proof": "4ef27b9be1efccd397914c67fec74cf808238cd849a47d4dc520027765fe6793cc9297f5dd8bc22c4e1caffed36c843e69b0eb537b4f3ff86a6fe143fcf2f305", + "issued": 1793059200, + "decision": "DeniedRenewalsExhausted", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "417050e1e1a91c9b63243ffee3129d2c055ca56d70958a8098689bd29c8aebca60c651cdac086921ab78dfed5fe90886617730438ffb2fba976b249375730d00" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "offline", + "renews": null, + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "a374308479cb979cb61138ea1bab854b4e3d030da0fdb6328bb58f7a60875b27", + "request_t": 1793145600, + "proof": "6dd52b5eab545d4097ca844c266f4021103932f29a85d42bc0586f15422e7a59946da7e7e656473b172e81ef430633419384bf5e8ef8e0a9e8765150d2602d07", + "issued": 1793145600, + "decision": "Granted", + "licence_expiry": 1793318400, + "renewal": 0, + "upstream_status": 200, + "response_sha256": "0170e0e46d881b576cf4a84d74b00bf2ac66ac6d3543ff39fc473fe5a21a2364", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "fceedec5f27b8e4d18830f78f9a7fefc437031068d6c3ec426139e7f0a5f7c8eebbad18507ae8b86a0a2530ba78b46e96b9617d553414376acc84956560c350d" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": null + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "renewal", + "renews": "ec4a840899b3ff650b935e4948103c9c26694850a77aa8ea35982c319ba46de3", + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "0618f82577fabe02d244fca58e5ce33b72eebd4df9b04d2dacffb9e98285eec8", + "request_t": 1793404800, + "proof": "c08daf30da10b3507f8c31e2901750591bb2db9d3e0c52ab26c5ac7e7eb0cd5459763ecd49bf8fd2413b671683d5ade088c582ba7aab9fe63a1d1bdbf08e3102", + "issued": 1793404800, + "decision": "DeniedRenewalLapsed", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "7b02592de5f8b7fb48d6df95e1822e77973fb05d3e5afafa29610c5b904dc6c3a20f3a1b9b2c2014c1c573e7d96a4b2afeb28dd6153162447f7bd5f583e40902" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": null + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "renewal", + "renews": "7777777777777777777777777777777777777777777777777777777777777777", + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "76c8460d7c7ca6c2c8d9b295924ee970f7cddac0b54165cf1f88643b893f7006", + "request_t": 1793404810, + "proof": "8075e0cbb4f4a775fafcef244995ee23b0bd231af735dba365aa4776435acd388adb54fea4163055afb5cab52ce5beac2dcd7eaa11e3f5bb8d4f646eef8d1a0e", + "issued": 1793404810, + "decision": "DeniedRenewalUnknown", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "249095e7d6c229aeb2e9b699d16777e09d78fa6505944d26ce577e711a0a949c7e347a96ae67873037556ed89c77dace448e28a9c7fbb9ebbb7c38ef6e34c002" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": null + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "offline", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "6304220a088613df62d8ab47e625039341dd090caaade2027325c4ce4b553e1f", + "request_t": 1793404820, + "proof": "f9fc8bcefa4f7fcd63ce13c565341513e71678a1b91b7ab71ee521eef69a601d0dbe2d5ab8ffb5a65df7cb2bedffdd660ab2a1dd430057bb82e57852efe3ab0d", + "issued": 1793404820, + "decision": "DeniedOfflineNotEntitled", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "c25a87daad70482ca52c1365277422b41c41a0460b4dc5f775af031665ce824ff427b68b852e02bec28b48a8909a4ee1ecebe8e89839f88e7139ac68d0572a00" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "offline", + "renews": null, + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "73bed362023dd34f3aa9a91103920cfce1324fdb68909098fe760bcf7cfe8594", + "request_t": 1793448000, + "proof": "568bb0fc0bd7942c14d71283e6371468783ed3288c85725e208094c84bfddaba089945805a2ebf04d04fa342331e78c07dd0ae1e3243969f5eddc21f47110a05", + "issued": 1793448000, + "decision": "Granted", + "licence_expiry": 1793620800, + "renewal": 0, + "upstream_status": 200, + "response_sha256": "0170e0e46d881b576cf4a84d74b00bf2ac66ac6d3543ff39fc473fe5a21a2364", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "f459f6dbf73abf8e5c7f94426f73bbfb5ba2db8a11dfa504bd2101f90f8dd91aa26a3fbc21928dceeaf21d0ceb58af267e6eff745af2fca14dae933cc54ada0a" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "decision_mismatch", + "at": [ + "receipt", + 7 + ] + }, + { + "name": "n_scheme_delivery_mismatch", + "about": "a cens key released as an HLS key file, which HLS defines for no such scheme", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "clip" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "clip-0004", + "list": "cb8942b0f85f68894de3f899ec362f3d034b557ca38f1a5590417e463fb1cbf5", + "cpix_sha256": null, + "required_entitlement": "svod:basic", + "delivery": { + "kind": "hls-key-uri", + "system_id": null + }, + "keys": [ + { + "kid": "16e7de4b348c4707987e397d76edf209", + "scheme": "cens", + "period": null, + "key_commit": "2b9b516a3a3146e4c166ae110eb1b2f000bfef1ac5e9b8b2b7b035f422931632" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b4531010f6f2b67a9d9238b5bbdd09715028fdcf978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000a73766f643a6261736963000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c6534c9c72c6e06c075992ed9ec37d6ab705f30be5939c3ec21c96252c63fdb495e792e1a5627e5b2e80082a235c5b5900a685b20a99203e2e2af5f51357388030d", + "request_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "request_t": 1792887300, + "proof": "77a7a6220f59ad9cf8773f5b0077487853c05e6297e060d76bd6e4b9c76c15ecaac51f8a565600e303f66b1d6efd142d39beddcb662e270158d78a3a21e92a02", + "issued": 1792887300, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "04b0688ea19170a0978f03a0b29b05f8be9fdacf449f7e1a319051d12b36ee2b3287d1d4064adff628c282340242e6e1115700753b166d1ab39678b01b50a20a" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "scheme_delivery_mismatch", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "n_cpix_commit_mismatch", + "about": "a list whose commitment is not its document's key", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film_tampered_commit" + ], + "cpix": [ + { + "list": 0, + "file": "film.cpix.xml" + } + ], + "log": [], + "tickets": [], + "outcome": "refuse", + "reason": "cpix_mismatch", + "at": [ + "cpix", + 0 + ] + }, + { + "name": "n_cpix_digest_mismatch", + "about": "a list naming another document's digest", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film_wrong_digest" + ], + "cpix": [ + { + "list": 0, + "file": "film.cpix.xml" + } + ], + "log": [], + "tickets": [], + "outcome": "refuse", + "reason": "cpix_mismatch", + "at": [ + "cpix", + 0 + ] + }, + { + "name": "n_cpix_encrypted", + "about": "a document whose key is encrypted, which this revision does not read", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "encrypted" + ], + "cpix": [ + { + "list": 0, + "file": "encrypted.cpix.xml" + } + ], + "log": [], + "tickets": [], + "outcome": "refuse", + "reason": "cpix_unsupported", + "at": [ + "cpix", + 0 + ] + }, + { + "name": "n_cpix_malformed", + "about": "a document that is not well-formed", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [ + { + "list": 0, + "file": "broken.cpix.xml" + } + ], + "log": [], + "tickets": [], + "outcome": "refuse", + "reason": "cpix_malformed", + "at": [ + "cpix", + 0 + ] + }, + { + "name": "n_list_forged", + "about": "a list whose signature has one bit changed", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film_forged" + ], + "cpix": [], + "log": [], + "tickets": [], + "outcome": "refuse", + "reason": "list_bad_signature", + "at": [ + "list", + 0 + ] + }, + { + "name": "n_list_malformed", + "about": "a list with a byte after its signature", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film_trailing" + ], + "cpix": [], + "log": [], + "tickets": [], + "outcome": "refuse", + "reason": "list_malformed", + "at": [ + "list", + 0 + ] + }, + { + "name": "n_list_two_for_one_content", + "about": "two lists for one content", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film", + "film_tampered_commit" + ], + "cpix": [], + "log": [], + "tickets": [], + "outcome": "refuse", + "reason": "list_malformed", + "at": [ + "list", + 1 + ] + }, + { + "name": "n_list_unknown", + "about": "a release citing a list the verifier is not given", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "b851e0321933fce99a44c3aa7ea3fe5a2c7669e3c1e25c601003bc62da25aa21", + "request_t": 1792886500, + "proof": "ccb70cbdb080a589cf26c3fdbac2d21d0b195c12cab49c3a90c172527e6898f45d86393f8255e56943eebd28667871a518c2194d4301f9bdafd26beac06fe60d", + "issued": 1792886501, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "52321853be4abb3e25a435a95e54ddb91cebd8d200ff64c571147fef166b1e14", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "83c67eb4665baf92b4c6803c645ba14d778c87b35011e7e8328f074911ec9a8ffb30636d78cf1a0aa3b9cea057e3b4d4d95feb742534f1861c6ae5420a0a010c" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "list_unknown", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "n_signer_unknown", + "about": "a release by a proxy the verifier does not trust", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "52bd078cfccda2bf71bd23625655bd700e5dd79294b2fc49b1493946502b17a6" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "b851e0321933fce99a44c3aa7ea3fe5a2c7669e3c1e25c601003bc62da25aa21", + "request_t": 1792886500, + "proof": "ccb70cbdb080a589cf26c3fdbac2d21d0b195c12cab49c3a90c172527e6898f45d86393f8255e56943eebd28667871a518c2194d4301f9bdafd26beac06fe60d", + "issued": 1792886501, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "52321853be4abb3e25a435a95e54ddb91cebd8d200ff64c571147fef166b1e14", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "83c67eb4665baf92b4c6803c645ba14d778c87b35011e7e8328f074911ec9a8ffb30636d78cf1a0aa3b9cea057e3b4d4d95feb742534f1861c6ae5420a0a010c" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "signer_unknown", + "at": [ + "receipt", + 0 + ] + }, + { + "name": "n_replay_granted", + "about": "a replayed request restated as a release", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": null, + "request_sha256": "ba718264615af763af9d246f87e5cc8ea4600804a1719069744378b26f6fe5e1", + "request_t": 1792887000, + "proof": null, + "issued": 1792887000, + "decision": "DeniedEntitlementInvalid", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "ffe3772ca5cf0e5940fc05131b5a414775b47fef508f6355fda7d5cc5af0d4699e0bf096836be232688d6a7cbf31e38681a86bba2c94110560f8e6250e974b05" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b45310140334b75e54044627f196c5b9ead194e4a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d3800000000000000000000000003fbede6ff0cca9fd75b2007d071b21d21eec451d83903e3f7ed8a302a587e88c0000001568747470733a2f2f726f6775652e6578616d706c656970d746f081c7902d6009eec0f4b53f774e7e27c60ea6960095fc40f600027ff86649dc61795402f9ecd0c3543d7c8ebb3935c2065b5093ef91b1a1558bf90c", + "request_sha256": "d4a67cbd55810ba8bdfc05780e16d6d057640311d6ab2fbc7d1ae5425ea7e271", + "request_t": 1792887010, + "proof": "1de816bb3e431eed0c3a3745fedd373bb506492e232cee6206d2205bde50ddb1fbd40d0f1b8d67a48b978f28437aa723b92376d6bed0f5283986781d1be8de05", + "issued": 1792887010, + "decision": "DeniedEntitlementInvalid", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "a476ba1832f38540ad7d92bdb5c370c7cf1b2ac99d7e933b99abd1665dc6df177d2a43bdf7ed05fa1845fd15afe76b15a37381310a42b7f07ae7c68a950c8a06" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "9b2033f53d175649c69fbf23156f027856bac920cb8a470f83fdfc05d391ad67", + "request_t": 1792887020, + "proof": "56c38ad68fd25fca2ac258a1cbeede721e43056d845b844c7505015ba7b090515c8910e5144dd444c0d437a258f82d190f39bfdb70ca26a01c04b4a1e1e8e303", + "issued": 1792887020, + "decision": "DeniedEntitlementInvalid", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "7d6b569f66bb5c0d496ecc837e87693e027c157486cb12c129dc2b05e9812b0cd34f7042fb6ad5fc4ebda75bc7dd5bc13c7fb083140d31f5061f9f71a255380d" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "d0f5c0adda9675326ef3c0bd4c2839d8cf041e779d0fcd22063982bed06ad45f", + "request_t": 1792886730, + "proof": "8e6fb18da84e81528e92a657d6d4af1657ba8fbbd920992a36d57f1d161e66daf23bb9003890e2aa7a28cc46aaaa20c734b1b4059fcfe2bfd599309d1f5f9606", + "issued": 1792887031, + "decision": "DeniedStale", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "a6212386b2c21a909168ef56070ebbcd5a0524f10141c1c0079c0ecf6a642cbaf8d641f6aaf12b4115d8577a3db28c991ae1018e1d443c7f72e4e583bc768906" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "89bbc902799db02f04c18f5b0af5a8f8e06e4477e7087c08c961e7590411e789", + "request_t": 1792887040, + "proof": "f98211f6a41d6e54202f58b714450ae7467621c2e6cdae663c2b8c05fc0c121b87e6fc7af824d085f58321105c68c2056a9335a06e2326e47aacfab1d58bc701", + "issued": 1792887040, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "8178ac72b28d77fcb851fcd301583182bd05d9663ed2ec8131e60f057c2795f7", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "d4cef37183e7b501bb8605244c2746f5c03d0b2da65585dceb3511d1564618223fcacb083281bb2cb07b00246732a8e37bff439c3fdea57819fbcc7de1f93c05" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "89bbc902799db02f04c18f5b0af5a8f8e06e4477e7087c08c961e7590411e789", + "request_t": 1792887040, + "proof": "f98211f6a41d6e54202f58b714450ae7467621c2e6cdae663c2b8c05fc0c121b87e6fc7af824d085f58321105c68c2056a9335a06e2326e47aacfab1d58bc701", + "issued": 1792887045, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "8178ac72b28d77fcb851fcd301583182bd05d9663ed2ec8131e60f057c2795f7", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "25850a0699eefac2dd80abc5d238d9942bcfdef96d844aa648924acb9c1eacf6c626f6c55c7dd83edeb8bcde02193a95798f832bb4030395ac873ac1b51cf70a" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8bcc435d9d47cda1c3586404d1ec666b", + "scheme": null, + "period": null, + "key_commit": null + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "593c9a4fecb9d7e8866ecff74892eaa7768216409f83039642c16d0ce15c98f5", + "request_t": 1792887050, + "proof": "02276ca08ed41005ade7e03d1414b60b3d43fc3352b16af70066f3e16343e8c7a1ed187fb74b7b734c17923d560d0624d8da4a5268af380b161d5851105ce60e", + "issued": 1792887050, + "decision": "DeniedUnknownKey", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "0f001dac17873d235547e0bd2d9115c66c64cccb06de83c3e0e82f93c22b58c5f4397e122491089a9ec3d7c6da13d1dd68a1e16bda0c864987bf7df8efb5cf0a" + }, + { + "form": 2, + "content_id": "film-9999", + "list": null, + "cpix_sha256": null, + "required_entitlement": null, + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": null, + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "c8c32b7f2d8fb656bad75c031e0634ef95583c42eca2fc323bea76e551c3ae5c", + "request_t": 1792887060, + "proof": "9c0fa48a29b1ecabe053188087dba0828eedbd47837e09fe4b734ba9da6f07d685485fe56ac006960ae764c0bd0ad309d7a1343bc3fbb4d838720bcf2a935304", + "issued": 1792887060, + "decision": "DeniedUnknownKey", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "0e460c12e4746b8e824ce2ea70e9dba3469b859fb227c0c2b5e34a89bf7c86bdfadaf8773ec4df575f14b96438e08c0498b7b3a4a0fadbfa7864706d27ec7f0d" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b4531010f6f2b67a9d9238b5bbdd09715028fdcf978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000a73766f643a6261736963000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c6534c9c72c6e06c075992ed9ec37d6ab705f30be5939c3ec21c96252c63fdb495e792e1a5627e5b2e80082a235c5b5900a685b20a99203e2e2af5f51357388030d", + "request_sha256": "5fe71444dc9a62020d463d52b39070dc7266456887132dbe3e900255acbe18a4", + "request_t": 1792887070, + "proof": "3d8b211e6748ebc2f9bb27923609606e7a7fe1b23cd5de94149842f4a40a9e5940ed53e01b9cc1ebf653da2f1a2288193eeedee0ea5de0f08488a4ced746770e", + "issued": 1792887070, + "decision": "DeniedNoEntitlement", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "f304c8c21cb316d5eb9f5ea94ad9767ece78b21ab896de7d22a88dbdb123b8c9d36ce044fbc992dec5923a0adf7ab4659a9dc87554b0656c0edb3ebcfedd6f07" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b45310185ebd301c7a8ccda976e3a16ad3233a34a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006ade9800000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65004dcf60d14d5b0cf677b872dcf7dd7517d7a8d963ab78325293665b5de264c750984cabe75e3bc680eea0204118ee41b645544092f84af248a3c468f26f8f0a", + "request_sha256": "cae93d2b47d76f58e476bd2d447228038adb616d70a811f8873e8bdf7f26063d", + "request_t": 1792887080, + "proof": "70b3ff7dd50785e15319e3e0ed347e2babc065ebd3de8e01bb1c9cd7ef3c0ee245a253950b930ad1f1f51a075bac5aa37bd207bc50f4e08a900da65d5064750e", + "issued": 1792887080, + "decision": "DeniedNotYetValid", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "3b86490f48185bd147c922e063c5ee62e1bbe359db7750efe33c54b62f5d676659b5f8aeb0bf5aa9cb45e1b16e3d84246f56767181fb2a0963e598c0fd3e070a" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "decision_mismatch", + "at": [ + "receipt", + 5 + ] + }, + { + "name": "n_renewal_of_nothing_granted", + "about": "a renewal of no licence restated as a release", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "offline", + "renews": null, + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "13c0a753a441586fc5f1fab10d63041bcff46f8b34e8e8a401b4371df4aea9ae", + "request_t": 1792886460, + "proof": "bc289183d950923d83035fd398910c2ab754459882ca86eb8b0483f675282011668d77e16f120c2f8d3bc12e61c66770f7e2530b1678d965527ae2cf3f1ef504", + "issued": 1792886460, + "decision": "Granted", + "licence_expiry": 1793059260, + "renewal": 0, + "upstream_status": 200, + "response_sha256": "0170e0e46d881b576cf4a84d74b00bf2ac66ac6d3543ff39fc473fe5a21a2364", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "8143cc6528dacc483d067b74a3424903c27a192ff194bb51a479cb99b2b796faaee7546f66bb50df29e836835f6d8cdd8359c2dbeb009f74faee5f381b642908" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "renewal", + "renews": "eb9accc322f5c9a840ec34a5e2f5e63fb80779bc5f425a638dc6476df87099db", + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "ae3231e79f90934d12a8f98e532a88587b8f11d5b122c1267a2f7421fc689c57", + "request_t": 1792972800, + "proof": "ceac605104b77cc884cd7fc494e748c4d8a39787ef2e2d8618f7b0fa43a7277bd34e689a7c950c3b8dc02466dd2971a845d54d4fc820556a89d318cf8e5e8a0c", + "issued": 1792972800, + "decision": "Granted", + "licence_expiry": 1793145600, + "renewal": 1, + "upstream_status": 200, + "response_sha256": "0170e0e46d881b576cf4a84d74b00bf2ac66ac6d3543ff39fc473fe5a21a2364", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "80b179867bf35862e8682036b6a70c4352e4ce2daf62eb48dbdf619cabbc199c9c931b6e03b5c72f867c530db019143044ead01fe38f963265ff5d832066ae07" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": null + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "renewal", + "renews": "ac064f6f4617015e0e633b42d41086dd75bd76165e1bb4098061dbf038358af3", + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "4e71f3158253f99a041108e1114c53cade7264fea953639c2fb3ade7e90b7c8e", + "request_t": 1793059200, + "proof": "4ef27b9be1efccd397914c67fec74cf808238cd849a47d4dc520027765fe6793cc9297f5dd8bc22c4e1caffed36c843e69b0eb537b4f3ff86a6fe143fcf2f305", + "issued": 1793059200, + "decision": "DeniedRenewalsExhausted", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "417050e1e1a91c9b63243ffee3129d2c055ca56d70958a8098689bd29c8aebca60c651cdac086921ab78dfed5fe90886617730438ffb2fba976b249375730d00" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "offline", + "renews": null, + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "a374308479cb979cb61138ea1bab854b4e3d030da0fdb6328bb58f7a60875b27", + "request_t": 1793145600, + "proof": "6dd52b5eab545d4097ca844c266f4021103932f29a85d42bc0586f15422e7a59946da7e7e656473b172e81ef430633419384bf5e8ef8e0a9e8765150d2602d07", + "issued": 1793145600, + "decision": "Granted", + "licence_expiry": 1793318400, + "renewal": 0, + "upstream_status": 200, + "response_sha256": "0170e0e46d881b576cf4a84d74b00bf2ac66ac6d3543ff39fc473fe5a21a2364", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "fceedec5f27b8e4d18830f78f9a7fefc437031068d6c3ec426139e7f0a5f7c8eebbad18507ae8b86a0a2530ba78b46e96b9617d553414376acc84956560c350d" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": null + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "renewal", + "renews": "ec4a840899b3ff650b935e4948103c9c26694850a77aa8ea35982c319ba46de3", + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "0618f82577fabe02d244fca58e5ce33b72eebd4df9b04d2dacffb9e98285eec8", + "request_t": 1793404800, + "proof": "c08daf30da10b3507f8c31e2901750591bb2db9d3e0c52ab26c5ac7e7eb0cd5459763ecd49bf8fd2413b671683d5ade088c582ba7aab9fe63a1d1bdbf08e3102", + "issued": 1793404800, + "decision": "DeniedRenewalLapsed", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "7b02592de5f8b7fb48d6df95e1822e77973fb05d3e5afafa29610c5b904dc6c3a20f3a1b9b2c2014c1c573e7d96a4b2afeb28dd6153162447f7bd5f583e40902" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "renewal", + "renews": "7777777777777777777777777777777777777777777777777777777777777777", + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "76c8460d7c7ca6c2c8d9b295924ee970f7cddac0b54165cf1f88643b893f7006", + "request_t": 1793404810, + "proof": "8075e0cbb4f4a775fafcef244995ee23b0bd231af735dba365aa4776435acd388adb54fea4163055afb5cab52ce5beac2dcd7eaa11e3f5bb8d4f646eef8d1a0e", + "issued": 1793404810, + "decision": "Granted", + "licence_expiry": 1793491210, + "renewal": 1, + "upstream_status": 200, + "response_sha256": "8178ac72b28d77fcb851fcd301583182bd05d9663ed2ec8131e60f057c2795f7", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "716593fd260f9048f946a341eb308b5168b63a4f794eba7b802d1f99c2d1083ceb4d15b19310536f87c81078cb12bee476071770625db3d1243127ab7a89650f" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": null + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": null + } + ], + "kind": "offline", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "6304220a088613df62d8ab47e625039341dd090caaade2027325c4ce4b553e1f", + "request_t": 1793404820, + "proof": "f9fc8bcefa4f7fcd63ce13c565341513e71678a1b91b7ab71ee521eef69a601d0dbe2d5ab8ffb5a65df7cb2bedffdd660ab2a1dd430057bb82e57852efe3ab0d", + "issued": 1793404820, + "decision": "DeniedOfflineNotEntitled", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "c25a87daad70482ca52c1365277422b41c41a0460b4dc5f775af031665ce824ff427b68b852e02bec28b48a8909a4ee1ecebe8e89839f88e7139ac68d0572a00" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "offline", + "renews": null, + "entitlement": "574b4531013e582ad6b98d0ef86b270bb949622fb14a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006ae68100000000000002a3000000000123661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65ff3be13bbcf9e78435c105a735505c456025959e0feea2d21c484597eb1e47b3a154bfeec310d2291b9f1bea9fddc8fbf8dc6479a8370244ded130a916b3b706", + "request_sha256": "73bed362023dd34f3aa9a91103920cfce1324fdb68909098fe760bcf7cfe8594", + "request_t": 1793448000, + "proof": "568bb0fc0bd7942c14d71283e6371468783ed3288c85725e208094c84bfddaba089945805a2ebf04d04fa342331e78c07dd0ae1e3243969f5eddc21f47110a05", + "issued": 1793448000, + "decision": "Granted", + "licence_expiry": 1793491200, + "renewal": 0, + "upstream_status": 200, + "response_sha256": "0170e0e46d881b576cf4a84d74b00bf2ac66ac6d3543ff39fc473fe5a21a2364", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "be22fc4c01d8a701d9f2d653e89daaf6ec166310137f123ec7ad8a6ed40e8758cae7d61042bdbc405ee9d8ba27982c087e9f7724ceb0e38f278d7c4f06c99d05" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "decision_mismatch", + "at": [ + "receipt", + 5 + ] + }, + { + "name": "n_period_outside_granted", + "about": "the key of a period after the entitlement ends, restated as a release", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "channel" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "channel-0003", + "list": "5b88af6630e02fe040a4ef49354b2536c0ee60d6279fe75ebf575a30193c3e3e", + "cpix_sha256": "89b2fdaed4db112bcdd95daeee74bd45c21c973ab29fc982e7c1c612d258b12d", + "required_entitlement": "live:sports", + "delivery": { + "kind": "drm", + "system_id": "94ce86fb07ff4f43adb893d2fa968ca2" + }, + "keys": [ + { + "kid": "924698c359ad3e890d5be75bed61d6c2", + "scheme": "cbcs", + "period": { + "index": 1, + "start": 1792886400, + "end": 1792890000 + }, + "key_commit": "a039736b904f04172680956481844d7a0027423b117003e5c3f45a079da23b30" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101fb8f6482a3d9bce49ac31eca0b6f5983f978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000b6c6976653a73706f727473000000006add4680000000006add5b9800000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65f80ffbc40cf98e4fd39ac773306681f7ec3c0bad9be1d3c0066020696c4ec4e3ae5b64d986a1279b28b354c82ac01d5453a361e4685e2e840d90376894d8d70c", + "request_sha256": "ed3d243f777044781ef9a28960ea6718840a2f7ba65eff9ac612ee8fa68bd0a1", + "request_t": 1792886460, + "proof": "5f27047fa39e2a2a35a843983db865ca82aaf74a10787fea9b832bce7d2ecc1c10e9038cb15da4996e46e6f72fca206ef1ebfa321b036707e1e0c8d9d2797508", + "issued": 1792886460, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "729b46b0e0aea5de750c8d60d4bb1c0a12b2bee1e795f0985c9be14e7edaa654", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "3447a98a6926609a2a97c14c6327765a1088d1320214fa17baa59823aecb920bd43be6fab214f45d2f8d12493e5f01d9d9579004b8ac925b0f1d9c88eafdd001" + }, + { + "form": 2, + "content_id": "channel-0003", + "list": "5b88af6630e02fe040a4ef49354b2536c0ee60d6279fe75ebf575a30193c3e3e", + "cpix_sha256": "89b2fdaed4db112bcdd95daeee74bd45c21c973ab29fc982e7c1c612d258b12d", + "required_entitlement": "live:sports", + "delivery": { + "kind": "drm", + "system_id": "94ce86fb07ff4f43adb893d2fa968ca2" + }, + "keys": [ + { + "kid": "8d1504c7e59d94629fed63ad7e27b44a", + "scheme": "cbcs", + "period": { + "index": 2, + "start": 1792890000, + "end": 1792893600 + }, + "key_commit": "7693c56d35ba7f1cd0f5448b76fe00a448b5646f0fda98925316195506c8d3cc" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101fb8f6482a3d9bce49ac31eca0b6f5983f978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000b6c6976653a73706f727473000000006add4680000000006add5b9800000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65f80ffbc40cf98e4fd39ac773306681f7ec3c0bad9be1d3c0066020696c4ec4e3ae5b64d986a1279b28b354c82ac01d5453a361e4685e2e840d90376894d8d70c", + "request_sha256": "f0dd710d643e056f61ac4932862e0fd6facf779d84ac1d54a23a8cab9c37deb0", + "request_t": 1792889900, + "proof": "f36d949407e523f0b6d98398250b48ee89e4177b7daffdc2fbaca76d0b3378933ebbc90091be1b30416b9e8ace4e0c44203bc4970dbc62bb04b3e04c7bf76d07", + "issued": 1792889900, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "729b46b0e0aea5de750c8d60d4bb1c0a12b2bee1e795f0985c9be14e7edaa654", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "12ce832e884d6e24d4cb7d030568510e669bc43858230eb0253352c6946e3130db3c94bfce4dd27108ef1489f2c2ac1afbbde691858fa829530719dc9497430e" + }, + { + "form": 2, + "content_id": "channel-0003", + "list": "5b88af6630e02fe040a4ef49354b2536c0ee60d6279fe75ebf575a30193c3e3e", + "cpix_sha256": "89b2fdaed4db112bcdd95daeee74bd45c21c973ab29fc982e7c1c612d258b12d", + "required_entitlement": "live:sports", + "delivery": { + "kind": "drm", + "system_id": "94ce86fb07ff4f43adb893d2fa968ca2" + }, + "keys": [ + { + "kid": "01e0a684e472167eb200c53a878e6f6f", + "scheme": "cbcs", + "period": { + "index": 3, + "start": 1792893600, + "end": 1792897200 + }, + "key_commit": "b3a076ac6a926606ec7da35856276e5cb2f58d9743721339241a975ee2630c9b" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101fb8f6482a3d9bce49ac31eca0b6f5983f978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000b6c6976653a73706f727473000000006add4680000000006add5b9800000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65f80ffbc40cf98e4fd39ac773306681f7ec3c0bad9be1d3c0066020696c4ec4e3ae5b64d986a1279b28b354c82ac01d5453a361e4685e2e840d90376894d8d70c", + "request_sha256": "3d7cd783a92d385464ca46ff449c023392f59f23fef896411c66e634fc91ae2d", + "request_t": 1792891400, + "proof": "02bc8c5ecf1b4de9f5c1e5bc423c0ad62cf618904192b57146c4f0e0c35c49749e9056116708de93dea4a7aead7734148ec4386db88c94746e0885fd5a1d5d0c", + "issued": 1792891400, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "8178ac72b28d77fcb851fcd301583182bd05d9663ed2ec8131e60f057c2795f7", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "03a3d319fb05ced46eaf4051c8fd93444dbe0f1cd6bfcb7c8e4bfdcb486700513756ccf4f6b29065043d8c84c7abd56d2d1761a1e434b63f74a1506cc6b25d04" + }, + { + "form": 2, + "content_id": "channel-0003", + "list": "5b88af6630e02fe040a4ef49354b2536c0ee60d6279fe75ebf575a30193c3e3e", + "cpix_sha256": "89b2fdaed4db112bcdd95daeee74bd45c21c973ab29fc982e7c1c612d258b12d", + "required_entitlement": "live:sports", + "delivery": { + "kind": "drm", + "system_id": "94ce86fb07ff4f43adb893d2fa968ca2" + }, + "keys": [ + { + "kid": "01e0a684e472167eb200c53a878e6f6f", + "scheme": "cbcs", + "period": { + "index": 3, + "start": 1792893600, + "end": 1792897200 + }, + "key_commit": null + }, + { + "kid": "8d1504c7e59d94629fed63ad7e27b44a", + "scheme": "cbcs", + "period": { + "index": 2, + "start": 1792890000, + "end": 1792893600 + }, + "key_commit": null + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101fb8f6482a3d9bce49ac31eca0b6f5983f978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000b6c6976653a73706f727473000000006add4680000000006add5b9800000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65f80ffbc40cf98e4fd39ac773306681f7ec3c0bad9be1d3c0066020696c4ec4e3ae5b64d986a1279b28b354c82ac01d5453a361e4685e2e840d90376894d8d70c", + "request_sha256": "853f7192535823755c48c06f5b7601a229e09656773746013671ae53a5188030", + "request_t": 1792891410, + "proof": "60400e72b9cb4d5492c205b3db697a97a68da929f5b6af57d3d027f21da87e8f8af455f080a1538fd3d8d00c39d0de12ad63118b5f79ddedf6f0fed1c9905c00", + "issued": 1792891410, + "decision": "DeniedPeriodOutside", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": false, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "f989ed6f3f2cfcea300ce8af910f459aeda54ab50ded3b0cf58692d1bdbeb8f00c93617623d875cbf58f49d351f488a14604e1ac2db4bef14f94f37d9d6b9000" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "decision_mismatch", + "at": [ + "receipt", + 2 + ] + }, + { + "name": "n_duplicate", + "about": "one receipt twice in a log", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "b851e0321933fce99a44c3aa7ea3fe5a2c7669e3c1e25c601003bc62da25aa21", + "request_t": 1792886500, + "proof": "ccb70cbdb080a589cf26c3fdbac2d21d0b195c12cab49c3a90c172527e6898f45d86393f8255e56943eebd28667871a518c2194d4301f9bdafd26beac06fe60d", + "issued": 1792886501, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "52321853be4abb3e25a435a95e54ddb91cebd8d200ff64c571147fef166b1e14", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "83c67eb4665baf92b4c6803c645ba14d778c87b35011e7e8328f074911ec9a8ffb30636d78cf1a0aa3b9cea057e3b4d4d95feb742534f1861c6ae5420a0a010c" + }, + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "b851e0321933fce99a44c3aa7ea3fe5a2c7669e3c1e25c601003bc62da25aa21", + "request_t": 1792886500, + "proof": "ccb70cbdb080a589cf26c3fdbac2d21d0b195c12cab49c3a90c172527e6898f45d86393f8255e56943eebd28667871a518c2194d4301f9bdafd26beac06fe60d", + "issued": 1792886501, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "52321853be4abb3e25a435a95e54ddb91cebd8d200ff64c571147fef166b1e14", + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "83c67eb4665baf92b4c6803c645ba14d778c87b35011e7e8328f074911ec9a8ffb30636d78cf1a0aa3b9cea057e3b4d4d95feb742534f1861c6ae5420a0a010c" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "receipt_duplicate", + "at": [ + "receipt", + 1 + ] + }, + { + "name": "n_ticket_forged", + "about": "a ticket whose signature has one bit changed", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "episode" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "episode-0002", + "list": "7419666222736906d75b2e5da0968692ab56a4d0a457afe800bd6d2ef9866c36", + "cpix_sha256": "da0ad96e5be3cdb14613365ce2779c1cb890dda912952e9ff5cd28cb88cde475", + "required_entitlement": "svod:basic", + "delivery": { + "kind": "hls-key-uri", + "system_id": null + }, + "keys": [ + { + "kid": "6bd9096f4700ad0bd872027c08d6d444", + "scheme": "AES-128", + "period": null, + "key_commit": "468f4506e4bac9d7d02021c64ddec49f9e144fa6098515073e64e476b81e6b3e" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b4531010f6f2b67a9d9238b5bbdd09715028fdcf978e04ffec911466446c898e6f6c74d04cef231aaf03c1cf2769cd08a95455c0000000a73766f643a6261736963000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c6534c9c72c6e06c075992ed9ec37d6ab705f30be5939c3ec21c96252c63fdb495e792e1a5627e5b2e80082a235c5b5900a685b20a99203e2e2af5f51357388030d", + "request_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "request_t": 1792886700, + "proof": "1d8b7fe581f75eec5efa604822ec7d607b2a5d5f847fcb864bc7eb8eeadb99cf96f8e8e841d787b4878dd54b3126df988e765900e6afbc1c71bc8f78be368b09", + "issued": 1792886701, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "4608631dd88e24aa83b097439fde628c164c1c2469994b18c0d6492364788b54fd1009aaa669362b9aa6934944e3499f4cb2124a8ac4159a61668262a5a9e107" + } + ], + "tickets": [ + "574b5431010480815f5436c8e8cbf85ec25a8f756fdf68c2331259c4a246f45f333e0c79910101000000016bd9096f4700ad0bd872027c08d6d444000000006add47e927ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1d6e23d1a7b80e0d3efe8ecf9d3a94a7455da89e7ca5d8f1aeda3cc811d8738554b8527eb1911e1b0bb6a4da6587f48d534fe32b82b6782e4fb780c8625166608" + ], + "outcome": "refuse", + "reason": "ticket_invalid", + "at": [ + "ticket", + 0 + ] + }, + { + "name": "n_ticket_kids", + "about": "a ticket naming fewer keys than its receipt released", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "clear-key", + "system_id": null + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "1731fdf8e567e6bc473cd6eb2f4e91932ba435b3d29fd8cbac9202afeb7b8f7b", + "request_t": 1792886800, + "proof": "3b008a62d2acbee4183a55588fe1591ed90192c15713fb6796c86a3a060d1bc4594742336574b31e4a3e8e7a5edc62589b6f9d1b67e28f1b352dedbddc6faa0d", + "issued": 1792886800, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": null, + "response_sha256": null, + "released": true, + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example", + "sig": "56867c9042421cd21e2ee4ff4b826ed31b90aa7c135b7dbbe90e5d12943826d96a2577e565d9c41931563675c756e0f783a36445439b2b9303294e1e65ccd00c" + } + ], + "tickets": [ + "574b54310167b68d9bc84f0d299d696848a9b8f78e65f42369fb320ab426bed7efc5cfca710201000000018c92350e5042656d2d7656d7afbdf00b000000006add484c27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d16a7bc0515bb7c3e0b049c4e27a68b963d3f56c00b0806c3174dff368f35277c3317df78f579ba2a3c0c0f09d3904785a7d0cd5dc1a06a82f32edd05d01efcf05" + ], + "outcome": "refuse", + "reason": "ticket_invalid", + "at": [ + "ticket", + 0 + ] + }, + { + "name": "n_malformed", + "about": "a receipt without its `released` member", + "authorities": [ + "23661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d9" + ], + "proxies": [ + "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1" + ], + "lists": [ + "film" + ], + "cpix": [], + "log": [ + { + "form": 2, + "content_id": "film-0001", + "list": "0ecb74df36a03f66fa3aa674601eac2bf5324e57ccdfc9e7b8f5abe4f33af716", + "cpix_sha256": "1841e71b49c4375b46ab5b3e576cc3b0012799f3e5c616568e871f5d3cade01e", + "required_entitlement": "svod:premium", + "delivery": { + "kind": "drm", + "system_id": "edef8ba979d64acea3c827dcd51d21ed" + }, + "keys": [ + { + "kid": "7f2f1512416114229ded99cecd52be76", + "scheme": "cenc", + "period": null, + "key_commit": "e2004d4ea73f85b2162f08ae3dfcfc1f7a6ef6b7fb8416633d3111db920edd74" + }, + { + "kid": "8c92350e5042656d2d7656d7afbdf00b", + "scheme": "cenc", + "period": null, + "key_commit": "46ee48377008eee3eb36c2390fe0fe0597656a18fe1109c3a8057585025c0d94" + } + ], + "kind": "streaming", + "renews": null, + "entitlement": "574b453101251b1795bc764bfbc19d368e8483d7194a3bff82fc283bc3cb25a72fbe086b085490735db99ae1219e0807bcdf28f46c0000000c73766f643a7072656d69756d000000006add4680000000006b04d38000000000000000000000000023661a6d0fb7f0e38b183f9eb31351b9f9912b14b2702d1d83164c355de886d90000001c68747470733a2f2f656e7469746c656d656e74732e6578616d706c65c1fa7474f92ef3d337d1671296fb85f230799356d90f072aa84717aea1d9fa3b6d88797d2163d02f6cfb388b6dbdac7aa649c59807a3db5d2f49caf4226cf10f", + "request_sha256": "b851e0321933fce99a44c3aa7ea3fe5a2c7669e3c1e25c601003bc62da25aa21", + "request_t": 1792886500, + "proof": "ccb70cbdb080a589cf26c3fdbac2d21d0b195c12cab49c3a90c172527e6898f45d86393f8255e56943eebd28667871a518c2194d4301f9bdafd26beac06fe60d", + "issued": 1792886501, + "decision": "Granted", + "licence_expiry": null, + "renewal": null, + "upstream_status": 200, + "response_sha256": "52321853be4abb3e25a435a95e54ddb91cebd8d200ff64c571147fef166b1e14", + "sig": "83c67eb4665baf92b4c6803c645ba14d778c87b35011e7e8328f074911ec9a8ffb30636d78cf1a0aa3b9cea057e3b4d4d95feb742534f1861c6ae5420a0a010c", + "joules_micro": 0, + "energy_provenance": null, + "signer_pubkey": "27ae349d7742d4d3ba9a8be94c59e793e3569af239790909d3af96734dd9f5d1", + "signer_id": "https://keys.proxy.example" + } + ], + "tickets": [], + "outcome": "refuse", + "reason": "receipt_malformed", + "at": [ + "receipt", + 0 + ] + } + ] +} diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index 4416f8c46..5cec00a6d 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -565,6 +565,11 @@ video_ssai = ["video_manifest", "video_policy"] # recomputable decision. Reuses the manifest floor (EnergyProvenance, GrantRef, # Ed25519). video_keys = ["video_manifest"] +# extensions/key-release.md: the proxy form of wai.video.keys, its commitment list (WKL1) derived +# from a CPIX document, the entitlement (WKE1), the device's proof over a request, the release +# decision (freshness, replay, key periods, offline licences and renewals), the release ticket +# (WKT1) and verification of a proxy's log. The CPIX reader is in the module; no new dependency. +key_release = ["video_keys"] # wai.video.package — deterministic segmentation + attested, deduplicating # SegmentMap. Reuses the manifest floor (EnergyProvenance, GrantRef, Merkle). Also the # packaging extension's stage index (WSIX) and version-2 package receipt, which name @@ -939,6 +944,11 @@ name = "wai_live_watermark_vectors" path = "src/bin/wai_live_watermark_vectors.rs" required-features = ["live_watermark"] +[[bin]] +name = "wai_key_release_vectors" +path = "src/bin/wai_key_release_vectors.rs" +required-features = ["key_release"] + [[bin]] name = "wai_live_ads_vectors" path = "src/bin/wai_live_ads_vectors.rs" @@ -1747,6 +1757,11 @@ name = "live_watermark" path = "tests/live_watermark.rs" required-features = ["live_watermark"] +[[test]] +name = "key_release" +path = "tests/key_release.rs" +required-features = ["key_release"] + [[test]] name = "live_ads" path = "tests/live_ads.rs" diff --git a/wai/wai-rs/src/bin/wai_key_release_vectors.rs b/wai/wai-rs/src/bin/wai_key_release_vectors.rs new file mode 100644 index 000000000..f1a81c3c5 --- /dev/null +++ b/wai/wai-rs/src/bin/wai_key_release_vectors.rs @@ -0,0 +1,75 @@ +//! Conformance corpus for key release (`live-conformance/keys/`, extensions/key-release.md §12). +//! +//! wai_key_release_vectors gen write vectors.json and the CPIX documents, +//! from fixed seeds +//! wai_key_release_vectors verify every file regenerates byte for byte, no other +//! file but README.md is there, and every case gets +//! the outcome it states +//! +//! The generator and this verifier are one implementation. The corpus's second +//! implementation is `live-conformance/key_release_verify.py`, written from the extension's +//! text and sharing no code with this binary. + +use std::collections::BTreeMap; +use std::path::Path; +use wai::key_release::corpus; + +fn main() { + let a: Vec = std::env::args().collect(); + match (a.get(1).map(String::as_str), a.get(2)) { + (Some("gen"), Some(d)) => { + if let Err(e) = std::fs::create_dir_all(d) { + eprintln!("create {d}: {e}"); + std::process::exit(1); + } + for (name, body) in corpus::generate() { + let p = Path::new(d).join(&name); + if let Err(e) = std::fs::write(&p, body) { + eprintln!("write {}: {e}", p.display()); + std::process::exit(1); + } + } + println!("wrote {d}"); + } + (Some("verify"), Some(d)) => std::process::exit(verify(Path::new(d))), + _ => { + eprintln!("usage: wai_key_release_vectors gen|verify "); + std::process::exit(2); + } + } +} + +fn verify(dir: &Path) -> i32 { + let mut files = BTreeMap::new(); + let entries = match std::fs::read_dir(dir) { + Ok(e) => e, + Err(e) => { + eprintln!("read {}: {e}", dir.display()); + return 1; + } + }; + for e in entries.flatten() { + let name = e.file_name().to_string_lossy().into_owned(); + if name == "README.md" { + continue; + } + match std::fs::read_to_string(e.path()) { + Ok(s) => { + files.insert(name, s); + } + Err(err) => { + eprintln!("read {name}: {err}"); + return 1; + } + } + } + let generated: BTreeMap = corpus::generate().into_iter().collect(); + let same = files == generated; + println!("{} every file regenerates byte for byte from its seeds, and no other file is there", if same { "PASS" } else { "FAIL" }); + let fails = corpus::verify(&files); + for f in &fails { + println!("FAIL {f}"); + } + println!("{} every case gets the outcome the corpus states", if fails.is_empty() { "PASS" } else { "FAIL" }); + (!same || !fails.is_empty()) as i32 +} diff --git a/wai/wai-rs/src/key_release.rs b/wai/wai-rs/src/key_release.rs new file mode 100644 index 000000000..8e69d57de --- /dev/null +++ b/wai/wai-rs/src/key_release.rs @@ -0,0 +1,1739 @@ +//! Key release: the proxy form of `wai.video.keys` (extensions/key-release.md). +//! +//! A signing proxy sits in front of a licence server and signs one receipt for every +//! licence request it answers, a release or a refusal. The receipt names each key's +//! encryption scheme (§3.1), the key delivery (§3.2), the content's commitment list +//! (`WKL1`, §4) and the CPIX document it came from, the viewer's entitlement (`WKE1`, +//! §5) and the device's proof over the request (§6), and the decision, which is a pure +//! function of those inputs and the receipts before it in the proxy's log (§7). The +//! proxy copies every `key_commit` from the key holder's list, so it never holds a key; +//! a key the client gets in the clear is never relayed by the proxy either: it signs a +//! release ticket (`WKT1`, §9.3) that a key server redeems. +//! +//! Form 1 of the receipt ([`crate::wai_video_keys`]) is unchanged. The two forms share +//! the key commitment and the identity domain. + +use std::collections::{BTreeMap, HashMap, HashSet}; + +use ed25519_dalek::{Signer, SigningKey}; +use sha2::{Digest, Sha256}; + +use crate::energy_class::{figure_and_class_cohere, write_optional, CounterFiltering, EnergyProvenance, Uncertainty}; +pub use crate::wai_video_keys::key_commitment; + +pub mod corpus; +pub mod cpix; + +/// The magic of a commitment list (§4.1). +pub const LIST_MAGIC: [u8; 4] = *b"WKL1"; +/// The magic of an entitlement (§5.1). +pub const ENTITLEMENT_MAGIC: [u8; 4] = *b"WKE1"; +/// The magic of a release ticket (§9.3). +pub const TICKET_MAGIC: [u8; 4] = *b"WKT1"; +/// The version of every record this revision defines. +pub const RECORD_VERSION: u8 = 1; +/// How far `issued` and `request_t` may differ (§7.2 check 2). +pub const FRESHNESS_S: u64 = 300; +/// The most KIDs one request names (§6.1). +pub const MAX_REQUEST_KEYS: usize = 64; +/// The most keys one list holds (§4.1). +pub const MAX_LIST_KEYS: usize = 4096; +/// The longest a release ticket lives (§9.3). +pub const MAX_TICKET_S: u64 = 300; +/// The resolution floor of a declared uncertainty (energy-measurement §2). +const RESOLUTION_FLOOR: u128 = 288_676; + +const LIST_DOMAIN: &[u8] = b"wai:video-keys-list\x01"; +const LIST_ID_DOMAIN: &[u8] = b"wai:video-keys-list-id\x01"; +const ENTITLEMENT_DOMAIN: &[u8] = b"wai:video-keys-entitlement\x01"; +const REQUEST_DOMAIN: &[u8] = b"wai:video-keys-request\x01"; +const RECEIPT_DOMAIN: &[u8] = b"wai:video-keys-proxy-receipt\x01"; +/// Form 1's identity domain, shared by form 2 (§8.1). +const RECEIPT_ID_DOMAIN: &[u8] = b"wai:video-keys-receipt-id\x01"; +const TICKET_DOMAIN: &[u8] = b"wai:video-keys-ticket\x01"; + +// --------------------------------------------------------------------------------------- +// §3: schemes and deliveries +// --------------------------------------------------------------------------------------- + +/// An encryption scheme (§3.1): what was applied to the media's bytes. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Scheme { + Cenc, + Cbc1, + Cens, + Cbcs, + Aes128, + SampleAes, + Aes256Gcm, +} + +impl Scheme { + pub const ALL: [Scheme; 7] = + [Scheme::Cenc, Scheme::Cbc1, Scheme::Cens, Scheme::Cbcs, Scheme::Aes128, Scheme::SampleAes, Scheme::Aes256Gcm]; + + pub fn code(self) -> u8 { + match self { + Scheme::Cenc => 1, + Scheme::Cbc1 => 2, + Scheme::Cens => 3, + Scheme::Cbcs => 4, + Scheme::Aes128 => 5, + Scheme::SampleAes => 6, + Scheme::Aes256Gcm => 7, + } + } + + pub fn from_code(c: u8) -> Option { + Scheme::ALL.into_iter().find(|s| s.code() == c) + } + + /// The label, which is CPIX's `@commonEncryptionScheme` value for it. + pub fn label(self) -> &'static str { + match self { + Scheme::Cenc => "cenc", + Scheme::Cbc1 => "cbc1", + Scheme::Cens => "cens", + Scheme::Cbcs => "cbcs", + Scheme::Aes128 => "AES-128", + Scheme::SampleAes => "SAMPLE-AES", + Scheme::Aes256Gcm => "AES-256-GCM", + } + } + + pub fn from_label(s: &str) -> Option { + Scheme::ALL.into_iter().find(|x| x.label() == s) + } + + /// The key's octets (§3.1). + pub fn key_len(self) -> usize { + if self == Scheme::Aes256Gcm { 32 } else { 16 } + } +} + +/// A key delivery's kind (§3.2). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum DeliveryKind { + HlsKeyUri, + ClearKey, + Drm, +} + +impl DeliveryKind { + pub const ALL: [DeliveryKind; 3] = [DeliveryKind::HlsKeyUri, DeliveryKind::ClearKey, DeliveryKind::Drm]; + + pub fn code(self) -> u8 { + match self { + DeliveryKind::HlsKeyUri => 1, + DeliveryKind::ClearKey => 2, + DeliveryKind::Drm => 3, + } + } + + pub fn from_code(c: u8) -> Option { + DeliveryKind::ALL.into_iter().find(|k| k.code() == c) + } + + pub fn label(self) -> &'static str { + match self { + DeliveryKind::HlsKeyUri => "hls-key-uri", + DeliveryKind::ClearKey => "clear-key", + DeliveryKind::Drm => "drm", + } + } + + pub fn from_label(s: &str) -> Option { + DeliveryKind::ALL.into_iter().find(|k| k.label() == s) + } + + /// A clear delivery's response is the key itself (§2). + pub fn is_clear(self) -> bool { + self != DeliveryKind::Drm + } + + /// Whether this delivery carries keys of `scheme` (§3.3). + pub fn carries(self, scheme: Scheme) -> bool { + use Scheme::*; + match self { + DeliveryKind::HlsKeyUri => matches!(scheme, Cenc | Cbcs | Aes128 | SampleAes | Aes256Gcm), + DeliveryKind::ClearKey => matches!(scheme, Cenc | Cbc1 | Cens | Cbcs), + DeliveryKind::Drm => true, + } + } +} + +/// A key delivery: its kind and, for `drm`, the system ID. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct Delivery { + pub kind: DeliveryKind, + pub system_id: Option<[u8; 16]>, +} + +impl Delivery { + fn write(&self, o: &mut Vec) { + o.push(self.kind.code()); + opt(o, self.system_id.as_ref().map(|s| &s[..])); + } +} + +/// A key period (§4.1): an index, a window of wall-clock seconds, or both. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Period { + pub index: Option, + /// `(start, end)`: from `start` up to but excluding `end`. + pub window: Option<(u64, u64)>, +} + +impl Period { + fn tag(&self) -> u8 { + match (self.index.is_some(), self.window.is_some()) { + (false, false) => 0, + (true, false) => 1, + (false, true) => 2, + (true, true) => 3, + } + } +} + +fn write_period(p: Option<&Period>, o: &mut Vec) { + match p { + None => o.push(0), + Some(p) => { + o.push(p.tag()); + if let Some(i) = p.index { + o.extend_from_slice(&i.to_be_bytes()); + } + if let Some((s, e)) = p.window { + o.extend_from_slice(&s.to_be_bytes()); + o.extend_from_slice(&e.to_be_bytes()); + } + } + } +} + +fn read_period(r: &mut Rd) -> Option> { + Some(match r.u8()? { + 0 => None, + 1 => Some(Period { index: Some(r.u32()?), window: None }), + 2 => Some(Period { index: None, window: Some((r.u64()?, r.u64()?)) }), + 3 => Some(Period { index: Some(r.u32()?), window: Some((r.u64()?, r.u64()?)) }), + _ => return None, + }) +} + +// --------------------------------------------------------------------------------------- +// Byte helpers +// --------------------------------------------------------------------------------------- + +fn string(o: &mut Vec, s: &str) { + o.extend_from_slice(&(s.len() as u32).to_be_bytes()); + o.extend_from_slice(s.as_bytes()); +} + +fn opt(o: &mut Vec, b: Option<&[u8]>) { + match b { + None => o.push(0), + Some(b) => { + o.push(1); + o.extend_from_slice(b); + } + } +} + +struct Rd<'a> { + b: &'a [u8], + at: usize, +} + +impl<'a> Rd<'a> { + fn new(b: &'a [u8]) -> Rd<'a> { + Rd { b, at: 0 } + } + fn take(&mut self, n: usize) -> Option<&'a [u8]> { + let end = self.at.checked_add(n)?; + let s = self.b.get(self.at..end)?; + self.at = end; + Some(s) + } + fn u8(&mut self) -> Option { + Some(self.take(1)?[0]) + } + fn u32(&mut self) -> Option { + Some(u32::from_be_bytes(self.take(4)?.try_into().ok()?)) + } + fn u64(&mut self) -> Option { + Some(u64::from_be_bytes(self.take(8)?.try_into().ok()?)) + } + fn arr(&mut self) -> Option<[u8; N]> { + self.take(N)?.try_into().ok() + } + fn string(&mut self) -> Option { + let n = self.u32()? as usize; + String::from_utf8(self.take(n)?.to_vec()).ok() + } + fn rest(&self) -> usize { + self.b.len() - self.at + } +} + +/// SHA-256 of `b`. +pub fn sha256(b: &[u8]) -> [u8; 32] { + Sha256::digest(b).into() +} + +/// Lowercase hexadecimal. +pub fn hex(b: &[u8]) -> String { + b.iter().map(|x| format!("{x:02x}")).collect() +} + +/// Lowercase hexadecimal of any length, read back. +pub fn unhex_vec(s: &str) -> Option> { + if !s.len().is_multiple_of(2) || !s.bytes().all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) { + return None; + } + (0..s.len() / 2).map(|i| u8::from_str_radix(&s[2 * i..2 * i + 2], 16).ok()).collect() +} + +/// Lowercase hexadecimal of exactly `N` bytes. +pub fn unhex(s: &str) -> Option<[u8; N]> { + unhex_vec(s)?.try_into().ok() +} + +const B64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +const B64URL: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +fn b64_encode(b: &[u8], alphabet: &[u8; 64], pad: bool) -> String { + let mut o = String::new(); + for c in b.chunks(3) { + let n = c.iter().enumerate().fold(0u32, |a, (i, &x)| a | (x as u32) << (16 - 8 * i)); + let chars = c.len() + 1; + for i in 0..4 { + if i < chars { + o.push(alphabet[(n >> (18 - 6 * i) & 63) as usize] as char); + } else if pad { + o.push('='); + } + } + } + o +} + +fn b64_decode_raw(s: &str, alphabet: &[u8; 64]) -> Option> { + let mut bits = 0u32; + let mut n = 0; + let mut o = Vec::new(); + for c in s.bytes() { + let v = alphabet.iter().position(|&a| a == c)? as u32; + bits = bits << 6 | v; + n += 6; + if n >= 8 { + n -= 8; + o.push((bits >> n) as u8); + bits &= (1 << n) - 1; + } + } + Some(o) +} + +/// base64url without padding (RFC 4648 §5), as the HTTP carriage writes it (§2). +pub fn b64url(b: &[u8]) -> String { + b64_encode(b, B64URL, false) +} + +/// base64url without padding, read back; only its canonical form reads. +pub fn unb64url(s: &str) -> Option> { + let v = b64_decode_raw(s, B64URL)?; + (b64url(&v) == s).then_some(v) +} + +/// base64 (RFC 4648 §4) with padding, its ASCII whitespace removed; only its canonical +/// form reads (§4.4 step 3.4). +pub fn unb64(text: &str) -> Option> { + let s: String = text.chars().filter(|c| !matches!(c, ' ' | '\t' | '\n' | '\r')).collect(); + let body = s.trim_end_matches('='); + let v = b64_decode_raw(body, B64)?; + (b64_encode(&v, B64, true) == s).then_some(v) +} + +/// base64 with padding. +pub fn b64(b: &[u8]) -> String { + b64_encode(b, B64, true) +} + +fn verifies(pubkey: &[u8; 32], msg: &[u8], sig: &[u8; 64]) -> bool { + crate::sig::verify_strict(pubkey, msg, sig).is_ok() +} + +// --------------------------------------------------------------------------------------- +// §4: the commitment list +// --------------------------------------------------------------------------------------- + +/// One key of a commitment list. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ListKey { + pub kid: [u8; 16], + pub scheme: Scheme, + pub key_commit: [u8; 32], + pub period: Option, +} + +/// A commitment list, `WKL1` (§4.1): one content's keys as the key holder states them. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CommitmentList { + pub content_id: String, + pub required_entitlement: String, + pub cpix: Option<[u8; 32]>, + pub issued: u64, + pub keys: Vec, + pub signer_pubkey: [u8; 32], + pub signer_id: String, + pub sig: [u8; 64], +} + +/// Why a list is refused (§4.3). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ListFault { + Malformed, + BadSignature, +} + +impl CommitmentList { + fn body(&self) -> Vec { + let mut o = LIST_MAGIC.to_vec(); + o.push(RECORD_VERSION); + string(&mut o, &self.content_id); + string(&mut o, &self.required_entitlement); + opt(&mut o, self.cpix.as_ref().map(|c| &c[..])); + o.extend_from_slice(&self.issued.to_be_bytes()); + o.extend_from_slice(&(self.keys.len() as u32).to_be_bytes()); + for k in &self.keys { + o.extend_from_slice(&k.kid); + o.push(k.scheme.code()); + o.extend_from_slice(&k.key_commit); + write_period(k.period.as_ref(), &mut o); + } + o.extend_from_slice(&self.signer_pubkey); + string(&mut o, &self.signer_id); + o + } + + fn signed(&self) -> Vec { + let mut m = LIST_DOMAIN.to_vec(); + m.extend_from_slice(&self.body()); + m + } + + /// The list's bytes (§4.1). + pub fn to_bytes(&self) -> Vec { + let mut o = self.body(); + o.extend_from_slice(&self.sig); + o + } + + /// Seal a list: its keys sorted by KID and its signature made. + #[allow(clippy::too_many_arguments)] + pub fn seal( + signer: &SigningKey, + signer_id: &str, + content_id: &str, + required_entitlement: &str, + cpix: Option<[u8; 32]>, + issued: u64, + mut keys: Vec, + ) -> CommitmentList { + keys.sort_by_key(|k| k.kid); + let mut l = CommitmentList { + content_id: content_id.into(), + required_entitlement: required_entitlement.into(), + cpix, + issued, + keys, + signer_pubkey: [0; 32], + signer_id: signer_id.into(), + sig: [0; 64], + }; + l.sign(signer); + l + } + + /// Sign the list again under `signer`. + pub fn sign(&mut self, signer: &SigningKey) { + self.signer_pubkey = signer.verifying_key().to_bytes(); + self.sig = signer.sign(&self.signed()).to_bytes(); + } + + /// Read a list and apply §4.3. + pub fn parse(b: &[u8]) -> Result { + let l = Self::read(b).ok_or(ListFault::Malformed)?; + if !verifies(&l.signer_pubkey, &l.signed(), &l.sig) { + return Err(ListFault::BadSignature); + } + Ok(l) + } + + fn read(b: &[u8]) -> Option { + let mut r = Rd::new(b); + if r.arr::<4>()? != LIST_MAGIC || r.u8()? != RECORD_VERSION { + return None; + } + let content_id = r.string()?; + let required_entitlement = r.string()?; + let cpix = match r.u8()? { + 0 => None, + 1 => Some(r.arr()?), + _ => return None, + }; + let issued = r.u64()?; + let n = r.u32()? as usize; + if !(1..=MAX_LIST_KEYS).contains(&n) || content_id.is_empty() || required_entitlement.is_empty() { + return None; + } + let mut keys: Vec = Vec::with_capacity(n); + for _ in 0..n { + let kid = r.arr()?; + let scheme = Scheme::from_code(r.u8()?)?; + let key_commit: [u8; 32] = r.arr()?; + let period = read_period(&mut r)?; + if key_commit == [0; 32] || keys.last().is_some_and(|p| p.kid >= kid) { + return None; + } + if let Some(Period { window: Some((s, e)), .. }) = period + && s >= e + { + return None; + } + keys.push(ListKey { kid, scheme, key_commit, period }); + } + let signer_pubkey = r.arr()?; + let signer_id = r.string()?; + let sig = r.arr()?; + if r.rest() != 0 { + return None; + } + Some(CommitmentList { content_id, required_entitlement, cpix, issued, keys, signer_pubkey, signer_id, sig }) + } + + /// The identity of a list's bytes (§4.2). + pub fn identity_of(bytes: &[u8]) -> [u8; 32] { + let mut h = blake3::Hasher::new(); + h.update(LIST_ID_DOMAIN); + h.update(bytes); + *h.finalize().as_bytes() + } + + pub fn identity(&self) -> [u8; 32] { + Self::identity_of(&self.to_bytes()) + } + + /// The key with this KID. + pub fn key(&self, kid: &[u8; 16]) -> Option<&ListKey> { + self.keys.binary_search_by(|k| k.kid.cmp(kid)).ok().map(|i| &self.keys[i]) + } +} + +// --------------------------------------------------------------------------------------- +// §5: the entitlement +// --------------------------------------------------------------------------------------- + +/// An entitlement, `WKE1` (§5.1). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Entitlement { + pub session: [u8; 16], + pub holder: [u8; 32], + pub entitlement: String, + pub not_before: u64, + pub not_after: u64, + pub offline_s: u64, + pub renewals: u32, + pub authority_pubkey: [u8; 32], + pub authority_id: String, + pub sig: [u8; 64], +} + +impl Entitlement { + fn body(&self) -> Vec { + let mut o = ENTITLEMENT_MAGIC.to_vec(); + o.push(RECORD_VERSION); + o.extend_from_slice(&self.session); + o.extend_from_slice(&self.holder); + string(&mut o, &self.entitlement); + o.extend_from_slice(&self.not_before.to_be_bytes()); + o.extend_from_slice(&self.not_after.to_be_bytes()); + o.extend_from_slice(&self.offline_s.to_be_bytes()); + o.extend_from_slice(&self.renewals.to_be_bytes()); + o.extend_from_slice(&self.authority_pubkey); + string(&mut o, &self.authority_id); + o + } + + fn signed(&self) -> Vec { + let mut m = ENTITLEMENT_DOMAIN.to_vec(); + m.extend_from_slice(&self.body()); + m + } + + pub fn to_bytes(&self) -> Vec { + let mut o = self.body(); + o.extend_from_slice(&self.sig); + o + } + + /// Issue an entitlement under `authority`. + #[allow(clippy::too_many_arguments)] + pub fn seal( + authority: &SigningKey, + authority_id: &str, + session: [u8; 16], + holder: [u8; 32], + entitlement: &str, + not_before: u64, + not_after: u64, + offline_s: u64, + renewals: u32, + ) -> Entitlement { + let mut e = Entitlement { + session, + holder, + entitlement: entitlement.into(), + not_before, + not_after, + offline_s, + renewals, + authority_pubkey: [0; 32], + authority_id: authority_id.into(), + sig: [0; 64], + }; + e.sign(authority); + e + } + + pub fn sign(&mut self, authority: &SigningKey) { + self.authority_pubkey = authority.verifying_key().to_bytes(); + self.sig = authority.sign(&self.signed()).to_bytes(); + } + + /// Read an entitlement's layout; [`Entitlement::holds`] applies its rules. + pub fn read(b: &[u8]) -> Option { + let mut r = Rd::new(b); + if r.arr::<4>()? != ENTITLEMENT_MAGIC || r.u8()? != RECORD_VERSION { + return None; + } + let e = Entitlement { + session: r.arr()?, + holder: r.arr()?, + entitlement: r.string()?, + not_before: r.u64()?, + not_after: r.u64()?, + offline_s: r.u64()?, + renewals: r.u32()?, + authority_pubkey: r.arr()?, + authority_id: r.string()?, + sig: r.arr()?, + }; + (r.rest() == 0).then_some(e) + } + + /// §5.2: the entitlement holds. + pub fn holds(&self) -> bool { + !self.entitlement.is_empty() + && self.not_before <= self.not_after + && (self.offline_s > 0 || self.renewals == 0) + && verifies(&self.authority_pubkey, &self.signed(), &self.sig) + } + + /// An entitlement's digest (§5.2): SHA-256 of its bytes. + pub fn digest(bytes: &[u8]) -> [u8; 32] { + sha256(bytes) + } +} + +// --------------------------------------------------------------------------------------- +// §6: the request +// --------------------------------------------------------------------------------------- + +/// A request's kind (§6.1). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum RequestKind { + Streaming, + Offline, + Renewal, +} + +impl RequestKind { + pub const ALL: [RequestKind; 3] = [RequestKind::Streaming, RequestKind::Offline, RequestKind::Renewal]; + + pub fn code(self) -> u8 { + match self { + RequestKind::Streaming => 1, + RequestKind::Offline => 2, + RequestKind::Renewal => 3, + } + } + + pub fn label(self) -> &'static str { + match self { + RequestKind::Streaming => "streaming", + RequestKind::Offline => "offline", + RequestKind::Renewal => "renewal", + } + } + + pub fn from_label(s: &str) -> Option { + RequestKind::ALL.into_iter().find(|k| k.label() == s) + } +} + +/// What a request asks for (§6.1). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Request { + pub content_id: String, + pub delivery: Delivery, + /// Ascending, distinct, from 1 to [`MAX_REQUEST_KEYS`]. + pub kids: Vec<[u8; 16]>, + pub kind: RequestKind, + pub renews: Option<[u8; 32]>, + pub request_sha256: [u8; 32], + pub request_t: u64, +} + +/// The bytes the holder key signs (§6.1). +pub fn proof_payload(entitlement: &[u8], r: &Request) -> Vec { + let mut o = REQUEST_DOMAIN.to_vec(); + o.extend_from_slice(&Entitlement::digest(entitlement)); + string(&mut o, &r.content_id); + r.delivery.write(&mut o); + o.extend_from_slice(&(r.kids.len() as u32).to_be_bytes()); + for k in &r.kids { + o.extend_from_slice(k); + } + o.push(r.kind.code()); + opt(&mut o, r.renews.as_ref().map(|x| &x[..])); + o.extend_from_slice(&r.request_sha256); + o.extend_from_slice(&r.request_t.to_be_bytes()); + o +} + +/// The device's proof over a request (§6.1). +pub fn prove(holder: &SigningKey, entitlement: &[u8], r: &Request) -> [u8; 64] { + holder.sign(&proof_payload(entitlement, r)).to_bytes() +} + +/// The KIDs of a `WAI-Key-Ids` field (§6.2), ascending; `None` names nothing to decide. +pub fn parse_kids(field: &str) -> Option> { + let mut kids = Vec::new(); + for p in field.split(',') { + let p = p.trim_matches(|c| c == ' ' || c == '\t'); + kids.push(unhex::<16>(p)?); + } + kids.sort_unstable(); + let n = kids.len(); + kids.dedup(); + ((1..=MAX_REQUEST_KEYS).contains(&n) && kids.len() == n).then_some(kids) +} + +// --------------------------------------------------------------------------------------- +// §7: the decision +// --------------------------------------------------------------------------------------- + +/// A release decision (§7.2). Codes 1 to 3 are form 1's. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Decision { + Granted, + DeniedNoEntitlement, + DeniedExpired, + DeniedEntitlementInvalid, + DeniedStale, + DeniedReplayed, + DeniedUnknownKey, + DeniedNotYetValid, + DeniedPeriodOutside, + DeniedOfflineNotEntitled, + DeniedRenewalUnknown, + DeniedRenewalsExhausted, + DeniedRenewalLapsed, +} + +impl Decision { + /// In code order. + pub const ALL: [Decision; 13] = [ + Decision::Granted, + Decision::DeniedNoEntitlement, + Decision::DeniedExpired, + Decision::DeniedEntitlementInvalid, + Decision::DeniedStale, + Decision::DeniedReplayed, + Decision::DeniedUnknownKey, + Decision::DeniedNotYetValid, + Decision::DeniedPeriodOutside, + Decision::DeniedOfflineNotEntitled, + Decision::DeniedRenewalUnknown, + Decision::DeniedRenewalsExhausted, + Decision::DeniedRenewalLapsed, + ]; + + pub fn code(self) -> u8 { + Decision::ALL.iter().position(|d| *d == self).expect("every decision is listed") as u8 + 1 + } + + pub fn from_code(c: u8) -> Option { + Decision::ALL.get((c as usize).checked_sub(1)?).copied() + } + + pub fn label(self) -> &'static str { + match self { + Decision::Granted => "Granted", + Decision::DeniedNoEntitlement => "DeniedNoEntitlement", + Decision::DeniedExpired => "DeniedExpired", + Decision::DeniedEntitlementInvalid => "DeniedEntitlementInvalid", + Decision::DeniedStale => "DeniedStale", + Decision::DeniedReplayed => "DeniedReplayed", + Decision::DeniedUnknownKey => "DeniedUnknownKey", + Decision::DeniedNotYetValid => "DeniedNotYetValid", + Decision::DeniedPeriodOutside => "DeniedPeriodOutside", + Decision::DeniedOfflineNotEntitled => "DeniedOfflineNotEntitled", + Decision::DeniedRenewalUnknown => "DeniedRenewalUnknown", + Decision::DeniedRenewalsExhausted => "DeniedRenewalsExhausted", + Decision::DeniedRenewalLapsed => "DeniedRenewalLapsed", + } + } + + pub fn from_label(s: &str) -> Option { + Decision::ALL.into_iter().find(|d| d.label() == s) + } +} + +/// The receipts earlier in a proxy's log (§7.1). +pub trait Log { + /// The earlier receipt with this identity. + fn by_identity(&self, id: &[u8; 32]) -> Option<&ProxyReceipt>; + /// Whether an earlier receipt carries this proof. + fn proof_seen(&self, proof: &[u8; 64]) -> bool; +} + +impl Log for [ProxyReceipt] { + fn by_identity(&self, id: &[u8; 32]) -> Option<&ProxyReceipt> { + self.iter().find(|r| r.identity() == *id) + } + fn proof_seen(&self, proof: &[u8; 64]) -> bool { + self.iter().any(|r| r.proof.as_ref() == Some(proof)) + } +} + +/// A log held in memory with its two indexes: what a proxy holds (§9.5). +#[derive(Default)] +pub struct LogIndex { + receipts: Vec, + ids: HashMap<[u8; 32], usize>, + proofs: HashSet<[u8; 64]>, +} + +impl LogIndex { + pub fn push(&mut self, r: ProxyReceipt) { + self.ids.insert(r.identity(), self.receipts.len()); + if let Some(p) = r.proof { + self.proofs.insert(p); + } + self.receipts.push(r); + } + pub fn len(&self) -> usize { + self.receipts.len() + } + pub fn is_empty(&self) -> bool { + self.receipts.is_empty() + } + pub fn receipts(&self) -> &[ProxyReceipt] { + &self.receipts + } +} + +impl Log for LogIndex { + fn by_identity(&self, id: &[u8; 32]) -> Option<&ProxyReceipt> { + self.ids.get(id).map(|&i| &self.receipts[i]) + } + fn proof_seen(&self, proof: &[u8; 64]) -> bool { + self.proofs.contains(proof) + } +} + +/// A decision and the licence it permits (§7.3). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Outcome { + pub decision: Decision, + /// For a `Granted` offline licence or renewal. + pub licence_expiry: Option, + pub renewal: Option, +} + +impl Outcome { + fn denied(decision: Decision) -> Outcome { + Outcome { decision, licence_expiry: None, renewal: None } + } +} + +/// §7.2 and §7.3: the decision of a request. +pub fn decide( + authorities: &[[u8; 32]], + entitlement: Option<&[u8]>, + proof: Option<&[u8; 64]>, + req: &Request, + list: Option<&CommitmentList>, + issued: u64, + log: &L, +) -> Outcome { + use Decision::*; + // Check 1. + let (e, proof) = match (entitlement, proof) { + (Some(b), Some(p)) => match Entitlement::read(b) { + Some(e) + if e.holds() + && authorities.contains(&e.authority_pubkey) + && verifies(&e.holder, &proof_payload(b, req), p) => + { + (e, p) + } + _ => return Outcome::denied(DeniedEntitlementInvalid), + }, + _ => return Outcome::denied(DeniedEntitlementInvalid), + }; + if issued.abs_diff(req.request_t) > FRESHNESS_S { + return Outcome::denied(DeniedStale); + } + if log.proof_seen(proof) { + return Outcome::denied(DeniedReplayed); + } + let Some(list) = list else { return Outcome::denied(DeniedUnknownKey) }; + let mut keys = Vec::with_capacity(req.kids.len()); + for kid in &req.kids { + match list.key(kid) { + Some(k) => keys.push(k), + None => return Outcome::denied(DeniedUnknownKey), + } + } + if e.entitlement != list.required_entitlement { + return Outcome::denied(DeniedNoEntitlement); + } + if issued < e.not_before { + return Outcome::denied(DeniedNotYetValid); + } + if issued > e.not_after { + return Outcome::denied(DeniedExpired); + } + for k in &keys { + if let Some(Period { window: Some((start, end)), .. }) = k.period + && (start > e.not_after || end <= e.not_before) + { + return Outcome::denied(DeniedPeriodOutside); + } + } + if req.kind != RequestKind::Streaming && e.offline_s == 0 { + return Outcome::denied(DeniedOfflineNotEntitled); + } + let licence_expiry = issued.saturating_add(e.offline_s).min(e.not_after); + match req.kind { + RequestKind::Streaming => Outcome { decision: Granted, licence_expiry: None, renewal: None }, + RequestKind::Offline => Outcome { decision: Granted, licence_expiry: Some(licence_expiry), renewal: Some(0) }, + RequestKind::Renewal => { + let prior = req.renews.as_ref().and_then(|id| log.by_identity(id)).filter(|p| { + p.released + && p.kind != RequestKind::Streaming + && p.content_id == req.content_id + && p.kids() == req.kids + && p.entitlement.as_deref().and_then(Entitlement::read).is_some_and(|pe| pe.session == e.session && pe.holder == e.holder) + }); + let Some(prior) = prior else { return Outcome::denied(DeniedRenewalUnknown) }; + let prior_renewal = prior.renewal.unwrap_or(0); + if prior_renewal as u64 + 1 > e.renewals as u64 { + return Outcome::denied(DeniedRenewalsExhausted); + } + if prior.licence_expiry.is_none_or(|x| issued > x) { + return Outcome::denied(DeniedRenewalLapsed); + } + Outcome { decision: Granted, licence_expiry: Some(licence_expiry), renewal: Some(prior_renewal + 1) } + } + } +} + +// --------------------------------------------------------------------------------------- +// §8: the proxy form of the receipt +// --------------------------------------------------------------------------------------- + +/// One requested key in a receipt (§8.2). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ReceiptKey { + pub kid: [u8; 16], + pub scheme: Option, + pub period: Option, + pub key_commit: Option<[u8; 32]>, +} + +/// A form-2 `wai.video.keys` receipt (§8). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ProxyReceipt { + pub content_id: String, + pub list: Option<[u8; 32]>, + pub cpix_sha256: Option<[u8; 32]>, + pub required_entitlement: Option, + pub delivery: Delivery, + pub keys: Vec, + pub kind: RequestKind, + pub renews: Option<[u8; 32]>, + pub entitlement: Option>, + pub request_sha256: [u8; 32], + pub request_t: u64, + pub proof: Option<[u8; 64]>, + pub issued: u64, + pub decision: Decision, + pub licence_expiry: Option, + pub renewal: Option, + pub upstream_status: Option, + pub response_sha256: Option<[u8; 32]>, + pub released: bool, + pub joules_micro: u64, + pub energy_provenance: Option, + pub signer_pubkey: [u8; 32], + pub signer_id: String, + pub sig: [u8; 64], +} + +/// The licence server's answer to a forwarded request (§9.2). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Upstream { + pub status: u16, + pub response_sha256: [u8; 32], +} + +impl ProxyReceipt { + /// Assemble and sign the receipt of a decided request (§9.1). `upstream` is the + /// licence server's answer for a forwarded `drm` request, and `None` otherwise; the + /// release, the key commitments and the licence fields follow §8.4 rules 7 to 9. + #[allow(clippy::too_many_arguments)] + pub fn assemble( + signer: &SigningKey, + signer_id: &str, + req: &Request, + list: Option<&CommitmentList>, + entitlement: Option>, + proof: Option<[u8; 64]>, + issued: u64, + outcome: &Outcome, + upstream: Option, + joules_micro: u64, + energy_provenance: Option, + ) -> ProxyReceipt { + let granted = outcome.decision == Decision::Granted; + let released = granted + && match req.delivery.kind { + DeliveryKind::Drm => upstream.is_some_and(|u| (200..=299).contains(&u.status)), + _ => true, + }; + let keys = req + .kids + .iter() + .map(|kid| { + let k = list.and_then(|l| l.key(kid)); + ReceiptKey { + kid: *kid, + scheme: k.map(|k| k.scheme), + period: k.and_then(|k| k.period), + key_commit: if released { k.map(|k| k.key_commit) } else { None }, + } + }) + .collect(); + let upstream = if granted && req.delivery.kind == DeliveryKind::Drm { upstream } else { None }; + let licence = released && req.kind != RequestKind::Streaming; + let mut r = ProxyReceipt { + content_id: req.content_id.clone(), + list: list.map(|l| l.identity()), + cpix_sha256: list.and_then(|l| l.cpix), + required_entitlement: list.map(|l| l.required_entitlement.clone()), + delivery: req.delivery, + keys, + kind: req.kind, + renews: req.renews, + entitlement, + request_sha256: req.request_sha256, + request_t: req.request_t, + proof, + issued, + decision: outcome.decision, + licence_expiry: if licence { outcome.licence_expiry } else { None }, + renewal: if licence { outcome.renewal } else { None }, + upstream_status: upstream.map(|u| u.status), + response_sha256: upstream.map(|u| u.response_sha256), + released, + joules_micro, + energy_provenance, + signer_pubkey: [0; 32], + signer_id: signer_id.into(), + sig: [0; 64], + }; + r.sign(signer); + r + } + + /// Sign the receipt again under `signer`. + pub fn sign(&mut self, signer: &SigningKey) { + self.signer_pubkey = signer.verifying_key().to_bytes(); + self.sig = signer.sign(&self.signing_payload()).to_bytes(); + } + + /// The request the receipt decided (§6.1). + pub fn request(&self) -> Request { + Request { + content_id: self.content_id.clone(), + delivery: self.delivery, + kids: self.kids(), + kind: self.kind, + renews: self.renews, + request_sha256: self.request_sha256, + request_t: self.request_t, + } + } + + pub fn kids(&self) -> Vec<[u8; 16]> { + self.keys.iter().map(|k| k.kid).collect() + } + + /// The signing payload (§8.3). + pub fn signing_payload(&self) -> Vec { + let mut o = RECEIPT_DOMAIN.to_vec(); + string(&mut o, &self.content_id); + opt(&mut o, self.list.as_ref().map(|x| &x[..])); + opt(&mut o, self.cpix_sha256.as_ref().map(|x| &x[..])); + match &self.required_entitlement { + None => o.push(0), + Some(s) => { + o.push(1); + string(&mut o, s); + } + } + self.delivery.write(&mut o); + o.extend_from_slice(&(self.keys.len() as u32).to_be_bytes()); + for k in &self.keys { + o.extend_from_slice(&k.kid); + o.push(k.scheme.map_or(0, Scheme::code)); + write_period(k.period.as_ref(), &mut o); + opt(&mut o, k.key_commit.as_ref().map(|x| &x[..])); + } + o.push(self.kind.code()); + opt(&mut o, self.renews.as_ref().map(|x| &x[..])); + match &self.entitlement { + None => o.push(0), + Some(e) => { + o.push(1); + o.extend_from_slice(&(e.len() as u32).to_be_bytes()); + o.extend_from_slice(e); + } + } + o.extend_from_slice(&self.request_sha256); + o.extend_from_slice(&self.request_t.to_be_bytes()); + opt(&mut o, self.proof.as_ref().map(|x| &x[..])); + o.extend_from_slice(&self.issued.to_be_bytes()); + o.push(self.decision.code()); + opt(&mut o, self.licence_expiry.map(u64::to_be_bytes).as_ref().map(|x| &x[..])); + opt(&mut o, self.renewal.map(u32::to_be_bytes).as_ref().map(|x| &x[..])); + opt(&mut o, self.upstream_status.map(u16::to_be_bytes).as_ref().map(|x| &x[..])); + opt(&mut o, self.response_sha256.as_ref().map(|x| &x[..])); + o.push(self.released as u8); + o.extend_from_slice(&self.joules_micro.to_be_bytes()); + write_optional(self.energy_provenance.as_ref(), &mut o); + o.extend_from_slice(&self.signer_pubkey); + string(&mut o, &self.signer_id); + o + } + + /// The receipt's identity (§8.3). + pub fn identity(&self) -> [u8; 32] { + let mut h = blake3::Hasher::new(); + h.update(RECEIPT_ID_DOMAIN); + h.update(&self.signing_payload()); + h.update(&self.sig); + *h.finalize().as_bytes() + } + + /// Its own rules (§8.4), in their order. + pub fn check(&self) -> Result<(), Reason> { + use Reason::*; + // Rule 1. + crate::sig::verify_strict(&self.signer_pubkey, &self.signing_payload(), &self.sig).map_err(|e| match e { + crate::sig::SigError::BadKey => BadKey, + crate::sig::SigError::BadSignature => BadSignature, + })?; + let invalid = |ok: bool| if ok { Ok(()) } else { Err(ReceiptInvalid) }; + // Rule 2. + invalid((1..=MAX_REQUEST_KEYS).contains(&self.keys.len()) && self.keys.windows(2).all(|w| w[0].kid < w[1].kid))?; + // Rule 3. + let drm = self.delivery.kind == DeliveryKind::Drm; + invalid(self.delivery.system_id.is_some() == drm && self.delivery.system_id != Some([0; 16]))?; + invalid(self.delivery.kind != DeliveryKind::HlsKeyUri || self.keys.len() == 1)?; + // Rule 4. + if self.keys.iter().filter_map(|k| k.scheme).any(|s| !self.delivery.kind.carries(s)) { + return Err(SchemeDeliveryMismatch); + } + // Rule 5. + if self.list.is_none() { + invalid( + self.cpix_sha256.is_none() + && self.required_entitlement.is_none() + && self.keys.iter().all(|k| k.scheme.is_none() && k.period.is_none() && k.key_commit.is_none()), + )?; + } else { + invalid(self.required_entitlement.is_some())?; + } + // Rule 6. + invalid(self.entitlement.is_some() == self.proof.is_some())?; + invalid(self.entitlement.is_some() || self.decision == Decision::DeniedEntitlementInvalid)?; + invalid(self.renews.is_some() == (self.kind == RequestKind::Renewal))?; + // Rule 7. + let granted = self.decision == Decision::Granted; + invalid(!self.released || granted)?; + invalid(self.keys.iter().all(|k| k.key_commit.is_some() == self.released))?; + // Rule 8. + if drm { + invalid(self.upstream_status.is_some() == granted && self.response_sha256.is_some() == granted)?; + invalid(self.released == self.upstream_status.is_some_and(|s| (200..=299).contains(&s)))?; + invalid(self.upstream_status.is_none_or(|s| (100..=599).contains(&s)))?; + } else { + invalid(self.upstream_status.is_none() && self.response_sha256.is_none() && self.released == granted)?; + } + // Rule 9. + let licence = self.released && self.kind != RequestKind::Streaming; + invalid(self.licence_expiry.is_some() == licence && self.renewal.is_some() == licence)?; + if licence { + invalid(self.licence_expiry.is_some_and(|x| x >= self.issued))?; + invalid((self.renewal == Some(0)) == (self.kind == RequestKind::Offline))?; + } + // Rule 10. + invalid(figure_and_class_cohere(self.joules_micro, self.energy_provenance.as_ref()) && self.class_at_floor())?; + Ok(()) + } + + /// The class's declared uncertainty reaches the resolution floor, and a calibration + /// reference holds something but white space: the parts of rule 10 a verifier + /// checks beside [`figure_and_class_cohere`], written here as §8.4 states them. + fn class_at_floor(&self) -> bool { + match &self.energy_provenance { + Some(EnergyProvenance::OnChipCounter { uncertainty, .. }) => self.declared_ok(uncertainty), + Some(EnergyProvenance::CalibratedInstrument { uncertainty, calibration_ref }) => { + self.declared_ok(uncertainty) && calibration_ref.chars().any(|c| !c.is_whitespace()) + } + _ => true, + } + } + + fn declared_ok(&self, u: &Uncertainty) -> bool { + u.relative_ppm > 0 && u.window_us > 0 && self.joules_micro as u128 * u.relative_ppm as u128 >= RESOLUTION_FLOOR + } + + /// The JSON carriage (§8.2), members in the order of its table. + pub fn to_json(&self) -> serde_json::Value { + use serde_json::{json, Map, Value}; + let h = |b: Option<&[u8]>| b.map_or(Value::Null, |b| json!(hex(b))); + let mut m = Map::new(); + m.insert("form".into(), json!(2)); + m.insert("content_id".into(), json!(self.content_id)); + m.insert("list".into(), h(self.list.as_ref().map(|x| &x[..]))); + m.insert("cpix_sha256".into(), h(self.cpix_sha256.as_ref().map(|x| &x[..]))); + m.insert("required_entitlement".into(), self.required_entitlement.as_ref().map_or(Value::Null, |s| json!(s))); + m.insert( + "delivery".into(), + json!({"kind": self.delivery.kind.label(), "system_id": h(self.delivery.system_id.as_ref().map(|x| &x[..]))}), + ); + let keys: Vec = self + .keys + .iter() + .map(|k| { + json!({ + "kid": hex(&k.kid), + "scheme": k.scheme.map_or(Value::Null, |s| json!(s.label())), + "period": k.period.map_or(Value::Null, |p| json!({ + "index": p.index, + "start": p.window.map(|w| w.0), + "end": p.window.map(|w| w.1), + })), + "key_commit": h(k.key_commit.as_ref().map(|x| &x[..])), + }) + }) + .collect(); + m.insert("keys".into(), Value::Array(keys)); + m.insert("kind".into(), json!(self.kind.label())); + m.insert("renews".into(), h(self.renews.as_ref().map(|x| &x[..]))); + m.insert("entitlement".into(), h(self.entitlement.as_deref())); + m.insert("request_sha256".into(), json!(hex(&self.request_sha256))); + m.insert("request_t".into(), json!(self.request_t)); + m.insert("proof".into(), h(self.proof.as_ref().map(|x| &x[..]))); + m.insert("issued".into(), json!(self.issued)); + m.insert("decision".into(), json!(self.decision.label())); + m.insert("licence_expiry".into(), json!(self.licence_expiry)); + m.insert("renewal".into(), json!(self.renewal)); + m.insert("upstream_status".into(), json!(self.upstream_status)); + m.insert("response_sha256".into(), h(self.response_sha256.as_ref().map(|x| &x[..]))); + m.insert("released".into(), json!(self.released)); + m.insert("joules_micro".into(), json!(self.joules_micro)); + m.insert("energy_provenance".into(), self.energy_provenance.as_ref().map_or(Value::Null, |p| json!(p.label()))); + match &self.energy_provenance { + Some(EnergyProvenance::OnChipCounter { uncertainty, filtering }) => { + m.insert("energy_uncertainty".into(), json!({"relative_ppm": uncertainty.relative_ppm, "window_us": uncertainty.window_us})); + m.insert("energy_counter_filtering".into(), json!(filtering.label())); + } + Some(EnergyProvenance::CalibratedInstrument { uncertainty, calibration_ref }) => { + m.insert("energy_uncertainty".into(), json!({"relative_ppm": uncertainty.relative_ppm, "window_us": uncertainty.window_us})); + m.insert("energy_calibration_ref".into(), json!(calibration_ref)); + } + _ => {} + } + m.insert("signer_pubkey".into(), json!(hex(&self.signer_pubkey))); + m.insert("signer_id".into(), json!(self.signer_id)); + m.insert("sig".into(), json!(hex(&self.sig))); + Value::Object(m) + } + + /// Read the JSON carriage (§8.2): exactly its members, each of its type. `None` is + /// `receipt_malformed`; the rules of §8.4 are [`ProxyReceipt::check`]'s. + pub fn from_json(v: &serde_json::Value) -> Option { + use serde_json::Value; + let o = v.as_object()?; + let label = match o.get("energy_provenance")? { + Value::Null => None, + Value::String(s) => Some(s.as_str()), + _ => return None, + }; + let mut want: Vec<&str> = RECEIPT_MEMBERS.to_vec(); + match label { + Some("OnChipCounter") => want.extend(["energy_uncertainty", "energy_counter_filtering"]), + Some("CalibratedInstrument") => want.extend(["energy_uncertainty", "energy_calibration_ref"]), + _ => {} + } + if o.len() != want.len() || !want.iter().all(|k| o.contains_key(*k)) { + return None; + } + if o.get("form")?.as_u64()? != 2 { + return None; + } + let uncertainty = || -> Option { + let u = o.get("energy_uncertainty")?.as_object()?; + if u.len() != 2 { + return None; + } + Some(Uncertainty { relative_ppm: u32::try_from(u.get("relative_ppm")?.as_u64()?).ok()?, window_us: u.get("window_us")?.as_u64()? }) + }; + let energy_provenance = match label { + None => None, + Some("ModelBased") => Some(EnergyProvenance::ModelBased), + Some("Estimator") => Some(EnergyProvenance::Estimator), + Some("OnChipCounter") => Some(EnergyProvenance::OnChipCounter { + uncertainty: uncertainty()?, + filtering: CounterFiltering::from_label(o.get("energy_counter_filtering")?.as_str()?)?, + }), + Some("CalibratedInstrument") => Some(EnergyProvenance::CalibratedInstrument { + uncertainty: uncertainty()?, + calibration_ref: o.get("energy_calibration_ref")?.as_str()?.to_string(), + }), + // The legacy label, and anything else, does not read in this form. + Some(_) => return None, + }; + let s = |k: &str| o.get(k)?.as_str(); + let u = |k: &str| o.get(k)?.as_u64(); + fn nullable(v: &Value, f: impl FnOnce(&Value) -> Option) -> Option> { + if v.is_null() { Some(None) } else { f(v).map(Some) } + } + let bytes = |k: &str| -> Option>> { nullable(o.get(k)?, |v| unhex_vec(v.as_str()?)) }; + let fixed32 = |k: &str| -> Option> { nullable(o.get(k)?, |v| unhex(v.as_str()?)) }; + let d = o.get("delivery")?.as_object()?; + if d.len() != 2 { + return None; + } + let delivery = Delivery { + kind: DeliveryKind::from_label(d.get("kind")?.as_str()?)?, + system_id: nullable(d.get("system_id")?, |v| unhex(v.as_str()?))?, + }; + let mut keys = Vec::new(); + for k in o.get("keys")?.as_array()? { + let k = k.as_object()?; + if k.len() != 4 { + return None; + } + let period = nullable(k.get("period")?, |p| { + let p = p.as_object()?; + if p.len() != 3 { + return None; + } + let index = nullable(p.get("index")?, |v| u32::try_from(v.as_u64()?).ok())?; + let start = nullable(p.get("start")?, Value::as_u64)?; + let end = nullable(p.get("end")?, Value::as_u64)?; + let window = match (start, end) { + (None, None) => None, + (Some(s), Some(e)) => Some((s, e)), + _ => return None, + }; + (index.is_some() || window.is_some()).then_some(Period { index, window }) + })?; + keys.push(ReceiptKey { + kid: unhex(k.get("kid")?.as_str()?)?, + scheme: nullable(k.get("scheme")?, |v| Scheme::from_label(v.as_str()?))?, + period, + key_commit: nullable(k.get("key_commit")?, |v| unhex(v.as_str()?))?, + }); + } + Some(ProxyReceipt { + content_id: s("content_id")?.to_string(), + list: fixed32("list")?, + cpix_sha256: fixed32("cpix_sha256")?, + required_entitlement: nullable(o.get("required_entitlement")?, |v| v.as_str().map(String::from))?, + delivery, + keys, + kind: RequestKind::from_label(s("kind")?)?, + renews: fixed32("renews")?, + entitlement: bytes("entitlement")?, + request_sha256: unhex(s("request_sha256")?)?, + request_t: u("request_t")?, + proof: nullable(o.get("proof")?, |v| unhex(v.as_str()?))?, + issued: u("issued")?, + decision: Decision::from_label(s("decision")?)?, + licence_expiry: nullable(o.get("licence_expiry")?, Value::as_u64)?, + renewal: nullable(o.get("renewal")?, |v| u32::try_from(v.as_u64()?).ok())?, + upstream_status: nullable(o.get("upstream_status")?, |v| u16::try_from(v.as_u64()?).ok())?, + response_sha256: fixed32("response_sha256")?, + released: o.get("released")?.as_bool()?, + joules_micro: u("joules_micro")?, + energy_provenance, + signer_pubkey: unhex(s("signer_pubkey")?)?, + signer_id: s("signer_id")?.to_string(), + sig: unhex(s("sig")?)?, + }) + } +} + +/// The members every form-2 receipt carries (§8.2); a class with evidence adds its own. +const RECEIPT_MEMBERS: [&str; 25] = [ + "form", + "content_id", + "list", + "cpix_sha256", + "required_entitlement", + "delivery", + "keys", + "kind", + "renews", + "entitlement", + "request_sha256", + "request_t", + "proof", + "issued", + "decision", + "licence_expiry", + "renewal", + "upstream_status", + "response_sha256", + "released", + "joules_micro", + "energy_provenance", + "signer_pubkey", + "signer_id", + "sig", +]; + +// --------------------------------------------------------------------------------------- +// §9.3: the release ticket +// --------------------------------------------------------------------------------------- + +/// A release ticket, `WKT1` (§9.3). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Ticket { + pub receipt: [u8; 32], + pub delivery: DeliveryKind, + /// 1 `temporary`, 2 `persistent-license`. + pub session_type: u8, + pub kids: Vec<[u8; 16]>, + pub expires: u64, + pub proxy_pubkey: [u8; 32], + pub sig: [u8; 64], +} + +impl Ticket { + fn body(&self) -> Vec { + let mut o = TICKET_MAGIC.to_vec(); + o.push(RECORD_VERSION); + o.extend_from_slice(&self.receipt); + o.push(self.delivery.code()); + o.push(self.session_type); + o.extend_from_slice(&(self.kids.len() as u32).to_be_bytes()); + for k in &self.kids { + o.extend_from_slice(k); + } + o.extend_from_slice(&self.expires.to_be_bytes()); + o.extend_from_slice(&self.proxy_pubkey); + o + } + + fn signed(&self) -> Vec { + let mut m = TICKET_DOMAIN.to_vec(); + m.extend_from_slice(&self.body()); + m + } + + pub fn to_bytes(&self) -> Vec { + let mut o = self.body(); + o.extend_from_slice(&self.sig); + o + } + + /// The ticket of a released clear-delivery receipt, living `ttl_s` seconds. + pub fn for_receipt(signer: &SigningKey, r: &ProxyReceipt, ttl_s: u64) -> Option { + if !r.released || !r.delivery.kind.is_clear() || !(1..=MAX_TICKET_S).contains(&ttl_s) { + return None; + } + let mut t = Ticket { + receipt: r.identity(), + delivery: r.delivery.kind, + session_type: if r.kind == RequestKind::Streaming { 1 } else { 2 }, + kids: r.kids(), + expires: r.issued + ttl_s, + proxy_pubkey: [0; 32], + sig: [0; 64], + }; + t.sign(signer); + Some(t) + } + + pub fn sign(&mut self, signer: &SigningKey) { + self.proxy_pubkey = signer.verifying_key().to_bytes(); + self.sig = signer.sign(&self.signed()).to_bytes(); + } + + /// Read a ticket: its layout (§9.3) and its signature. + pub fn parse(b: &[u8]) -> Option { + let mut r = Rd::new(b); + if r.arr::<4>()? != TICKET_MAGIC || r.u8()? != RECORD_VERSION { + return None; + } + let receipt = r.arr()?; + let delivery = DeliveryKind::from_code(r.u8()?).filter(|d| d.is_clear())?; + let session_type = r.u8()?; + let n = r.u32()? as usize; + if !(1..=2).contains(&session_type) || !(1..=MAX_REQUEST_KEYS).contains(&n) { + return None; + } + let mut kids: Vec<[u8; 16]> = Vec::with_capacity(n); + for _ in 0..n { + let k = r.arr()?; + if kids.last().is_some_and(|p| *p >= k) { + return None; + } + kids.push(k); + } + let t = Ticket { receipt, delivery, session_type, kids, expires: r.u64()?, proxy_pubkey: r.arr()?, sig: r.arr()? }; + (r.rest() == 0 && verifies(&t.proxy_pubkey, &t.signed(), &t.sig)).then_some(t) + } + + /// Whether the ticket follows the receipt it names (§10.2 step 4). + pub fn follows(&self, r: &ProxyReceipt) -> bool { + r.released + && r.delivery.kind.is_clear() + && self.receipt == r.identity() + && self.delivery == r.delivery.kind + && self.session_type == if r.kind == RequestKind::Streaming { 1 } else { 2 } + && self.kids == r.kids() + && self.proxy_pubkey == r.signer_pubkey + && self.expires > r.issued + && self.expires - r.issued <= MAX_TICKET_S + } +} + +// --------------------------------------------------------------------------------------- +// §10, §11: verification and its reasons +// --------------------------------------------------------------------------------------- + +/// A reason of §11. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Reason { + ReceiptMalformed, + BadKey, + BadSignature, + ReceiptInvalid, + SchemeDeliveryMismatch, + SignerUnknown, + ReceiptDuplicate, + ListMalformed, + ListBadSignature, + ListUnknown, + ListMismatch, + CpixMalformed, + CpixUnsupported, + CpixMismatch, + DecisionMismatch, + TicketInvalid, +} + +impl Reason { + /// In §11's order. + pub const ALL: [Reason; 16] = [ + Reason::ReceiptMalformed, + Reason::BadKey, + Reason::BadSignature, + Reason::ReceiptInvalid, + Reason::SchemeDeliveryMismatch, + Reason::SignerUnknown, + Reason::ReceiptDuplicate, + Reason::ListMalformed, + Reason::ListBadSignature, + Reason::ListUnknown, + Reason::ListMismatch, + Reason::CpixMalformed, + Reason::CpixUnsupported, + Reason::CpixMismatch, + Reason::DecisionMismatch, + Reason::TicketInvalid, + ]; + + pub fn code(self) -> &'static str { + match self { + Reason::ReceiptMalformed => "receipt_malformed", + Reason::BadKey => "bad_key", + Reason::BadSignature => "bad_signature", + Reason::ReceiptInvalid => "receipt_invalid", + Reason::SchemeDeliveryMismatch => "scheme_delivery_mismatch", + Reason::SignerUnknown => "signer_unknown", + Reason::ReceiptDuplicate => "receipt_duplicate", + Reason::ListMalformed => "list_malformed", + Reason::ListBadSignature => "list_bad_signature", + Reason::ListUnknown => "list_unknown", + Reason::ListMismatch => "list_mismatch", + Reason::CpixMalformed => "cpix_malformed", + Reason::CpixUnsupported => "cpix_unsupported", + Reason::CpixMismatch => "cpix_mismatch", + Reason::DecisionMismatch => "decision_mismatch", + Reason::TicketInvalid => "ticket_invalid", + } + } +} + +/// What a verifier is given (§10.1). +pub struct LogCase<'a> { + pub authorities: &'a [[u8; 32]], + pub proxies: &'a [[u8; 32]], + pub lists: &'a [Vec], + /// Each CPIX document with the index, in `lists`, of the list derived from it. + pub cpix: &'a [(usize, Vec)], + pub receipts: &'a [serde_json::Value], + pub tickets: &'a [Vec], +} + +/// What an accepted log states of one receipt (§10.3). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Facts { + pub identity: [u8; 32], + pub decision: Decision, + pub released: bool, + /// Each released key's KID and commitment. + pub keys: Vec<([u8; 16], [u8; 32])>, + pub licence_expiry: Option, + pub renewal: Option, +} + +/// A refused log: the reason and its place (§10.2). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Refusal { + pub reason: Reason, + /// `list`, `cpix`, `receipt` or `ticket`. + pub place: &'static str, + pub index: usize, +} + +/// Verify a proxy's log (§10.2). +pub fn verify_log(c: &LogCase) -> Result, Refusal> { + let refuse = |reason, place, index| Refusal { reason, place, index }; + // Step 1. + let mut lists = Vec::with_capacity(c.lists.len()); + let mut by_content: BTreeMap = BTreeMap::new(); + for (i, b) in c.lists.iter().enumerate() { + let l = CommitmentList::parse(b).map_err(|f| { + refuse(if f == ListFault::Malformed { Reason::ListMalformed } else { Reason::ListBadSignature }, "list", i) + })?; + if by_content.insert(l.content_id.clone(), i).is_some() { + return Err(refuse(Reason::ListMalformed, "list", i)); + } + lists.push(l); + } + let by_identity: HashMap<[u8; 32], usize> = lists.iter().enumerate().map(|(i, l)| (l.identity(), i)).collect(); + // Step 2. + for (j, (li, doc)) in c.cpix.iter().enumerate() { + let d = cpix::derive(doc).map_err(|e| { + refuse(if e == cpix::CpixError::Malformed { Reason::CpixMalformed } else { Reason::CpixUnsupported }, "cpix", j) + })?; + let l = lists.get(*li).ok_or(refuse(Reason::CpixMismatch, "cpix", j))?; + if l.content_id != d.content_id || l.cpix != Some(d.sha256) || l.keys != d.list_keys() { + return Err(refuse(Reason::CpixMismatch, "cpix", j)); + } + } + // Step 3. + let mut log: Vec = Vec::with_capacity(c.receipts.len()); + let mut seen = HashSet::new(); + let mut facts = Vec::with_capacity(c.receipts.len()); + for (i, v) in c.receipts.iter().enumerate() { + let at = |reason| refuse(reason, "receipt", i); + let r = ProxyReceipt::from_json(v).ok_or(at(Reason::ReceiptMalformed))?; + r.check().map_err(at)?; + if !c.proxies.contains(&r.signer_pubkey) { + return Err(at(Reason::SignerUnknown)); + } + let id = r.identity(); + if !seen.insert(id) { + return Err(at(Reason::ReceiptDuplicate)); + } + let list = match r.list { + Some(lid) => { + let l = &lists[*by_identity.get(&lid).ok_or(at(Reason::ListUnknown))?]; + if l.content_id != r.content_id { + return Err(at(Reason::ListMismatch)); + } + Some(l) + } + None => { + if by_content.contains_key(&r.content_id) { + return Err(at(Reason::ListMismatch)); + } + None + } + }; + if let Some(l) = list { + let same = r.cpix_sha256 == l.cpix + && r.required_entitlement.as_deref() == Some(l.required_entitlement.as_str()) + && r.keys.iter().all(|k| match l.key(&k.kid) { + Some(lk) => k.scheme == Some(lk.scheme) && k.period == lk.period && k.key_commit.is_none_or(|c| c == lk.key_commit), + None => k.scheme.is_none() && k.period.is_none() && k.key_commit.is_none(), + }); + if !same { + return Err(at(Reason::ListMismatch)); + } + } + let outcome = decide(c.authorities, r.entitlement.as_deref(), r.proof.as_ref(), &r.request(), list, r.issued, &log[..]); + if outcome.decision != r.decision { + return Err(at(Reason::DecisionMismatch)); + } + if r.released && r.kind != RequestKind::Streaming && (r.licence_expiry, r.renewal) != (outcome.licence_expiry, outcome.renewal) { + return Err(at(Reason::DecisionMismatch)); + } + facts.push(Facts { + identity: id, + decision: r.decision, + released: r.released, + keys: r.keys.iter().filter_map(|k| k.key_commit.map(|c| (k.kid, c))).collect(), + licence_expiry: r.licence_expiry, + renewal: r.renewal, + }); + log.push(r); + } + // Step 4. + for (j, b) in c.tickets.iter().enumerate() { + let t = Ticket::parse(b).ok_or(refuse(Reason::TicketInvalid, "ticket", j))?; + let r = log.iter().find(|r| r.identity() == t.receipt).ok_or(refuse(Reason::TicketInvalid, "ticket", j))?; + if !t.follows(r) { + return Err(refuse(Reason::TicketInvalid, "ticket", j)); + } + } + Ok(facts) +} + +#[cfg(test)] +mod tests; diff --git a/wai/wai-rs/src/key_release/corpus.rs b/wai/wai-rs/src/key_release/corpus.rs new file mode 100644 index 000000000..2596787fd --- /dev/null +++ b/wai/wai-rs/src/key_release/corpus.rs @@ -0,0 +1,730 @@ +//! The conformance corpus of key release (`live-conformance/keys/`, +//! extensions/key-release.md §12): proxy logs over CPIX-derived lists, each with the +//! outcome its verification reaches, written from fixed seeds. +//! +//! The generator and [`verify`] are one implementation. The corpus's second +//! implementation is `live-conformance/key_release_verify.py`, written from the +//! extension's text and sharing no code with this module. + +use std::collections::{BTreeMap, BTreeSet}; + +use ed25519_dalek::SigningKey; +use serde_json::{json, Value}; + +use super::cpix::{date_time_text, uuid_text, CPIX_NS, PSKC_NS}; +use super::*; + +/// 2026-10-25T00:00:00Z, the corpus's clock origin. +pub const T0: u64 = 1_792_886_400; +const DAY: u64 = 86_400; +const HOUR: u64 = 3_600; + +/// Three system IDs of the DASH-IF registry of content protection systems. +const SYSTEM_A: &str = "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; +const SYSTEM_B: &str = "9a04f079-9840-4286-ab92-e65be0885f95"; +const SYSTEM_C: &str = "94ce86fb-07ff-4f43-adb8-93d2fa968ca2"; + +const PROXY_ID: &str = "https://keys.proxy.example"; +const AUTHORITY_ID: &str = "https://entitlements.example"; +const HOLDER_ID: &str = "https://packager.example"; + +fn seed(label: &str) -> [u8; 32] { + sha256(format!("wai key-release corpus: {label}").as_bytes()) +} + +/// The principal named `name`: its Ed25519 key is SHA-256 of a fixed label. +pub fn principal(name: &str) -> SigningKey { + SigningKey::from_bytes(&seed(&format!("key {name}"))) +} + +fn pubkey(name: &str) -> [u8; 32] { + principal(name).verifying_key().to_bytes() +} + +/// A KID: the first 16 bytes of SHA-256 of a fixed label. +pub fn kid(label: &str) -> [u8; 16] { + seed(&format!("kid {label}"))[..16].try_into().unwrap() +} + +/// A content key of `len` octets: SHA-256 of a fixed label, truncated. +pub fn content_key(label: &str, len: usize) -> Vec { + let mut k = seed(&format!("content key {label}")).to_vec(); + k.truncate(len); + k +} + +fn session(label: &str) -> [u8; 16] { + seed(&format!("session {label}"))[..16].try_into().unwrap() +} + +fn system(id: &str) -> [u8; 16] { + super::cpix::uuid(id).unwrap() +} + +/// The principals, by name. +pub const PRINCIPALS: [&str; 7] = ["key_holder", "authority", "rogue_authority", "proxy", "rogue_proxy", "device_a", "device_b"]; + +// --------------------------------------------------------------------------------------- +// CPIX documents +// --------------------------------------------------------------------------------------- + +struct DocKey { + label: &'static str, + scheme: Scheme, + period: Option<(&'static str, u32, u64, u64)>, + encrypted: bool, +} + +fn cpix_doc(content_id: &str, keys: &[DocKey], systems: &[&str]) -> String { + let mut o = String::new(); + o.push_str("\n"); + o.push_str(&format!( + "\n" + )); + o.push_str(" \n"); + for k in keys { + let key = content_key(k.label, k.scheme.key_len()); + o.push_str(&format!( + " \n \n \n", + uuid_text(&kid(k.label)), + k.scheme.label() + )); + if k.encrypted { + o.push_str(&format!( + " \n \n \n {}\n \n \n", + b64(&seed(&format!("cipher {}", k.label))) + )); + } else { + o.push_str(&format!(" {}\n", b64(&key))); + } + o.push_str(" \n \n \n"); + } + o.push_str(" \n"); + if !systems.is_empty() { + o.push_str(" \n"); + for s in systems { + for k in keys { + o.push_str(&format!(" \n", uuid_text(&kid(k.label)))); + } + } + o.push_str(" \n"); + } + if keys.iter().any(|k| k.period.is_some()) { + o.push_str(" \n"); + for (id, index, start, end) in keys.iter().filter_map(|k| k.period) { + o.push_str(&format!( + " \n", + date_time_text(start), + date_time_text(end) + )); + } + o.push_str(" \n \n"); + for k in keys { + if let Some((id, ..)) = k.period { + o.push_str(&format!( + " \n \n \n", + uuid_text(&kid(k.label)) + )); + } + } + o.push_str(" \n"); + } + o.push_str("\n"); + o +} + +fn film_keys() -> Vec { + vec![ + DocKey { label: "film/video", scheme: Scheme::Cenc, period: None, encrypted: false }, + DocKey { label: "film/audio", scheme: Scheme::Cenc, period: None, encrypted: false }, + ] +} + +/// Every CPIX document of the corpus, by file name. +pub fn documents() -> BTreeMap<&'static str, String> { + let mut d = BTreeMap::new(); + d.insert("film.cpix.xml", cpix_doc("film-0001", &film_keys(), &[SYSTEM_A, SYSTEM_B])); + d.insert( + "episode.cpix.xml", + cpix_doc("episode-0002", &[DocKey { label: "episode/main", scheme: Scheme::Aes128, period: None, encrypted: false }], &[]), + ); + let channel: Vec = (1..=3u32) + .map(|i| DocKey { + label: ["channel/p1", "channel/p2", "channel/p3"][i as usize - 1], + scheme: Scheme::Cbcs, + period: Some((["p1", "p2", "p3"][i as usize - 1], i, T0 + (i as u64 - 1) * HOUR, T0 + i as u64 * HOUR)), + encrypted: false, + }) + .collect(); + d.insert("channel.cpix.xml", cpix_doc("channel-0003", &channel, &[SYSTEM_C])); + d.insert( + "encrypted.cpix.xml", + cpix_doc("film-0005", &[DocKey { label: "sealed/video", scheme: Scheme::Cenc, period: None, encrypted: true }], &[]), + ); + // The film document with one end tag that does not match its start tag. + let film = d["film.cpix.xml"].clone(); + d.insert("broken.cpix.xml", film.replacen("", "", 1)); + d +} + +// --------------------------------------------------------------------------------------- +// Lists and entitlements +// --------------------------------------------------------------------------------------- + +fn list_from(doc: &str, required: &str) -> CommitmentList { + let d = super::cpix::derive(doc.as_bytes()).expect("a corpus document derives"); + CommitmentList::seal(&principal("key_holder"), HOLDER_ID, &d.content_id, required, Some(d.sha256), T0 - DAY, d.list_keys()) +} + +/// Every commitment list of the corpus, by name, as bytes. +pub fn lists() -> BTreeMap<&'static str, Vec> { + let docs = documents(); + let holder = principal("key_holder"); + let film = list_from(&docs["film.cpix.xml"], "svod:premium"); + let mut l = BTreeMap::new(); + l.insert("film", film.to_bytes()); + l.insert("episode", list_from(&docs["episode.cpix.xml"], "svod:basic").to_bytes()); + l.insert("channel", list_from(&docs["channel.cpix.xml"], "live:sports").to_bytes()); + let clip = ListKey { kid: kid("clip/video"), scheme: Scheme::Cens, key_commit: key_commitment(&content_key("clip/video", 16)), period: None }; + l.insert("clip", CommitmentList::seal(&holder, HOLDER_ID, "clip-0004", "svod:basic", None, T0 - DAY, vec![clip]).to_bytes()); + // The film list with one commitment that is not its key's, signed by the key holder. + let mut tampered = film.clone(); + tampered.keys[0].key_commit = key_commitment(b"a key the document does not hold"); + tampered.sign(&holder); + l.insert("film_tampered_commit", tampered.to_bytes()); + // The film list naming another document's digest. + let mut wrong = film.clone(); + wrong.cpix = Some(sha256(docs["episode.cpix.xml"].as_bytes())); + wrong.sign(&holder); + l.insert("film_wrong_digest", wrong.to_bytes()); + // A list for the document whose key is encrypted. + let sealed = ListKey { kid: kid("sealed/video"), scheme: Scheme::Cenc, key_commit: key_commitment(&content_key("sealed/video", 16)), period: None }; + l.insert( + "encrypted", + CommitmentList::seal(&holder, HOLDER_ID, "film-0005", "svod:premium", Some(sha256(docs["encrypted.cpix.xml"].as_bytes())), T0 - DAY, vec![sealed]) + .to_bytes(), + ); + let mut forged = film.to_bytes(); + let n = forged.len(); + forged[n - 1] ^= 0x01; + l.insert("film_forged", forged); + let mut trailing = film.to_bytes(); + trailing.push(0); + l.insert("film_trailing", trailing); + l +} + +struct Ents { + premium: Entitlement, + offline: Entitlement, + basic: Entitlement, + sports: Entitlement, + rogue: Entitlement, + expired: Entitlement, + future: Entitlement, +} + +fn ents() -> Ents { + let a = pubkey("device_a"); + let b = pubkey("device_b"); + let auth = principal("authority"); + let e = |s: &str, holder, class: &str, nb, na, off, ren| Entitlement::seal(&auth, AUTHORITY_ID, session(s), holder, class, nb, na, off, ren); + Ents { + premium: e("a-premium", a, "svod:premium", T0, T0 + 30 * DAY, 0, 0), + offline: e("a-offline", a, "svod:premium", T0, T0 + 7 * DAY, 2 * DAY, 1), + basic: e("b-basic", b, "svod:basic", T0, T0 + 30 * DAY, 0, 0), + sports: e("b-sports", b, "live:sports", T0, T0 + 90 * 60, 0, 0), + rogue: Entitlement::seal(&principal("rogue_authority"), "https://rogue.example", session("a-rogue"), a, "svod:premium", T0, T0 + 30 * DAY, 0, 0), + expired: e("a-expired", a, "svod:premium", T0 - 30 * DAY, T0 - DAY, 0, 0), + future: e("a-future", a, "svod:premium", T0 + DAY, T0 + 30 * DAY, 0, 0), + } +} + +// --------------------------------------------------------------------------------------- +// A proxy, simulated +// --------------------------------------------------------------------------------------- + +struct Sim { + lists: Vec, + log: Vec, +} + +struct Ask<'a> { + content: &'a str, + delivery: Delivery, + kids: Vec<[u8; 16]>, + kind: RequestKind, + renews: Option<[u8; 32]>, + body: Vec, + t: u64, + ent: Option<&'a Entitlement>, + device: &'a str, + issued: u64, + /// What the licence server answers a forwarded request: its status and body, or + /// `None` for no answer. + upstream: Option<(u16, &'a [u8])>, +} + +fn drm(sys: &str) -> Delivery { + Delivery { kind: DeliveryKind::Drm, system_id: Some(system(sys)) } +} + +fn clear(kind: DeliveryKind) -> Delivery { + Delivery { kind, system_id: None } +} + +impl Sim { + fn new(names: &[&str]) -> Sim { + let all = lists(); + Sim { lists: names.iter().map(|n| CommitmentList::parse(&all[n]).expect("a corpus list")).collect(), log: Vec::new() } + } + + fn ask(&mut self, a: Ask) -> ProxyReceipt { + let mut kids = a.kids.clone(); + kids.sort_unstable(); + let req = Request { + content_id: a.content.into(), + delivery: a.delivery, + kids, + kind: a.kind, + renews: a.renews, + request_sha256: sha256(&a.body), + request_t: a.t, + }; + let ent = a.ent.map(|e| e.to_bytes()); + let proof = ent.as_ref().map(|e| prove(&principal(a.device), e, &req)); + self.decide(&req, ent, proof, a.issued, a.upstream) + } + + fn decide(&mut self, req: &Request, ent: Option>, proof: Option<[u8; 64]>, issued: u64, upstream: Option<(u16, &[u8])>) -> ProxyReceipt { + let list = self.lists.iter().find(|l| l.content_id == req.content_id); + let outcome = decide(&[pubkey("authority")], ent.as_deref(), proof.as_ref(), req, list, issued, &self.log[..]); + let up = (outcome.decision == Decision::Granted && req.delivery.kind == DeliveryKind::Drm).then(|| match upstream { + Some((status, body)) => Upstream { status, response_sha256: sha256(body) }, + None => Upstream { status: 502, response_sha256: sha256(b"") }, + }); + let r = ProxyReceipt::assemble(&principal("proxy"), PROXY_ID, req, list, ent, proof, issued, &outcome, up, 0, None); + self.log.push(r.clone()); + r + } + + /// The same request again, a while later. + fn replay(&mut self, of: &ProxyReceipt, issued: u64) -> ProxyReceipt { + let req = of.request(); + self.decide(&req, of.entitlement.clone(), of.proof, issued, Some((200, b"licence"))) + } +} + +/// A streaming request of the film's route of system A, answered 200 when forwarded. +fn ask<'a>(ent: Option<&'a Entitlement>, device: &'a str, content: &'a str, kids: Vec<[u8; 16]>, t: u64, issued: u64, label: &str) -> Ask<'a> { + Ask { + content, + delivery: drm(SYSTEM_A), + kids, + kind: RequestKind::Streaming, + renews: None, + body: format!("licence challenge: {label}").into_bytes(), + t, + ent, + device, + issued, + upstream: Some((200, b"licence")), + } +} + +/// An offline request, or a renewal, of the film's two keys. +fn off<'a>(kind: RequestKind, renews: Option<[u8; 32]>, t: u64, ent: &'a Entitlement, label: &str) -> Ask<'a> { + Ask { + content: "film-0001", + delivery: drm(SYSTEM_A), + kids: vec![kid("film/video"), kid("film/audio")], + kind, + renews, + body: format!("licence challenge: offline {label}").into_bytes(), + t, + ent: Some(ent), + device: "device_a", + issued: t, + upstream: Some((200, b"licence: persistent")), + } +} + +fn eme_request(kids: &[[u8; 16]], session_type: &str) -> Vec { + let ids: Vec = kids.iter().map(|k| format!("\"{}\"", b64url(k))).collect(); + format!("{{\"kids\":[{}],\"type\":\"{session_type}\"}}", ids.join(",")).into_bytes() +} + +// --------------------------------------------------------------------------------------- +// The vectors +// --------------------------------------------------------------------------------------- + +/// A vector as written: its inputs and the outcome it was written to have. +struct Case { + name: &'static str, + about: &'static str, + lists: Vec<&'static str>, + cpix: Vec<(usize, &'static str)>, + proxies: Vec<&'static str>, + log: Vec, + tickets: Vec>, + /// `None` to accept; otherwise the reason and place it is refused with. + refuse: Option<(Reason, &'static str, usize)>, +} + +fn accept(name: &'static str, about: &'static str, lists: Vec<&'static str>, cpix: Vec<(usize, &'static str)>, log: &[ProxyReceipt]) -> Case { + Case { name, about, lists, cpix, proxies: vec!["proxy"], log: log.iter().map(ProxyReceipt::to_json).collect(), tickets: vec![], refuse: None } +} + +fn resigned(mut r: ProxyReceipt, f: impl FnOnce(&mut ProxyReceipt)) -> ProxyReceipt { + f(&mut r); + r.sign(&principal("proxy")); + r +} + +fn release_fields(r: &mut ProxyReceipt, list: &CommitmentList) { + r.decision = Decision::Granted; + r.released = true; + for k in &mut r.keys { + k.key_commit = list.key(&k.kid).map(|l| l.key_commit); + } + if r.delivery.kind == DeliveryKind::Drm { + r.upstream_status = Some(200); + r.response_sha256 = Some(sha256(b"licence")); + } +} + +#[allow(clippy::vec_init_then_push)] +fn cases() -> Vec { + let e = ents(); + let film = || drm(SYSTEM_A); + let video = kid("film/video"); + let audio = kid("film/audio"); + let mut cases = Vec::new(); + + // A1: streaming releases through two content protection systems. + let mut s = Sim::new(&["film"]); + let r0 = s.ask(Ask { + content: "film-0001", delivery: film(), kids: vec![video, audio], kind: RequestKind::Streaming, renews: None, + body: b"licence challenge: film, device a".to_vec(), t: T0 + 100, ent: Some(&e.premium), device: "device_a", + issued: T0 + 101, upstream: Some((200, b"licence: film, both keys")), + }); + let mut r1 = s.ask(Ask { + content: "film-0001", delivery: drm(SYSTEM_B), kids: vec![video], kind: RequestKind::Streaming, renews: None, + body: b"licence challenge: film video, device a, second system".to_vec(), t: T0 + 200, ent: Some(&e.premium), + device: "device_a", issued: T0 + 200, upstream: Some((200, b"licence: film video")), + }); + // The second receipt carries a metered figure with its class. + r1.joules_micro = 1_200; + r1.energy_provenance = Some(EnergyProvenance::OnChipCounter { + uncertainty: Uncertainty { relative_ppm: 250_000, window_us: 1_000 }, + filtering: CounterFiltering::Off, + }); + r1.sign(&principal("proxy")); + let release_drm = vec![r0.clone(), r1]; + cases.push(accept("release_drm", "streaming releases of the film's keys through two content protection systems, forwarded and answered 200; the second receipt carries a metered figure", vec!["film"], vec![(0, "film.cpix.xml")], &release_drm)); + + // A2: an HLS key file, by ticket. + let mut s = Sim::new(&["episode"]); + let h0 = s.ask(Ask { + content: "episode-0002", delivery: clear(DeliveryKind::HlsKeyUri), kids: vec![kid("episode/main")], kind: RequestKind::Streaming, + renews: None, body: vec![], t: T0 + 300, ent: Some(&e.basic), device: "device_b", issued: T0 + 301, upstream: None, + }); + let h_ticket = Ticket::for_receipt(&principal("proxy"), &h0, 60).unwrap(); + let mut c = accept("release_hls_key_uri", "an HLS AES-128 key file released by a ticket the key server redeems; the proxy never relays the key", vec!["episode"], vec![(0, "episode.cpix.xml")], std::slice::from_ref(&h0)); + c.tickets = vec![h_ticket.to_bytes()]; + cases.push(c); + + // A3: Clear Key, temporary and persistent, by ticket. + let mut s = Sim::new(&["film"]); + let k0 = s.ask(Ask { + content: "film-0001", delivery: clear(DeliveryKind::ClearKey), kids: vec![video, audio], kind: RequestKind::Streaming, renews: None, + body: eme_request(&[video, audio], "temporary"), t: T0 + 400, ent: Some(&e.premium), device: "device_a", issued: T0 + 400, upstream: None, + }); + let k1 = s.ask(Ask { + content: "film-0001", delivery: clear(DeliveryKind::ClearKey), kids: vec![video], kind: RequestKind::Offline, renews: None, + body: eme_request(&[video], "persistent-license"), t: T0 + 500, ent: Some(&e.offline), device: "device_a", issued: T0 + 500, upstream: None, + }); + let k_tickets = [Ticket::for_receipt(&principal("proxy"), &k0, 60).unwrap(), Ticket::for_receipt(&principal("proxy"), &k1, 300).unwrap()]; + let mut c = accept("release_clear_key", "Clear Key releases by ticket: a temporary licence of two keys and a persistent licence of one", vec!["film"], vec![(0, "film.cpix.xml")], &[k0.clone(), k1]); + c.tickets = k_tickets.iter().map(Ticket::to_bytes).collect(); + cases.push(c); + + // A4: refusals. + let mut s = Sim::new(&["film"]); + let mut refusals = Vec::new(); + refusals.push(s.ask(ask(None, "device_a", "film-0001", vec![video], T0 + 600, T0 + 600, "no entitlement"))); + refusals.push(s.ask(ask(Some(&e.rogue), "device_a", "film-0001", vec![video], T0 + 610, T0 + 610, "an untrusted authority"))); + refusals.push(s.ask(ask(Some(&e.premium), "device_b", "film-0001", vec![video], T0 + 620, T0 + 620, "another device's proof"))); + refusals.push(s.ask(ask(Some(&e.premium), "device_a", "film-0001", vec![video], T0 + 330, T0 + 631, "stale"))); + let granted = s.ask(ask(Some(&e.premium), "device_a", "film-0001", vec![video], T0 + 640, T0 + 640, "fresh")); + refusals.push(granted.clone()); + refusals.push(s.replay(&granted, T0 + 645)); + refusals.push(s.ask(ask(Some(&e.premium), "device_a", "film-0001", vec![video, kid("film/commentary")], T0 + 650, T0 + 650, "a key not in the list"))); + refusals.push(s.ask(ask(Some(&e.premium), "device_a", "film-9999", vec![video], T0 + 660, T0 + 660, "content with no list"))); + let no_ent = s.ask(ask(Some(&e.basic), "device_b", "film-0001", vec![video], T0 + 670, T0 + 670, "the wrong class")); + refusals.push(no_ent.clone()); + refusals.push(s.ask(ask(Some(&e.future), "device_a", "film-0001", vec![video], T0 + 680, T0 + 680, "before the entitlement"))); + cases.push(accept("refusals", "signed refusals: no entitlement, an untrusted authority, another device's proof, a stale request, a replay of a granted one, an unknown key, unknown content, the wrong class, and an entitlement not yet valid", vec!["film"], vec![], &refusals)); + + // A5: an expired entitlement. + let mut s = Sim::new(&["film"]); + let x0 = s.ask(Ask { kind: RequestKind::Streaming, ent: Some(&e.expired), t: T0 + 700, issued: T0 + 700, ..ask(None, "device_a", "film-0001", vec![video], 0, 0, "expired") }); + let x1 = s.ask(Ask { kind: RequestKind::Offline, ent: Some(&e.offline), t: T0 + 60, issued: T0 + 60, ..ask(None, "device_a", "film-0001", vec![video], 0, 0, "offline") }); + let x2 = s.ask(Ask { + kind: RequestKind::Renewal, renews: Some(x1.identity()), ent: Some(&e.offline), t: T0 + 8 * DAY, issued: T0 + 8 * DAY, + ..ask(None, "device_a", "film-0001", vec![video], 0, 0, "renewal after the entitlement") + }); + cases.push(accept("expired_entitlement", "a streaming request under an entitlement that has ended, and a renewal asked after the entitlement it renews under ended", vec!["film"], vec![(0, "film.cpix.xml")], &[x0, x1, x2])); + + // A6: key rotation over three periods. + let mut s = Sim::new(&["channel"]); + let ch = |p: &str| kid(&format!("channel/{p}")); + let rot = |kids: Vec<[u8; 16]>, t: u64, label: &str| Ask { + content: "channel-0003", delivery: drm(SYSTEM_C), kids, kind: RequestKind::Streaming, renews: None, + body: format!("licence challenge: channel {label}").into_bytes(), t, ent: Some(&e.sports), device: "device_b", issued: t, + upstream: Some((200, b"licence: channel")), + }; + let rotation = vec![ + s.ask(rot(vec![ch("p1")], T0 + 60, "p1")), + s.ask(rot(vec![ch("p2")], T0 + 3_500, "p2 ahead of the rotation")), + s.ask(rot(vec![ch("p3")], T0 + 5_000, "p3")), + s.ask(rot(vec![ch("p2"), ch("p3")], T0 + 5_010, "p2 and p3")), + ]; + cases.push(accept("rotation", "key rotation: the keys of the periods an entitlement meets are released, the next one ahead of its period; the key of a period after the entitlement ends is refused, alone or beside one that would be released", vec!["channel"], vec![(0, "channel.cpix.xml")], &rotation)); + + // A7: offline licences and their renewals. + let mut s = Sim::new(&["film"]); + let (e_off, e_prem) = (&e.offline, &e.premium); + let o0 = s.ask(off(RequestKind::Offline, None, T0 + 60, e_off, "first")); + let o1 = s.ask(off(RequestKind::Renewal, Some(o0.identity()), T0 + DAY, e_off, "renewal 1")); + let o2 = s.ask(off(RequestKind::Renewal, Some(o1.identity()), T0 + 2 * DAY, e_off, "renewal 2")); + let o3 = s.ask(off(RequestKind::Offline, None, T0 + 3 * DAY, e_off, "second")); + let o4 = s.ask(off(RequestKind::Renewal, Some(o3.identity()), T0 + 6 * DAY, e_off, "renewal after lapse")); + let o5 = s.ask(off(RequestKind::Renewal, Some([0x77; 32]), T0 + 6 * DAY + 10, e_off, "renewal of nothing")); + let o6 = s.ask(off(RequestKind::Offline, None, T0 + 6 * DAY + 20, e_prem, "not entitled offline")); + let o7 = s.ask(off(RequestKind::Offline, None, T0 + 6 * DAY + 43_200, e_off, "near the end")); + let offline = vec![o0, o1, o2, o3, o4, o5, o6, o7]; + cases.push(accept("offline", "offline licences: a release, its one permitted renewal, a renewal past the count, a renewal after the licence lapsed, a renewal of no licence, an entitlement with no offline allowance, and a licence clamped to the entitlement's end", vec!["film"], vec![], &offline)); + + // A8: the licence server refuses, or does not answer. + let mut s = Sim::new(&["film"]); + let u0 = s.ask(Ask { upstream: Some((403, b"licence server: refused")), t: T0 + 800, issued: T0 + 800, ent: Some(&e.premium), ..ask(None, "device_a", "film-0001", vec![video], 0, 0, "upstream refuses") }); + let u1 = s.ask(Ask { upstream: None, t: T0 + 810, issued: T0 + 810, ent: Some(&e.premium), ..ask(None, "device_a", "film-0001", vec![video], 0, 0, "upstream silent") }); + cases.push(accept("upstream_refused", "granted requests the licence server refused (403) or did not answer (502): nothing is released", vec!["film"], vec![], &[u0.clone(), u1])); + + // Negative controls. + let film_list = CommitmentList::parse(&lists()["film"]).unwrap(); + let neg = |name, about, lists: Vec<&'static str>, cpix: Vec<(usize, &'static str)>, log: Vec, refuse: (Reason, &'static str, usize)| Case { + name, about, lists, cpix, proxies: vec!["proxy"], log, tickets: vec![], refuse: Some(refuse), + }; + let one = |r: &ProxyReceipt| vec![r.to_json()]; + cases.push(neg("n_misreported_grant", "a refusal for the wrong class restated as a release and signed again", vec!["film"], vec![], + one(&resigned(no_ent.clone(), |r| release_fields(r, &film_list))), (Reason::DecisionMismatch, "receipt", 0))); + let mut flipped = r0.clone(); + flipped.sig[0] ^= 1; + cases.push(neg("n_forged_signature", "a release whose signature has one bit changed", vec!["film"], vec![], one(&flipped), (Reason::BadSignature, "receipt", 0))); + let forgery = &crate::sig::forgeries(|k| { + let mut r = r0.clone(); + r.signer_pubkey = *k; + r.signing_payload() + })[0]; + let mut weak = r0.clone(); + weak.signer_pubkey = forgery.key; + weak.sig = forgery.sig; + cases.push(neg("n_bad_key", "a release signed under the identity point, which a lenient verifier accepts for every message", vec!["film"], vec![], one(&weak), (Reason::BadKey, "receipt", 0))); + cases.push(neg("n_commit_not_listed", "a release whose commitment is not the list's", vec!["film"], vec![], + one(&resigned(r0.clone(), |r| r.keys[0].key_commit = Some(key_commitment(b"not the listed key")))), (Reason::ListMismatch, "receipt", 0))); + cases.push(neg("n_refusal_with_commit", "a refusal that commits to keys", vec!["film"], vec![], + one(&resigned(no_ent.clone(), |r| r.keys[0].key_commit = film_list.key(&video).map(|k| k.key_commit))), (Reason::ReceiptInvalid, "receipt", 0))); + cases.push(neg("n_forwarded_refusal", "a refusal that records the licence server's answer, so was forwarded", vec!["film"], vec![], + one(&resigned(no_ent.clone(), |r| { + r.upstream_status = Some(200); + r.response_sha256 = Some(sha256(b"licence")); + })), (Reason::ReceiptInvalid, "receipt", 0))); + cases.push(neg("n_released_on_upstream_error", "a release the licence server refused", vec!["film"], vec![], + one(&resigned(u0.clone(), |r| { + r.released = true; + for k in &mut r.keys { + k.key_commit = film_list.key(&k.kid).map(|l| l.key_commit); + } + })), (Reason::ReceiptInvalid, "receipt", 0))); + let mut long = offline.clone(); + long[7] = resigned(long[7].clone(), |r| r.licence_expiry = Some(r.issued + 2 * DAY)); + cases.push(neg("n_licence_outlives_entitlement", "an offline licence whose expiry is past the entitlement's end", vec!["film"], vec![], + long.iter().map(ProxyReceipt::to_json).collect(), (Reason::DecisionMismatch, "receipt", 7))); + let mut s = Sim::new(&["clip"]); + let cens = s.ask(Ask { + content: "clip-0004", delivery: clear(DeliveryKind::HlsKeyUri), kids: vec![kid("clip/video")], kind: RequestKind::Streaming, renews: None, + body: vec![], t: T0 + 900, ent: Some(&e.basic), device: "device_b", issued: T0 + 900, upstream: None, + }); + cases.push(neg("n_scheme_delivery_mismatch", "a cens key released as an HLS key file, which HLS defines for no such scheme", vec!["clip"], vec![], one(&cens), (Reason::SchemeDeliveryMismatch, "receipt", 0))); + cases.push(neg("n_cpix_commit_mismatch", "a list whose commitment is not its document's key", vec!["film_tampered_commit"], vec![(0, "film.cpix.xml")], vec![], (Reason::CpixMismatch, "cpix", 0))); + cases.push(neg("n_cpix_digest_mismatch", "a list naming another document's digest", vec!["film_wrong_digest"], vec![(0, "film.cpix.xml")], vec![], (Reason::CpixMismatch, "cpix", 0))); + cases.push(neg("n_cpix_encrypted", "a document whose key is encrypted, which this revision does not read", vec!["encrypted"], vec![(0, "encrypted.cpix.xml")], vec![], (Reason::CpixUnsupported, "cpix", 0))); + cases.push(neg("n_cpix_malformed", "a document that is not well-formed", vec!["film"], vec![(0, "broken.cpix.xml")], vec![], (Reason::CpixMalformed, "cpix", 0))); + cases.push(neg("n_list_forged", "a list whose signature has one bit changed", vec!["film_forged"], vec![], vec![], (Reason::ListBadSignature, "list", 0))); + cases.push(neg("n_list_malformed", "a list with a byte after its signature", vec!["film_trailing"], vec![], vec![], (Reason::ListMalformed, "list", 0))); + cases.push(neg("n_list_two_for_one_content", "two lists for one content", vec!["film", "film_tampered_commit"], vec![], vec![], (Reason::ListMalformed, "list", 1))); + cases.push(neg("n_list_unknown", "a release citing a list the verifier is not given", vec![], vec![], one(&r0), (Reason::ListUnknown, "receipt", 0))); + let mut c = neg("n_signer_unknown", "a release by a proxy the verifier does not trust", vec!["film"], vec![], one(&r0), (Reason::SignerUnknown, "receipt", 0)); + c.proxies = vec!["rogue_proxy"]; + cases.push(c); + let mut replayed = refusals.clone(); + replayed[5] = resigned(replayed[5].clone(), |r| release_fields(r, &film_list)); + cases.push(neg("n_replay_granted", "a replayed request restated as a release", vec!["film"], vec![], replayed.iter().map(ProxyReceipt::to_json).collect(), (Reason::DecisionMismatch, "receipt", 5))); + let mut renew = offline.clone(); + renew[5] = resigned(renew[5].clone(), |r| { + release_fields(r, &film_list); + r.licence_expiry = Some(r.issued + DAY); + r.renewal = Some(1); + }); + cases.push(neg("n_renewal_of_nothing_granted", "a renewal of no licence restated as a release", vec!["film"], vec![], renew.iter().map(ProxyReceipt::to_json).collect(), (Reason::DecisionMismatch, "receipt", 5))); + let channel_list = CommitmentList::parse(&lists()["channel"]).unwrap(); + let mut late = rotation.clone(); + late[2] = resigned(late[2].clone(), |r| release_fields(r, &channel_list)); + cases.push(neg("n_period_outside_granted", "the key of a period after the entitlement ends, restated as a release", vec!["channel"], vec![], late.iter().map(ProxyReceipt::to_json).collect(), (Reason::DecisionMismatch, "receipt", 2))); + cases.push(neg("n_duplicate", "one receipt twice in a log", vec!["film"], vec![], vec![r0.to_json(), r0.to_json()], (Reason::ReceiptDuplicate, "receipt", 1))); + let mut forged_ticket = h_ticket.to_bytes(); + let n = forged_ticket.len(); + forged_ticket[n - 1] ^= 1; + let mut c = neg("n_ticket_forged", "a ticket whose signature has one bit changed", vec!["episode"], vec![], one(&h0), (Reason::TicketInvalid, "ticket", 0)); + c.tickets = vec![forged_ticket]; + cases.push(c); + let mut narrow = k_tickets[0].clone(); + narrow.kids = vec![video]; + narrow.sign(&principal("proxy")); + let mut c = neg("n_ticket_kids", "a ticket naming fewer keys than its receipt released", vec!["film"], vec![], one(&k0), (Reason::TicketInvalid, "ticket", 0)); + c.tickets = vec![narrow.to_bytes()]; + cases.push(c); + let mut missing = r0.to_json(); + missing.as_object_mut().unwrap().remove("released"); + cases.push(neg("n_malformed", "a receipt without its `released` member", vec!["film"], vec![], vec![missing], (Reason::ReceiptMalformed, "receipt", 0))); + cases +} + +// --------------------------------------------------------------------------------------- +// Writing and checking +// --------------------------------------------------------------------------------------- + +/// A case's lists, CPIX documents and proxies, as a verifier is given them. +type Inputs = (Vec>, Vec<(usize, Vec)>, Vec<[u8; 32]>); + +fn inputs(c: &Case, docs: &BTreeMap<&str, String>, all: &BTreeMap<&str, Vec>) -> Inputs { + let lists = c.lists.iter().map(|n| all[n].clone()).collect(); + let cpix = c.cpix.iter().map(|(i, f)| (*i, docs[f].as_bytes().to_vec())).collect(); + let proxies = c.proxies.iter().map(|p| pubkey(p)).collect(); + (lists, cpix, proxies) +} + +fn facts_json(f: &Facts) -> Value { + json!({ + "identity": hex(&f.identity), + "decision": f.decision.label(), + "released": f.released, + "keys": f.keys.iter().map(|(k, c)| json!({"kid": hex(k), "key_commit": hex(c)})).collect::>(), + "licence_expiry": f.licence_expiry, + "renewal": f.renewal, + }) +} + +/// Every file of the corpus, by name: `vectors.json` and the CPIX documents. +pub fn generate() -> Vec<(String, String)> { + let docs = documents(); + let all = lists(); + let authorities = [pubkey("authority")]; + let mut vectors = Vec::new(); + let mut reasons = BTreeSet::new(); + let mut decisions = BTreeSet::new(); + for c in cases() { + let (lists, cpix, proxies) = inputs(&c, &docs, &all); + let case = LogCase { authorities: &authorities, proxies: &proxies, lists: &lists, cpix: &cpix, receipts: &c.log, tickets: &c.tickets }; + let got = verify_log(&case); + let mut v = json!({ + "name": c.name, + "about": c.about, + "authorities": authorities.iter().map(|a| hex(a)).collect::>(), + "proxies": proxies.iter().map(|p| hex(p)).collect::>(), + "lists": c.lists, + "cpix": c.cpix.iter().map(|(i, f)| json!({"list": i, "file": f})).collect::>(), + "log": c.log, + "tickets": c.tickets.iter().map(|t| hex(t)).collect::>(), + }); + let o = v.as_object_mut().unwrap(); + match (c.refuse, got) { + (None, Ok(facts)) => { + for f in &facts { + decisions.insert(f.decision.code()); + } + o.insert("outcome".into(), json!("accept")); + o.insert("receipts".into(), Value::Array(facts.iter().map(facts_json).collect())); + } + (Some((reason, place, index)), Err(r)) if r == (Refusal { reason, place, index }) => { + reasons.insert(reason.code()); + o.insert("outcome".into(), json!("refuse")); + o.insert("reason".into(), json!(reason.code())); + o.insert("at".into(), json!([place, index])); + } + (want, got) => panic!("{}: written to {want:?}, verifies {got:?}", c.name), + } + vectors.push(v); + } + let want: BTreeSet<&str> = Reason::ALL.iter().map(|r| r.code()).collect(); + assert_eq!(reasons, want, "the corpus gives every reason of §11"); + assert_eq!(decisions.len(), Decision::ALL.len(), "the corpus gives every decision of §7.2"); + let doc = json!({ + "corpus": "wai key-release (extensions/key-release.md)", + "t0": T0, + "principals": PRINCIPALS.iter().map(|p| (p.to_string(), json!(hex(&pubkey(p))))).collect::>(), + "lists": all.iter().map(|(n, b)| (n.to_string(), json!(hex(b)))).collect::>(), + "vectors": vectors, + }); + let mut files = vec![("vectors.json".to_string(), serde_json::to_string_pretty(&doc).unwrap() + "\n")]; + files.extend(docs.into_iter().map(|(n, d)| (n.to_string(), d))); + files +} + +/// Check committed files: every vector reaches the outcome it states. Returns one line a +/// failure. +pub fn verify(files: &BTreeMap) -> Vec { + let mut fails = Vec::new(); + let Some(doc) = files.get("vectors.json").and_then(|s| serde_json::from_str::(s).ok()) else { + return vec!["vectors.json does not read".into()]; + }; + let lists: BTreeMap> = doc["lists"] + .as_object() + .map(|m| m.iter().filter_map(|(k, v)| Some((k.clone(), unhex_vec(v.as_str()?)?))).collect()) + .unwrap_or_default(); + let keys = |v: &Value| -> Vec<[u8; 32]> { v.as_array().map(|a| a.iter().filter_map(|x| unhex(x.as_str()?)).collect()).unwrap_or_default() }; + for v in doc["vectors"].as_array().into_iter().flatten() { + let name = v["name"].as_str().unwrap_or("?"); + let Some(ls) = v["lists"].as_array().map(|a| a.iter().filter_map(|n| lists.get(n.as_str()?).cloned()).collect::>()) else { + fails.push(format!("{name}: no lists")); + continue; + }; + let cpix: Vec<(usize, Vec)> = v["cpix"] + .as_array() + .into_iter() + .flatten() + .filter_map(|c| Some((c["list"].as_u64()? as usize, files.get(c["file"].as_str()?)?.as_bytes().to_vec()))) + .collect(); + let receipts: Vec = v["log"].as_array().cloned().unwrap_or_default(); + let tickets: Vec> = v["tickets"].as_array().into_iter().flatten().filter_map(|t| unhex_vec(t.as_str()?)).collect(); + let (authorities, proxies) = (keys(&v["authorities"]), keys(&v["proxies"])); + let case = LogCase { authorities: &authorities, proxies: &proxies, lists: &ls, cpix: &cpix, receipts: &receipts, tickets: &tickets }; + match (v["outcome"].as_str(), verify_log(&case)) { + (Some("accept"), Ok(facts)) => { + let got: Vec = facts.iter().map(facts_json).collect(); + if v["receipts"] != Value::Array(got) { + fails.push(format!("{name}: the facts differ")); + } + } + (Some("refuse"), Err(r)) => { + if v["reason"] != json!(r.reason.code()) || v["at"] != json!([r.place, r.index]) { + fails.push(format!("{name}: refused {} at {} {}, not as stated", r.reason.code(), r.place, r.index)); + } + } + (o, got) => fails.push(format!("{name}: states {o:?}, verifies {:?}", got.map(|f| f.len()))), + } + } + fails +} diff --git a/wai/wai-rs/src/key_release/cpix.rs b/wai/wai-rs/src/key_release/cpix.rs new file mode 100644 index 000000000..91e861fa3 --- /dev/null +++ b/wai/wai-rs/src/key_release/cpix.rs @@ -0,0 +1,576 @@ +//! A commitment list derived from a CPIX document (extensions/key-release.md §4.4). +//! +//! The reader is the XML 1.0 a CPIX document uses: UTF-8, namespaces, attributes in +//! either quote, the five predefined entities and character references, comments, +//! processing instructions and CDATA sections. A document type declaration is refused, +//! so no entity is ever defined or expanded. It is written here rather than taken from a +//! crate so that the one thing it reads is exactly what §4.4 names. + +use super::{key_commitment, sha256, unb64, ListKey, Period, Scheme}; + +/// The CPIX namespace (CPIX §7.4.1). +pub const CPIX_NS: &str = "urn:dashif:org:cpix"; +/// The namespace of `Secret`, `PlainValue` and `EncryptedValue` (RFC 6030). +pub const PSKC_NS: &str = "urn:ietf:params:xml:ns:keyprov:pskc"; +const XML_NS: &str = "http://www.w3.org/XML/1998/namespace"; +const MAX_DEPTH: usize = 64; + +/// Why a CPIX document is refused (§4.4). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CpixError { + Malformed, + Unsupported, +} + +/// One key of a CPIX document: what §4.4 reads of it, the key included. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CpixKey { + pub kid: [u8; 16], + pub scheme: Scheme, + pub key: Vec, + pub period: Option, +} + +/// What §4.4 derives from a document. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Derived { + pub content_id: String, + pub sha256: [u8; 32], + /// In ascending KID order. + pub keys: Vec, +} + +impl Derived { + /// The list's keys: each key's KID, scheme, commitment and period. + pub fn list_keys(&self) -> Vec { + self.keys + .iter() + .map(|k| ListKey { kid: k.kid, scheme: k.scheme, key_commit: key_commitment(&k.key), period: k.period }) + .collect() + } +} + +/// An element: its namespace, local name, attributes (unprefixed, so in no namespace, +/// unless prefixed), children and text. +#[derive(Clone, Debug, Default)] +pub struct Element { + pub ns: Option, + pub name: String, + pub attrs: Vec<(Option, String, String)>, + pub children: Vec, + pub text: String, +} + +impl Element { + /// The value of an attribute in no namespace. + pub fn attr(&self, name: &str) -> Option<&str> { + self.attrs.iter().find(|(ns, n, _)| ns.is_none() && n == name).map(|(_, _, v)| v.as_str()) + } + + /// The children of this namespace and name. + pub fn kids<'a>(&'a self, ns: &'a str, name: &'a str) -> impl Iterator + 'a { + self.children.iter().filter(move |c| c.ns.as_deref() == Some(ns) && c.name == name) + } + + pub fn kid(&self, ns: &str, name: &str) -> Option<&Element> { + self.children.iter().find(|c| c.ns.as_deref() == Some(ns) && c.name == name) + } +} + +/// Derive a list's content, digest and keys from a document (§4.4). +pub fn derive(doc: &[u8]) -> Result { + use CpixError::*; + // Step 1. + let text = std::str::from_utf8(doc).map_err(|_| Malformed)?; + let root = parse(text).ok_or(Malformed)?; + if root.ns.as_deref() != Some(CPIX_NS) || root.name != "CPIX" { + return Err(Malformed); + } + let sha = sha256(doc); + // Step 2. + let content_id = root.attr("contentId").ok_or(Unsupported)?.to_string(); + // Step 3. + let mut keys: Vec = Vec::new(); + let list = root.kid(CPIX_NS, "ContentKeyList"); + for ck in list.into_iter().flat_map(|l| l.kids(CPIX_NS, "ContentKey")) { + let kid = ck.attr("kid").and_then(uuid).ok_or(Malformed)?; + if keys.iter().any(|k| k.kid == kid) { + return Err(Malformed); + } + if ck.attr("contentId").is_some() || ck.attr("dependsOnKey").is_some() { + return Err(Unsupported); + } + let scheme = ck.attr("commonEncryptionScheme").and_then(Scheme::from_label).ok_or(Unsupported)?; + let plain = ck + .kid(CPIX_NS, "Data") + .and_then(|d| d.kid(PSKC_NS, "Secret")) + .filter(|s| s.kid(PSKC_NS, "EncryptedValue").is_none()) + .and_then(|s| s.kid(PSKC_NS, "PlainValue")) + .ok_or(Unsupported)?; + let key = unb64(&plain.text).filter(|k| k.len() == scheme.key_len()).ok_or(Malformed)?; + keys.push(CpixKey { kid, scheme, key, period: None }); + } + if keys.is_empty() { + return Err(Malformed); + } + // Step 4. + let periods: Vec<&Element> = + root.kid(CPIX_NS, "ContentKeyPeriodList").into_iter().flat_map(|l| l.kids(CPIX_NS, "ContentKeyPeriod")).collect(); + let mut named: Vec<([u8; 16], usize)> = Vec::new(); + for rule in root.kid(CPIX_NS, "ContentKeyUsageRuleList").into_iter().flat_map(|l| l.kids(CPIX_NS, "ContentKeyUsageRule")) { + let kid = rule.attr("kid").and_then(uuid).filter(|k| keys.iter().any(|x| x.kid == *k)).ok_or(Malformed)?; + for f in rule.kids(CPIX_NS, "KeyPeriodFilter") { + let id = f.attr("periodId").ok_or(Malformed)?; + let p = periods.iter().position(|p| p.attr("id") == Some(id)).ok_or(Malformed)?; + match named.iter().find(|(k, _)| *k == kid) { + Some((_, q)) if *q != p => return Err(Unsupported), + Some(_) => {} + None => named.push((kid, p)), + } + } + } + // Step 5. + keys.sort_by_key(|k| k.kid); + for k in &mut keys { + if let Some((_, p)) = named.iter().find(|(kid, _)| *kid == k.kid) { + k.period = Some(period(periods[*p])?); + } + } + Ok(Derived { content_id, sha256: sha, keys }) +} + +fn period(p: &Element) -> Result { + use CpixError::*; + if ["startOffset", "endOffset", "duration"].iter().any(|a| p.attr(a).is_some()) { + return Err(Unsupported); + } + let index = match p.attr("index") { + None => None, + Some(s) => Some(decimal_u32(s).ok_or(Unsupported)?), + }; + let window = match (p.attr("start"), p.attr("end")) { + (None, None) => None, + (Some(s), Some(e)) => { + let (s, e) = (date_time(s).ok_or(Unsupported)?, date_time(e).ok_or(Unsupported)?); + if s >= e { + return Err(Malformed); + } + Some((s, e)) + } + _ => return Err(Unsupported), + }; + if index.is_none() && window.is_none() { + return Err(Unsupported); + } + Ok(Period { index, window }) +} + +fn decimal_u32(s: &str) -> Option { + if s.is_empty() || !s.bytes().all(|c| c.is_ascii_digit()) { + return None; + } + s.parse().ok() +} + +/// A UUID in its canonical text form, either case, as its 16 bytes in order. +pub fn uuid(s: &str) -> Option<[u8; 16]> { + let b = s.as_bytes(); + if b.len() != 36 || [8, 13, 18, 23].iter().any(|&i| b[i] != b'-') { + return None; + } + let digits: String = s.chars().filter(|&c| c != '-').collect(); + if digits.len() != 32 || !digits.bytes().all(|c| c.is_ascii_hexdigit()) { + return None; + } + let mut o = [0u8; 16]; + for (i, x) in o.iter_mut().enumerate() { + *x = u8::from_str_radix(&digits[2 * i..2 * i + 2], 16).ok()?; + } + Some(o) +} + +/// The UUID text form of 16 bytes, lowercase. +pub fn uuid_text(b: &[u8; 16]) -> String { + let h = super::hex(b); + format!("{}-{}-{}-{}-{}", &h[..8], &h[8..12], &h[12..16], &h[16..20], &h[20..]) +} + +/// `YYYY-MM-DDThh:mm:ssZ` as seconds since 1970-01-01T00:00:00Z. +pub fn date_time(s: &str) -> Option { + let b = s.as_bytes(); + if b.len() != 20 || b[4] != b'-' || b[7] != b'-' || b[10] != b'T' || b[13] != b':' || b[16] != b':' || b[19] != b'Z' { + return None; + } + let num = |r: std::ops::Range| -> Option { + let t = &s[r]; + t.bytes().all(|c| c.is_ascii_digit()).then(|| t.parse().ok())? + }; + let (y, mo, d, h, mi, se) = (num(0..4)?, num(5..7)?, num(8..10)?, num(11..13)?, num(14..16)?, num(17..19)?); + let leap = (y % 4 == 0 && y % 100 != 0) || y % 400 == 0; + let days_in = [31, if leap { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + if y < 1970 || !(1..=12).contains(&mo) || d == 0 || d > days_in[mo as usize - 1] || h > 23 || mi > 59 || se > 59 { + return None; + } + Some((days_from_civil(y, mo, d) * 86_400) + h * 3600 + mi * 60 + se) +} + +/// Days from 1970-01-01 to a date of the proleptic Gregorian calendar, 1970 or later. +fn days_from_civil(y: u64, m: u64, d: u64) -> u64 { + let y = if m <= 2 { y - 1 } else { y }; + let era = y / 400; + let yoe = y - era * 400; + let mp = (m + 9) % 12; + let doy = (153 * mp + 2) / 5 + d - 1; + let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + era * 146_097 + doe - 719_468 +} + +/// `seconds` as `YYYY-MM-DDThh:mm:ssZ`. +pub fn date_time_text(seconds: u64) -> String { + let (days, rem) = (seconds / 86_400, seconds % 86_400); + let z = days + 719_468; + let era = z / 146_097; + let doe = z - era * 146_097; + let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; + let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + let mp = (5 * doy + 2) / 153; + let d = doy - (153 * mp + 2) / 5 + 1; + let m = if mp < 10 { mp + 3 } else { mp - 9 }; + let y = yoe + era * 400 + if m <= 2 { 1 } else { 0 }; + format!("{y:04}-{m:02}-{d:02}T{:02}:{:02}:{:02}Z", rem / 3600, rem % 3600 / 60, rem % 60) +} + +// --------------------------------------------------------------------------------------- +// The XML reader +// --------------------------------------------------------------------------------------- + +/// Parse a well-formed XML 1.0 document with no document type declaration into its root. +pub fn parse(doc: &str) -> Option { + let doc = doc.strip_prefix('\u{feff}').unwrap_or(doc); + if doc.chars().any(|c| ((c as u32) < 0x20 && !matches!(c, '\t' | '\n' | '\r')) || matches!(c, '\u{fffe}' | '\u{ffff}')) { + return None; + } + let norm = doc.replace("\r\n", "\n").replace('\r', "\n"); + let mut p = Parser { s: norm.as_bytes(), at: 0, text: &norm }; + if p.starts("")?; + let decl = &norm[5..end]; + if let Some(i) = decl.find("encoding") { + let rest = decl[i + 8..].trim_start().strip_prefix('=')?.trim_start(); + let q = rest.chars().next()?; + let v = rest[1..].split(q).next()?; + if !v.eq_ignore_ascii_case("utf-8") { + return None; + } + } + p.at = end + 2; + } + p.misc()?; + let mut scopes = vec![vec![("xml".to_string(), XML_NS.to_string())]]; + let root = p.element(&mut scopes, 0)?; + p.misc()?; + (p.at == p.s.len()).then_some(root) +} + +struct Parser<'a> { + s: &'a [u8], + at: usize, + text: &'a str, +} + +impl Parser<'_> { + fn starts(&self, t: &str) -> bool { + self.s[self.at..].starts_with(t.as_bytes()) + } + + fn find(&self, t: &str) -> Option { + self.text[self.at..].find(t).map(|i| self.at + i) + } + + fn ws(&mut self) -> bool { + let start = self.at; + while self.at < self.s.len() && matches!(self.s[self.at], b' ' | b'\t' | b'\n') { + self.at += 1; + } + self.at > start + } + + /// Comments, processing instructions and white space; a document type declaration is + /// refused. + fn misc(&mut self) -> Option<()> { + loop { + self.ws(); + if self.starts("")?; + let body = &self.text[self.at + 4..end]; + if body.contains("--") || body.ends_with('-') { + return None; + } + self.at = end + 3; + Some(()) + } + + fn pi(&mut self) -> Option<()> { + self.at += 2; + let name = self.name()?; + if name.eq_ignore_ascii_case("xml") || name.contains(':') { + return None; + } + let end = self.find("?>")?; + if end > self.at && !self.s[self.at].is_ascii_whitespace() { + return None; + } + self.at = end + 2; + Some(()) + } + + fn name(&mut self) -> Option { + let start = self.at; + let first = self.text[self.at..].chars().next()?; + if !(first.is_ascii_alphabetic() || first == '_' || first == ':' || first as u32 >= 0x80) { + return None; + } + for c in self.text[self.at..].chars() { + if c.is_ascii_alphanumeric() || matches!(c, '_' | ':' | '-' | '.') || c as u32 >= 0x80 { + self.at += c.len_utf8(); + } else { + break; + } + } + let n = &self.text[start..self.at]; + let colons = n.matches(':').count(); + if colons > 1 || n.starts_with(':') || n.ends_with(':') { + return None; + } + Some(n.to_string()) + } + + /// Text with references resolved, up to the next `<` (or the quote, in an attribute). + fn chars_until(&mut self, stop: u8, attribute: bool) -> Option { + let mut o = String::new(); + while self.at < self.s.len() && self.s[self.at] != stop { + let c = self.s[self.at]; + if attribute && c == b'<' { + return None; + } + if c == b'&' { + let end = self.find(";")?; + let r = &self.text[self.at + 1..end]; + let ch = match r { + "lt" => '<', + "gt" => '>', + "amp" => '&', + "quot" => '"', + "apos" => '\'', + _ => { + let v = if let Some(h) = r.strip_prefix("#x") { + if h.is_empty() || !h.bytes().all(|b| b.is_ascii_hexdigit()) { + return None; + } + u32::from_str_radix(h, 16).ok()? + } else { + let d = r.strip_prefix('#')?; + if d.is_empty() || !d.bytes().all(|b| b.is_ascii_digit()) { + return None; + } + d.parse().ok()? + }; + let ch = char::from_u32(v)?; + if (v < 0x20 && !matches!(v, 0x9 | 0xA | 0xD)) || v == 0xFFFE || v == 0xFFFF { + return None; + } + ch + } + }; + o.push(ch); + self.at = end + 1; + } else { + let ch = self.text[self.at..].chars().next()?; + if !attribute && self.text[self.at..].starts_with("]]>") { + return None; + } + o.push(if attribute && matches!(ch, '\t' | '\n') { ' ' } else { ch }); + self.at += ch.len_utf8(); + } + } + Some(o) + } + + fn element(&mut self, scopes: &mut Vec>, depth: usize) -> Option { + if depth > MAX_DEPTH || !self.starts("<") { + return None; + } + self.at += 1; + let qname = self.name()?; + let mut raw: Vec<(String, String)> = Vec::new(); + loop { + let had_ws = self.ws(); + if self.starts("/>") || self.starts(">") { + break; + } + if !had_ws { + return None; + } + let an = self.name()?; + self.ws(); + if !self.starts("=") { + return None; + } + self.at += 1; + self.ws(); + let q = *self.s.get(self.at)?; + if q != b'"' && q != b'\'' { + return None; + } + self.at += 1; + let v = self.chars_until(q, true)?; + if self.at >= self.s.len() { + return None; + } + self.at += 1; + if raw.iter().any(|(n, _)| *n == an) { + return None; + } + raw.push((an, v)); + } + let mut scope = Vec::new(); + for (n, v) in &raw { + if n == "xmlns" { + scope.push((String::new(), v.clone())); + } else if let Some(p) = n.strip_prefix("xmlns:") { + if v.is_empty() || p == "xmlns" || (p == "xml") != (v == XML_NS) { + return None; + } + scope.push((p.to_string(), v.clone())); + } + } + scopes.push(scope); + let resolve = |scopes: &Vec>, prefix: &str| -> Option> { + for s in scopes.iter().rev() { + if let Some((_, u)) = s.iter().rev().find(|(p, _)| p == prefix) { + return Some(if u.is_empty() { None } else { Some(u.clone()) }); + } + } + if prefix.is_empty() { Some(None) } else { None } + }; + let (prefix, local) = match qname.split_once(':') { + Some((p, l)) => (p, l), + None => ("", qname.as_str()), + }; + let mut el = Element { ns: resolve(scopes, prefix)?, name: local.to_string(), ..Default::default() }; + for (n, v) in raw { + if n == "xmlns" || n.starts_with("xmlns:") { + continue; + } + let (ns, an) = match n.split_once(':') { + Some((p, l)) => (Some(resolve(scopes, p)??), l.to_string()), + None => (None, n.clone()), + }; + if el.attrs.iter().any(|(ons, on, _)| *ons == ns && *on == an) { + return None; + } + el.attrs.push((ns, an, v)); + } + if self.starts("/>") { + self.at += 2; + scopes.pop(); + return Some(el); + } + self.at += 1; + loop { + let t = self.chars_until(b'<', false)?; + el.text.push_str(&t); + if self.at >= self.s.len() { + return None; + } + if self.starts("") { + return None; + } + self.at += 1; + scopes.pop(); + return Some(el); + } else if self.starts(" + + x]]>AB +"#; + let root = parse(doc).expect("well-formed"); + assert_eq!(root.ns.as_deref(), Some(CPIX_NS)); + assert_eq!(root.attr("contentId"), Some("a&b")); + let ck = root.kid(CPIX_NS, "ContentKeyList").unwrap().kid(CPIX_NS, "ContentKey").unwrap(); + assert_eq!(ck.kid(PSKC_NS, "Secret").unwrap().text, "xAB"); + } + + #[test] + fn the_reader_refuses_what_is_not_well_formed() { + for bad in [ + "", + "", + "]>&e;", + "&e;", + "", + "", + "", + "\u{1}", + "", + "", + "]]>", + ] { + assert!(parse(bad).is_none(), "{bad:?} is not well-formed"); + } + } + + #[test] + fn times_and_uuids_round_trip() { + assert_eq!(date_time("2026-10-25T00:00:00Z"), Some(1_792_886_400)); + assert_eq!(date_time_text(1_792_886_400), "2026-10-25T00:00:00Z"); + assert_eq!(date_time("2028-02-29T23:59:59Z").map(date_time_text).as_deref(), Some("2028-02-29T23:59:59Z")); + assert_eq!(date_time("2027-02-29T00:00:00Z"), None); + assert_eq!(date_time("2026-10-25T00:00:00.5Z"), None); + let u = uuid("EDEF8BA9-79d6-4ace-a3c8-27dcd51d21ed").unwrap(); + assert_eq!(uuid_text(&u), "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"); + assert_eq!(uuid("edef8ba979d64acea3c827dcd51d21ed"), None); + } +} diff --git a/wai/wai-rs/src/key_release/tests.rs b/wai/wai-rs/src/key_release/tests.rs new file mode 100644 index 000000000..181daa541 --- /dev/null +++ b/wai/wai-rs/src/key_release/tests.rs @@ -0,0 +1,331 @@ +use super::corpus::{content_key, kid, principal, T0}; +use super::*; + +fn list() -> CommitmentList { + let keys = vec![ + ListKey { kid: kid("t/video"), scheme: Scheme::Cenc, key_commit: key_commitment(&content_key("t/video", 16)), period: None }, + ListKey { + kid: kid("t/p1"), + scheme: Scheme::Cbcs, + key_commit: key_commitment(&content_key("t/p1", 16)), + period: Some(Period { index: Some(1), window: Some((T0, T0 + 3600)) }), + }, + ]; + CommitmentList::seal(&principal("key_holder"), "holder", "t-content", "svod:premium", Some([9; 32]), T0, keys) +} + +fn ent(offline_s: u64, renewals: u32) -> Entitlement { + Entitlement::seal( + &principal("authority"), + "authority", + [1; 16], + principal("device_a").verifying_key().to_bytes(), + "svod:premium", + T0, + T0 + 7200, + offline_s, + renewals, + ) +} + +fn req(kids: Vec<[u8; 16]>, kind: RequestKind, renews: Option<[u8; 32]>, t: u64) -> Request { + let mut kids = kids; + kids.sort_unstable(); + Request { + content_id: "t-content".into(), + delivery: Delivery { kind: DeliveryKind::Drm, system_id: Some([7; 16]) }, + kids, + kind, + renews, + request_sha256: sha256(b"challenge"), + request_t: t, + } +} + +fn authorities() -> [[u8; 32]; 1] { + [principal("authority").verifying_key().to_bytes()] +} + +/// Decide and seal one request, forwarded and answered 200 when granted. +fn seal(e: &Entitlement, r: &Request, issued: u64, log: &[ProxyReceipt]) -> ProxyReceipt { + let b = e.to_bytes(); + let proof = prove(&principal("device_a"), &b, r); + let l = list(); + let o = decide(&authorities(), Some(&b), Some(&proof), r, Some(&l), issued, log); + let up = Upstream { status: 200, response_sha256: sha256(b"licence") }; + ProxyReceipt::assemble(&principal("proxy"), "proxy", r, Some(&l), Some(b), Some(proof), issued, &o, Some(up), 0, None) +} + +#[test] +fn the_tables_round_trip() { + for s in Scheme::ALL { + assert_eq!(Scheme::from_code(s.code()), Some(s)); + assert_eq!(Scheme::from_label(s.label()), Some(s)); + } + for d in Decision::ALL { + assert_eq!(Decision::from_code(d.code()), Some(d)); + assert_eq!(Decision::from_label(d.label()), Some(d)); + } + // Form 1's codes keep their meaning. + assert_eq!(Decision::Granted.code(), 1); + assert_eq!(Decision::DeniedNoEntitlement.code(), 2); + assert_eq!(Decision::DeniedExpired.code(), 3); + assert!(!DeliveryKind::HlsKeyUri.carries(Scheme::Cens) && !DeliveryKind::HlsKeyUri.carries(Scheme::Cbc1)); + assert!(!DeliveryKind::ClearKey.carries(Scheme::Aes128)); + assert!(Scheme::ALL.iter().all(|s| DeliveryKind::Drm.carries(*s))); + assert_eq!(Scheme::Aes256Gcm.key_len(), 32); +} + +#[test] +fn a_list_round_trips_and_its_rules_refuse() { + let l = list(); + let b = l.to_bytes(); + assert_eq!(CommitmentList::parse(&b), Ok(l.clone())); + assert!(l.keys.windows(2).all(|w| w[0].kid < w[1].kid), "sealed in KID order"); + let mut trailing = b.clone(); + trailing.push(0); + assert_eq!(CommitmentList::parse(&trailing), Err(ListFault::Malformed)); + let mut sig = b.clone(); + let n = sig.len(); + sig[n - 2] ^= 1; + assert_eq!(CommitmentList::parse(&sig), Err(ListFault::BadSignature)); + let reseal = |f: &dyn Fn(&mut CommitmentList)| { + let mut m = l.clone(); + f(&mut m); + m.sign(&principal("key_holder")); + CommitmentList::parse(&m.to_bytes()) + }; + assert_eq!(reseal(&|m| m.keys.swap(0, 1)), Err(ListFault::Malformed), "KIDs out of order"); + assert_eq!(reseal(&|m| m.keys[0].key_commit = [0; 32]), Err(ListFault::Malformed)); + assert_eq!(reseal(&|m| m.keys[1].period = Some(Period { index: None, window: Some((5, 5)) })), Err(ListFault::Malformed)); + assert_eq!(reseal(&|m| m.content_id.clear()), Err(ListFault::Malformed)); + assert!(reseal(&|m| m.cpix = None).is_ok()); +} + +#[test] +fn an_entitlement_holds_only_as_signed() { + let e = ent(0, 0); + let b = e.to_bytes(); + assert_eq!(Entitlement::read(&b), Some(e.clone())); + assert!(e.holds()); + let mut m = e.clone(); + m.not_after += 1; + assert!(!m.holds(), "a changed window does not verify"); + let mut m = e.clone(); + m.renewals = 1; + m.sign(&principal("authority")); + assert!(!m.holds(), "renewals with no offline allowance"); + let mut m = e; + m.not_before = m.not_after + 1; + m.sign(&principal("authority")); + assert!(!m.holds()); + let mut t = b.clone(); + t.push(0); + assert_eq!(Entitlement::read(&t), None); +} + +#[test] +fn each_check_decides_in_its_order() { + use Decision::*; + let l = list(); + let e = ent(3600, 1); + let b = e.to_bytes(); + let dev = principal("device_a"); + let auth = authorities(); + let video = kid("t/video"); + let run = |r: &Request, ent: Option<&[u8]>, proof: Option<[u8; 64]>, issued: u64, log: &[ProxyReceipt]| { + decide(&auth, ent, proof.as_ref(), r, Some(&l), issued, log).decision + }; + let r = req(vec![video], RequestKind::Streaming, None, T0 + 10); + let p = prove(&dev, &b, &r); + assert_eq!(run(&r, None, None, T0 + 10, &[]), DeniedEntitlementInvalid); + assert_eq!(run(&r, Some(&b), Some(prove(&principal("device_b"), &b, &r)), T0 + 10, &[]), DeniedEntitlementInvalid); + assert_eq!(decide(&[[3; 32]], Some(&b), Some(&p), &r, Some(&l), T0 + 10, &[] as &[ProxyReceipt]).decision, DeniedEntitlementInvalid); + assert_eq!(run(&r, Some(&b), Some(p), T0 + 10 + FRESHNESS_S + 1, &[]), DeniedStale); + assert_eq!(run(&r, Some(&b), Some(p), T0 + 10 + FRESHNESS_S, &[]), Granted, "the freshness bound is inclusive"); + let granted = seal(&e, &r, T0 + 10, &[]); + assert_eq!(run(&r, Some(&b), Some(p), T0 + 11, std::slice::from_ref(&granted)), DeniedReplayed); + let unknown = req(vec![video, [0xEE; 16]], RequestKind::Streaming, None, T0 + 10); + assert_eq!(run(&unknown, Some(&b), Some(prove(&dev, &b, &unknown)), T0 + 10, &[]), DeniedUnknownKey); + assert_eq!(decide(&auth, Some(&b), Some(&p), &r, None, T0 + 10, &[] as &[ProxyReceipt]).decision, DeniedUnknownKey); + let mut basic = e.clone(); + basic.entitlement = "svod:basic".into(); + basic.sign(&principal("authority")); + let bb = basic.to_bytes(); + assert_eq!(run(&r, Some(&bb), Some(prove(&dev, &bb, &r)), T0 + 10, &[]), DeniedNoEntitlement); + let early = req(vec![video], RequestKind::Streaming, None, T0 - 10); + assert_eq!(run(&early, Some(&b), Some(prove(&dev, &b, &early)), T0 - 10, &[]), DeniedNotYetValid); + let late = req(vec![video], RequestKind::Streaming, None, T0 + 7201); + assert_eq!(run(&late, Some(&b), Some(prove(&dev, &b, &late)), T0 + 7201, &[]), DeniedExpired); + let at_end = req(vec![video], RequestKind::Streaming, None, T0 + 7200); + assert_eq!(run(&at_end, Some(&b), Some(prove(&dev, &b, &at_end)), T0 + 7200, &[]), Granted, "not_after is included"); + // A period that ends before the entitlement starts. + let mut later = e.clone(); + later.not_before = T0 + 3600; + later.sign(&principal("authority")); + let lb = later.to_bytes(); + let p1 = req(vec![kid("t/p1")], RequestKind::Streaming, None, T0 + 3700); + assert_eq!(run(&p1, Some(&lb), Some(prove(&dev, &lb, &p1)), T0 + 3700, &[]), DeniedPeriodOutside, "end at most not_before"); + let pre = ent(0, 0).to_bytes(); + let offline = req(vec![video], RequestKind::Offline, None, T0 + 10); + assert_eq!(run(&offline, Some(&pre), Some(prove(&dev, &pre, &offline)), T0 + 10, &[]), DeniedOfflineNotEntitled); +} + +#[test] +fn an_offline_licence_renews_once_and_never_outlives_the_entitlement() { + use Decision::*; + let e = ent(3600, 1); + let video = kid("t/video"); + let o0 = seal(&e, &req(vec![video], RequestKind::Offline, None, T0 + 10), T0 + 10, &[]); + assert_eq!((o0.decision, o0.licence_expiry, o0.renewal), (Granted, Some(T0 + 3610), Some(0))); + let log = vec![o0.clone()]; + let o1 = seal(&e, &req(vec![video], RequestKind::Renewal, Some(o0.identity()), T0 + 3000), T0 + 3000, &log); + assert_eq!((o1.decision, o1.licence_expiry, o1.renewal), (Granted, Some(T0 + 6600), Some(1))); + let log = vec![o0.clone(), o1.clone()]; + let o2 = seal(&e, &req(vec![video], RequestKind::Renewal, Some(o1.identity()), T0 + 4000), T0 + 4000, &log); + assert_eq!(o2.decision, DeniedRenewalsExhausted); + let lapsed = seal(&e, &req(vec![video], RequestKind::Renewal, Some(o0.identity()), T0 + 3611), T0 + 3611, std::slice::from_ref(&o0)); + assert_eq!(lapsed.decision, DeniedRenewalLapsed); + let other = seal(&e, &req(vec![video, kid("t/p1")], RequestKind::Renewal, Some(o0.identity()), T0 + 100), T0 + 100, std::slice::from_ref(&o0)); + assert_eq!(other.decision, DeniedRenewalUnknown, "a renewal names the same keys"); + let clamp = seal(&e, &req(vec![video], RequestKind::Offline, None, T0 + 7000), T0 + 7000, &[]); + assert_eq!(clamp.licence_expiry, Some(T0 + 7200)); + for r in [&o0, &o1, &o2, &lapsed, &other, &clamp] { + assert_eq!(r.check(), Ok(())); + } +} + +#[test] +fn a_receipt_round_trips_and_verifies_strictly() { + let r = seal(&ent(0, 0), &req(vec![kid("t/video")], RequestKind::Streaming, None, T0 + 10), T0 + 10, &[]); + assert!(r.released && r.keys[0].key_commit == Some(key_commitment(&content_key("t/video", 16)))); + let back = ProxyReceipt::from_json(&r.to_json()).expect("its own JSON reads"); + assert_eq!(back, r); + assert_eq!(back.identity(), r.identity()); + let mut j = r.to_json(); + j["energy_provenance"] = serde_json::json!("HwShunt"); + assert_eq!(ProxyReceipt::from_json(&j), None, "the legacy label does not read in this form"); + let mut j = r.to_json(); + j.as_object_mut().unwrap().insert("extra".into(), serde_json::json!(1)); + assert_eq!(ProxyReceipt::from_json(&j), None); + crate::sig::assert_refuses_forgeries(&r, |r, k, s| (r.signer_pubkey, r.sig) = (k, s), |r| r.signing_payload(), |r| r.check().is_ok()); +} + +#[test] +fn each_own_rule_refuses() { + let r = seal(&ent(3600, 1), &req(vec![kid("t/video")], RequestKind::Offline, None, T0 + 10), T0 + 10, &[]); + assert_eq!(r.check(), Ok(())); + let reseal = |f: &dyn Fn(&mut ProxyReceipt)| { + let mut m = r.clone(); + f(&mut m); + m.sign(&principal("proxy")); + m.check() + }; + use Reason::*; + assert_eq!(reseal(&|m| m.keys.push(m.keys[0].clone())), Err(ReceiptInvalid), "rule 2"); + assert_eq!(reseal(&|m| m.delivery.system_id = None), Err(ReceiptInvalid), "rule 3"); + assert_eq!(reseal(&|m| m.delivery.system_id = Some([0; 16])), Err(ReceiptInvalid), "rule 3"); + assert_eq!( + reseal(&|m| { + m.delivery = Delivery { kind: DeliveryKind::ClearKey, system_id: None }; + m.keys[0].scheme = Some(Scheme::Aes128); + m.upstream_status = None; + m.response_sha256 = None; + }), + Err(SchemeDeliveryMismatch), + "rule 4" + ); + assert_eq!(reseal(&|m| m.required_entitlement = None), Err(ReceiptInvalid), "rule 5"); + assert_eq!(reseal(&|m| m.proof = None), Err(ReceiptInvalid), "rule 6"); + assert_eq!(reseal(&|m| m.renews = Some([1; 32])), Err(ReceiptInvalid), "rule 6"); + assert_eq!(reseal(&|m| m.decision = Decision::DeniedStale), Err(ReceiptInvalid), "rule 7"); + assert_eq!(reseal(&|m| m.keys[0].key_commit = None), Err(ReceiptInvalid), "rule 7"); + assert_eq!(reseal(&|m| m.upstream_status = Some(500)), Err(ReceiptInvalid), "rule 8"); + assert_eq!(reseal(&|m| m.response_sha256 = None), Err(ReceiptInvalid), "rule 8"); + assert_eq!(reseal(&|m| m.licence_expiry = None), Err(ReceiptInvalid), "rule 9"); + assert_eq!(reseal(&|m| m.renewal = Some(1)), Err(ReceiptInvalid), "rule 9"); + assert_eq!(reseal(&|m| m.licence_expiry = Some(m.issued - 1)), Err(ReceiptInvalid), "rule 9"); + assert_eq!(reseal(&|m| m.joules_micro = 5), Err(ReceiptInvalid), "rule 10: a figure with no class"); + assert_eq!( + reseal(&|m| { + m.joules_micro = 1; + m.energy_provenance = Some(EnergyProvenance::OnChipCounter { + uncertainty: Uncertainty { relative_ppm: 1_000, window_us: 10 }, + filtering: CounterFiltering::Unknown, + }); + }), + Err(ReceiptInvalid), + "rule 10: below the resolution floor" + ); +} + +#[test] +fn a_ticket_follows_its_receipt() { + let r0 = seal(&ent(0, 0), &req(vec![kid("t/video")], RequestKind::Streaming, None, T0 + 10), T0 + 10, &[]); + let mut r = r0.clone(); + r.delivery = Delivery { kind: DeliveryKind::ClearKey, system_id: None }; + r.upstream_status = None; + r.response_sha256 = None; + r.sign(&principal("proxy")); + assert_eq!(r.check(), Ok(())); + assert!(Ticket::for_receipt(&principal("proxy"), &r0, 60).is_none(), "a wrapped delivery has no ticket"); + assert!(Ticket::for_receipt(&principal("proxy"), &r, 301).is_none()); + let t = Ticket::for_receipt(&principal("proxy"), &r, 60).unwrap(); + let back = Ticket::parse(&t.to_bytes()).expect("reads"); + assert_eq!(back, t); + assert!(t.follows(&r)); + let mut other = t.clone(); + other.session_type = 2; + other.sign(&principal("proxy")); + assert!(!other.follows(&r)); + let mut b = t.to_bytes(); + b[8] ^= 1; + assert!(Ticket::parse(&b).is_none(), "a changed byte does not verify"); +} + +#[test] +fn the_encodings_are_canonical() { + for n in 0..40u8 { + let v: Vec = (0..n).map(|i| i.wrapping_mul(37)).collect(); + assert_eq!(unb64url(&b64url(&v)), Some(v.clone())); + assert_eq!(unb64(&b64(&v)), Some(v)); + } + assert_eq!(unb64("AAE="), Some(vec![0, 1])); + assert_eq!(unb64("AAF="), None, "non-zero unused bits"); + assert_eq!(unb64("AAE"), None, "padding is required"); + assert_eq!(unb64(" AA\nE= "), Some(vec![0, 1]), "white space is removed"); + assert_eq!(unb64url("AAE="), None); + assert_eq!(parse_kids(&format!("{},{}", hex(&[2; 16]), hex(&[1; 16]))), Some(vec![[1; 16], [2; 16]])); + assert_eq!(parse_kids(&format!("{0},{0}", hex(&[2; 16]))), None, "a KID twice"); + assert_eq!(parse_kids(&hex(&[0xAB; 16]).to_uppercase()), None, "lowercase only"); + assert_eq!(parse_kids(""), None); +} + +#[test] +fn the_cpix_documents_derive_their_lists() { + let docs = corpus::documents(); + let film = cpix::derive(docs["film.cpix.xml"].as_bytes()).expect("the film document derives"); + assert_eq!(film.content_id, "film-0001"); + assert_eq!(film.keys.len(), 2); + assert!(film.keys.iter().all(|k| k.key == content_key(if k.kid == kid("film/video") { "film/video" } else { "film/audio" }, 16))); + let channel = cpix::derive(docs["channel.cpix.xml"].as_bytes()).unwrap(); + assert!(channel.keys.iter().all(|k| k.period.is_some_and(|p| p.index.is_some() && p.window.is_some()))); + assert_eq!(cpix::derive(docs["encrypted.cpix.xml"].as_bytes()), Err(cpix::CpixError::Unsupported)); + assert_eq!(cpix::derive(docs["broken.cpix.xml"].as_bytes()), Err(cpix::CpixError::Malformed)); + let f = &docs["film.cpix.xml"]; + for (from, to, want) in [ + (" contentId=\"film-0001\"", "", cpix::CpixError::Unsupported), + ("commonEncryptionScheme=\"cenc\"", "commonEncryptionScheme=\"cbc2\"", cpix::CpixError::Unsupported), + ("", "AAAA", cpix::CpixError::Malformed), + ("\n", "\n\n", cpix::CpixError::Malformed), + ] { + assert!(f.contains(from), "{from}"); + assert_eq!(cpix::derive(f.replacen(from, to, 1).as_bytes()), Err(want), "{from} -> {to}"); + } + let c = &docs["channel.cpix.xml"]; + let dup = c.replacen("periodId=\"p2\"", "periodId=\"p1\"", 1).replacen("", "", 1); + assert_eq!(cpix::derive(dup.as_bytes()), Err(cpix::CpixError::Unsupported), "a key for two periods"); + assert_eq!(cpix::derive(c.replacen("periodId=\"p2\"", "periodId=\"p9\"", 1).as_bytes()), Err(cpix::CpixError::Malformed)); + assert_eq!(cpix::derive(c.replacen("index=\"1\"", "index=\"4294967296\"", 1).as_bytes()), Err(cpix::CpixError::Unsupported)); +} diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 38b0b7fc4..f0be0cde2 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -812,6 +812,15 @@ pub mod wai_video_ssai; #[cfg(feature = "video_keys")] pub mod wai_video_keys; +// Key release (extensions/key-release.md): the proxy form of wai.video.keys, signed by a +// proxy in front of any licence server for every request it answers, a release or a refusal, +// with the scheme, the key delivery and the CPIX document the keys came in, and a decision a +// verifier recomputes from signed inputs. The proxy copies each key commitment from the key +// holder's list, so it holds no key. +/// cbindgen:ignore +#[cfg(feature = "key_release")] +pub mod key_release; + // Deterministic packaging + attested SegmentMap (wai.video.package): a source is // sliced into content-hashed segments; the signed Merkle SegmentMap is what a // manifest reference is checked against, and identical content (ads, bumpers, diff --git a/wai/wai-rs/tests/key_release.rs b/wai/wai-rs/tests/key_release.rs new file mode 100644 index 000000000..71c8ba943 --- /dev/null +++ b/wai/wai-rs/tests/key_release.rs @@ -0,0 +1,83 @@ +//! Key release (extensions/key-release.md): the committed corpus `live-conformance/keys/` +//! regenerates byte for byte from its fixed seeds, holds no other file, every case in it gets +//! the outcome it states, and the extension's §11 lists exactly the reasons the reference +//! reports. The generator itself asserts that the corpus gives every one of them. +#![cfg(feature = "key_release")] + +use std::collections::BTreeMap; +use std::path::PathBuf; +use wai::key_release::{corpus, Decision, DeliveryKind, Reason, Scheme}; + +fn wai_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") +} + +fn committed() -> BTreeMap { + let dir = wai_dir().join("live-conformance/keys"); + std::fs::read_dir(&dir) + .expect("the committed corpus") + .flatten() + .map(|e| (e.file_name().to_string_lossy().into_owned(), e.path())) + .filter(|(n, _)| n != "README.md") + .map(|(n, p)| (n, std::fs::read_to_string(p).expect("a text file"))) + .collect() +} + +#[test] +fn the_committed_corpus_regenerates_from_its_seeds() { + let files = committed(); + let generated: BTreeMap = corpus::generate().into_iter().collect(); + let extra: Vec<&String> = files.keys().filter(|k| !generated.contains_key(*k)).collect(); + let missing: Vec<&String> = generated.keys().filter(|k| !files.contains_key(*k)).collect(); + let differ: Vec<&String> = generated.iter().filter(|(k, v)| files.get(*k).is_some_and(|f| f != *v)).map(|(k, _)| k).collect(); + assert!(extra.is_empty() && missing.is_empty() && differ.is_empty(), "extra {extra:?}, missing {missing:?}, differ {differ:?}: run `wai_key_release_vectors gen`"); +} + +#[test] +fn every_case_gets_its_stated_outcome() { + let fails = corpus::verify(&committed()); + assert!(fails.is_empty(), "{}", fails.join("\n")); +} + +/// §11 of the extension lists exactly the codes `Reason::ALL` has, each once, in order. +#[test] +fn the_extension_lists_the_reasons() { + let text = std::fs::read_to_string(wai_dir().join("extensions/key-release.md")).expect("the extension"); + let at = text.find("\n## 11. Reasons").expect("a §11"); + let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e); + let listed: Vec = + text[at..end].lines().filter(|l| l.starts_with("| `")).filter_map(|l| l.split('`').nth(1).map(String::from)).collect(); + let want: Vec = Reason::ALL.iter().map(|r| r.code().to_string()).collect(); + assert_eq!(listed, want, "§11's table and Reason::ALL differ"); +} + +/// The rows of the table under heading `head`, up to the next heading, split on `|`. +fn rows(head: &str) -> Vec> { + let text = std::fs::read_to_string(wai_dir().join("extensions/key-release.md")).expect("the extension"); + let at = text.find(&format!("\n{head}\n")).unwrap_or_else(|| panic!("a heading {head}")); + let body = &text[at + head.len() + 2..]; + let end = body.find("\n#").unwrap_or(body.len()); + body[..end] + .lines() + .filter(|l| l.starts_with("| ") && !l.starts_with("| ---") && !l.starts_with("|---")) + .skip(1) + .map(|l| l.trim_matches('|').split('|').map(|c| c.trim().trim_matches('`').to_string()).collect()) + .collect() +} + +/// §3.1, §3.2 and §7.2 give each scheme, delivery and decision the code and label the +/// reference writes, and no other. +#[test] +fn the_extension_tables_are_the_reference_codes() { + let schemes: Vec<(String, String)> = rows("### 3.1 Schemes").iter().map(|r| (r[0].clone(), r[1].clone())).collect(); + let want: Vec<(String, String)> = Scheme::ALL.iter().map(|s| (s.code().to_string(), s.label().to_string())).collect(); + assert_eq!(schemes, want, "§3.1"); + let deliveries: Vec<(String, String)> = rows("### 3.2 Key deliveries").iter().map(|r| (r[0].clone(), r[1].clone())).collect(); + let want: Vec<(String, String)> = DeliveryKind::ALL.iter().map(|d| (d.code().to_string(), d.label().to_string())).collect(); + assert_eq!(deliveries, want, "§3.2"); + let mut decisions: Vec<(String, String)> = rows("### 7.2 The checks, in order").iter().map(|r| (r[1].clone(), r[2].clone())).collect(); + decisions.sort(); + let mut want: Vec<(String, String)> = Decision::ALL.iter().map(|d| (d.label().to_string(), d.code().to_string())).collect(); + want.sort(); + assert_eq!(decisions, want, "§7.2"); +} -- 2.54.0 (Apple Git-157) From 9030200054a097576dd3515dc03ac6f5a4d895ca Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 25 Oct 2026 21:29:26 -0400 Subject: [PATCH 084/242] wai: stream audit's independent verifier, and three more controls live-conformance/stream_audit_verify.py, written from the extension's text: it reads acknowledgements with settlement_verify.py, claims with ad_presentation_verify.py and logs and energy classes with live_metering_verify.py, recomputes every report, gives every vector the corpus's outcome, requires 15 mutated copies to fail and catches each of its 31 checks dropped. Dropping three of them first went uncaught (an item that is not a whole object, a chain's first acknowledgement naming a prev, notes that are not the bound ones); the corpus gains a control for each. Co-Authored-By: Claude Opus 5.5 --- wai/live-conformance/audit/vectors.json | 135 ++++ wai/live-conformance/stream_audit_verify.py | 786 ++++++++++++++++++++ wai/wai-rs/src/stream_audit/corpus.rs | 12 + 3 files changed, 933 insertions(+) create mode 100644 wai/live-conformance/stream_audit_verify.py diff --git a/wai/live-conformance/audit/vectors.json b/wai/live-conformance/audit/vectors.json index 7c1a52ec3..80beb0f84 100644 --- a/wai/live-conformance/audit/vectors.json +++ b/wai/live-conformance/audit/vectors.json @@ -4865,6 +4865,141 @@ ] } }, + { + "name": "ack_item_not_whole_object", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100e9be769c1d2e4701321c7f38eac604cfa5717e359f0b1a1f94f8c32966e3d912000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a67576300000000000000000000000000000000000000000000000000000000000000000203000000000000000000040000000000000000000000e590d03f3a5be789c6ad201609bc74dad445fd021eb61a14cc3a173673e244d360916ad79187fdcadcd42015ab9cd61ebeaa98f379a8f941c669c7222836f209", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e06000000000000000000000000000000000000000000000000000000000000000000010004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000027010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d5efeec496d14e0391d5bdc3199141d1e18217bdb9a4781af2abf74d2065fa66ce49ba1bda6ad40e566d84f8a9a658c79b9b004f74036b366badeeb7cd712d24bd37958982bc885bbeb7d878bee8b8a66a373201cac65477813f3761b18b65202", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be05000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_ack_invalid", + "at": [ + "ack", + 0, + "entry", + 0 + ] + } + }, + { + "name": "ack_first_names_a_prev", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce24ac3053630eb9973bb5d0544e657dc170010062eaf09e5e060040ad36f19e5e06000501150068747470733a2f2f63646e2d612e6578616d706c650100ff7f212c548983243c314239a27246d95537914b5dd15da14921a4769f082d70000000000000ffffffff0000000000000000000000000000000000000000000000007b8dcb7c10399befc9681bbf529f57b567a81d5973a5284073e496c43a675763000000000000000000000000000000000000000000000000000000000000000002030000000000000000000400000000000000000000002a26b7151fc6cb14f58b6caae5a7984758a6f24421b0fe7f2c3f4dd067b39a0308668e7b4d6935f8bf5eee344126c04adff63bb86cad70d52bea91af46515a0d", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69d92ae4ebfea6a1b2deedbb087cbd8a42278541efffa23eae52f8faaa24bce240062eaf09e5e060040ad36f19e5e0600353d6772213a3380cec05fd5f785689f07775668f83702fe25039029d65eef1701000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000002c010000000000000b39b91cc3965a6cca7d66e9803be218a9e57bd6a18c0f26ef75ec4c8b111c2d73e043c45a27e6d9819350ee8cf7e0de9fa639cad53f47420a1b661d231a24c0dfb2bc4da74e484d40a3484c97f1112054317f8fa7a6c1e82fed0f7cf851144aec04f92e894ea827be56825270fae2a46f567b9157b336a2c9e8ae552ac66f07", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000" + ] + } + ], + "claims": [], + "log": "[[{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":0,\"ts_ms\":1792886600100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":1,\"ts_ms\":1792886600200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"ac3053630eb9973bb5d0544e657dc170\",\"sn\":2,\"ts_ms\":1792886604900,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": null, + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "ack_chain_broken", + "at": [ + "ack", + 0 + ] + } + }, + { + "name": "notes_not_the_bound_ones", + "bundle": { + "v": 1, + "session": "57415331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3775cf7dd7835eb64cb721e9756bae4ffe51193ce7d821356505867e3aa6552e29c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e47a628e02ebd980ba5e0fb790cc0161560100a0fee49e5e06008063c8e69e5e06000202150068747470733a2f2f63646e2d612e6578616d706c65150068747470733a2f2f63646e2d622e6578616d706c6505003b1d27d3cccc8c522bd589f7657da1b113200a6573d8ef6d85bd8f05457c18513da4be0a28e087720382c4ea8b0fc399bc59ca6956a7478192c9ecb627df0fa47f3d4d03c5427528551f68f449df2dd3cc76ffd127dc4109a6f6a5adfd3266e5e69cd4f55b49b97be5787cff28bae1989b53f22ef9a0d4a15a56cb1cf6748305fdaf2e91106cc9f6b73e327d1dacda59456eefa80240879213746b940f6e20e302006488bdc79885c458643cc4699f4bf7730c4d7ce9ba3bf335e23f4e49099b0176ca8c47fca83b70baad1c63f9f2f1bbc9c00deac9eebaa3e843beeef683cd327006000000e80300008813000010270170170000a00f000010270310270000a00f0000102701b0360000a00f0000b80b0350460000d0070000102701fc530000401f00001027018403000001000000dc05000000000000010000008403000000000000a7b48adfdc740a41c820db3bc8d7f265da8db0fdc4f997cb3c3ce431c62a40d3ebdbad7c5b33ede9054d283e7e4067186c394988d7d0a332aee09e1272a58f950203d012130000000000030400000000000000000000004fcc5e1422174e83bdcd5a8dde54ad92025cf577f3d10fbd43868ecd0917837eccf240106edbe4f9561aea4281f70dfb6ea5d5712e985d425e73dabca04d2000", + "acks": [ + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e400a0fee49e5e0600803697e59e5e0600000000000000000000000000000000000000000000000000000000000000000001000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000006202000000000000a4dcb70d985fd047b2ad36239f5fdcb85e1c414f01eb1ed03fbf59bb2b27efd42d2a5a96d82b5511203846c5676b3989652bcdf003f8e8c138d804e71513de20cd3dc6696ec92bbb2caff4d96c6460940f84ec2069f168b6eba073ac98007527f420acef2656fb9956756c7e524fd63e5a10613b4f3ad499df570c3439a4bd0e", + "items": [ + "c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be00000000000000002c01000000000000668ab7156c0cdafd3c825e6a0509e61906d7963db2730b433a01ec0836597c6700000000000000003601000000000000" + ] + }, + { + "record": "574441310101023e17980c2aac61e960ec4891cd524709688fe6a048e49a4eefbf911ee78694e69c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e4803697e59e5e060000cd2fe69e5e0600fdaf2e91106cc9f6b73e327d1dacda59456eefa80240879213746b940f6e20e301000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000004001000000000000acc4721b823695931be9ac381900d99e3654fb960d5c2dec49199d562c02f248986296bbe1bc46edf918a1489014010c5b4ea6a086ad82da076a735e8924da90a75c5dd65ecd72f25b6bf7334eaf6fa1ad18f2a0ac8588fbb094502ccae81dff5434971cc7217670ff93281372bb7cc3944886694b09e9203b4045778075240e", + "items": [ + "45bc67b48205766a0dcb1529580caa95566229ab2b75ab76e306149f187b0e8f00000000000000004001000000000000" + ] + }, + { + "record": "57444131010102aa9823a88a3500d91e003a07aa44da6e732282285eb660753f886af8317618179c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e400a0fee49e5e0600803697e59e5e060000000000000000000000000000000000000000000000000000000000000000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000cc100000000000005b946688e15c0dc062fff6403ca57dfeff256c28bf851a1c6cb0a276892be1e671a13a49b84a40bfb078a0674eb84317572a927c8c72819a4c9271d985970095b11a7462786029d1f1eaf541c3fbb29de5185097b42897287d1eeff8e91c23069f2ea26fee1b198ec93c94b0c6a71ce9f6a269d1052454659e797664856dd709", + "items": [ + "1cc1bee91e1678d1d0cc88e35feb520a88814da36bf757e76929e41ab22b81e70000000000000000c80000000000000058c5a889717aa9f19a18257a79a4a5e1fe5ffcd3bd764110c48caa99bb25f32600000000000000008403000000000000b46f33eda8230cc1d862f234b52230f4f28a2811bfb0551b2b7180382051051a00000000000000008403000000000000ea144235442623eae241fb82d66d987db9a712af44764a88ebe72a39b6d875be00000000000000008403000000000000e60f1e58f1280583b829596ae6b30238dc1d4fe1b746968ed4760a7afe7a79810000000000000000bc02000000000000a337ff2ce9c5dd88f28e86626a6949ebb451c7b834d6efe245fde68f9ebaf8f60000000000000000bc02000000000000" + ] + }, + { + "record": "57444131010102aa9823a88a3500d91e003a07aa44da6e732282285eb660753f886af8317618179c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e4803697e59e5e060000cd2fe69e5e06007f3d4d03c5427528551f68f449df2dd3cc76ffd127dc4109a6f6a5adfd3266e50100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000800c0000000000009449879d18725f52b9da1fad72a4e0a43295e12928f6071d3ade475bd8bd4273283707c4176716cf235199da2437611ea72b40b9ded8a8abfb3430608c5a29b950263fe9aa70b5b3075c20a08ed884b3c153ef3ccdfcde934d224e621657f30ecff7750b6194ccf0750420d0f6d55c6dccdba4b5456a0f8dbf8fc8581a613a0d", + "items": [ + "4f92db4b2f1126bbea570457a2bb4af5d4539ac78dadd09c1a0b7be8423907f6000000000000000084030000000000005a3329c2a4bbba26ba88806b2e6241497a8e5c327227698ef4ea28ca698248ca0000000000000000bc02000000000000e44b87886191603d7af3d1d7ac0cf8b1e919347c9adb95e108159ec7aa64f8350000000000000000bc020000000000000433693c3a9563f9d91ed5834b3603a57fcffeabd7c5515d75d7436fd649ed3300000000000000008403000000000000" + ] + }, + { + "record": "57444131010102aa9823a88a3500d91e003a07aa44da6e732282285eb660753f886af8317618179c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e400cd2fe69e5e06008063c8e69e5e06003da4be0a28e087720382c4ea8b0fc399bc59ca6956a7478192c9ecb627df0fa401000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000008403000000000000c4219ed8ccd91ccb9e86fc1493094ef7580b706227fe6e38b6c68483773817cf73f823ed1659878991de0529d3c0b0f8df6fd11910793dc46583016e46339ea6a6b1814742c79a6c3f0aca0378778a80152218d5b1a25fc5b270e2b95c49b1b979a589ff49136c29ac05a91b9de4871c1c6a3336a04793fd3c20fc76b9bd1b05", + "items": [ + "17a19fb27a17ab820ca3c1a28f780eff76e0aee401a9525db42f2e63109a23df00000000000000008403000000000000" + ] + } + ], + "claims": [ + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa3a744df636e2eb68b252d21d9efdb3e1a48746c61612d3bf5729910c0420dbbe97a628e02ebd980ba5e0fb790cc01615600e0b5a429b349be39beb139fc798d6372dcd7acf84b54db1f1bb38deba0136ed89c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e403ffffffff0200e60f1e58f1280583b829596ae6b30238dc1d4fe1b746968ed4760a7afe7a7981a337ff2ce9c5dd88f28e86626a6949ebb451c7b834d6efe245fde68f9ebaf8f6010000000000a00f000010270100001f5b94cc0e845508d2728c57a1f00fee43d393e1cec1bf732016a5558c7e989e651b294dcd2882935bb50d61e858b77132f65f2d7e14934dd596bb2f22723e05", + "marker": "#EXT-X-DATERANGE:ID=\"ad-1\",START-DATE=\"2026-10-25T00:00:06.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "e60f1e58f1280583b829596ae6b30238dc1d4fe1b746968ed4760a7afe7a7981", + "duration_ms": 2000 + }, + { + "content_hash": "a337ff2ce9c5dd88f28e86626a6949ebb451c7b834d6efe245fde68f9ebaf8f6", + "duration_ms": 2000 + } + ] + }, + { + "claim": "57504331017fa7f0efd1dc11faf3ebc728e92030996fdf8616c6365eeb325ac8c204234fa363323e5827fe2af15ba4e106c527f002329c0ad3149835c43792c8f5672ebddd7a628e02ebd980ba5e0fb790cc0161560063e9f2c46288ffe99caf952f59dc5900efe0b3d7d1de5e9acf92380f7793fa079c3f5ffd8b0244a7ccece4d3f377fc843af82dd662dc78d0154d2e1d8c6104e403ffffffff02005a3329c2a4bbba26ba88806b2e6241497a8e5c327227698ef4ea28ca698248cae44b87886191603d7af3d1d7ac0cf8b1e919347c9adb95e108159ec7aa64f835010000000000a00f0000b80b01000012d6cafdf3ce998330ebf16e02a9fc17bce91151ffd23e9dbe07d0d41b2034a0e920062ec1ab7e210d459e54c8537d04bbaad31549cb4bd63f28c9e17d6c7401", + "marker": "#EXT-X-DATERANGE:ID=\"ad-2\",START-DATE=\"2026-10-25T00:00:14.000Z\",DURATION=4.000,SCTE35-OUT=0xFC302000", + "creative": [ + { + "content_hash": "5a3329c2a4bbba26ba88806b2e6241497a8e5c327227698ef4ea28ca698248ca", + "duration_ms": 2000 + }, + { + "content_hash": "e44b87886191603d7af3d1d7ac0cf8b1e919347c9adb95e108159ec7aa64f835", + "duration_ms": 2000 + } + ] + } + ], + "log": "[[{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":0,\"ts_ms\":1792886400100,\"event\":\"start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":1,\"ts_ms\":1792886400200,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"c5ff6aa93aa312d89f3276a6ecac7af890854224c45c32ff2d35f51443ee37be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":2,\"ts_ms\":1792886400400,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"1cc1bee91e1678d1d0cc88e35feb520a88814da36bf757e76929e41ab22b81e7\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":3,\"ts_ms\":1792886400600,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"58c5a889717aa9f19a18257a79a4a5e1fe5ffcd3bd764110c48caa99bb25f326\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":4,\"ts_ms\":1792886401000,\"event\":\"present\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":{\"unit\":0,\"layers\":1,\"first\":true,\"late_ms\":null,\"decode_us\":null},\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":5,\"ts_ms\":1792886402400,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"b46f33eda8230cc1d862f234b52230f4f28a2811bfb0551b2b7180382051051a\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":6,\"ts_ms\":1792886403000,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"b46f33eda8230cc1d862f234b52230f4f28a2811bfb0551b2b7180382051051a\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":7,\"ts_ms\":1792886404400,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"668ab7156c0cdafd3c825e6a0509e61906d7963db2730b433a01ec0836597c67\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":8,\"ts_ms\":1792886404500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"ea144235442623eae241fb82d66d987db9a712af44764a88ebe72a39b6d875be\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":9,\"ts_ms\":1792886406500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"e60f1e58f1280583b829596ae6b30238dc1d4fe1b746968ed4760a7afe7a7981\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":10,\"ts_ms\":1792886408500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"a337ff2ce9c5dd88f28e86626a6949ebb451c7b834d6efe245fde68f9ebaf8f6\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":11,\"ts_ms\":1792886410500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"4f92db4b2f1126bbea570457a2bb4af5d4539ac78dadd09c1a0b7be8423907f6\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":12,\"ts_ms\":1792886410600,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"45bc67b48205766a0dcb1529580caa95566229ab2b75ab76e306149f187b0e8f\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":13,\"ts_ms\":1792886412500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"5a3329c2a4bbba26ba88806b2e6241497a8e5c327227698ef4ea28ca698248ca\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":14,\"ts_ms\":1792886414500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"e44b87886191603d7af3d1d7ac0cf8b1e919347c9adb95e108159ec7aa64f835\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":15,\"ts_ms\":1792886416500,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"0433693c3a9563f9d91ed5834b3603a57fcffeabd7c5515d75d7436fd649ed33\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":16,\"ts_ms\":1792886420000,\"event\":\"stall-start\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":17,\"ts_ms\":1792886421500,\"event\":\"stall-end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":18,\"ts_ms\":1792886422000,\"event\":\"response\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":{\"ot\":null,\"url\":null,\"content_hash\":\"17a19fb27a17ab820ca3c1a28f780eff76e0aee401a9525db42f2e63109a23df\",\"capability\":null,\"rendition\":null,\"layer\":null,\"layers_total\":null,\"layer_cap\":null,\"counted\":null,\"transferred\":null,\"d_ms\":null,\"dl_ms\":null,\"ttfb_ms\":null,\"ttlb_ms\":null,\"rc\":200,\"path\":null,\"receipt\":null},\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null},{\"v\":1,\"sid\":\"7a628e02ebd980ba5e0fb790cc016156\",\"sn\":19,\"ts_ms\":1792886429500,\"event\":\"end\",\"cid\":null,\"st\":\"l\",\"sf\":\"h\",\"object\":null,\"present\":null,\"switch\":null,\"layer_change\":null,\"capable\":null,\"cohort\":null,\"metrics\":null,\"claim\":null}]]", + "notes": "[{\"content_hash\":\"58c5a889717aa9f19a18257a79a4a5e1fe5ffcd3bd764110c48caa99bb25f326\",\"cmsd_static\":\"ot=v,sf=h,st=l\",\"cmsd_dynamic\":null},{\"content_hash\":\"b46f33eda8230cc1d862f234b52230f4f28a2811bfb0551b2b7180382051051a\",\"cmsd_static\":null,\"cmsd_dynamic\":\"\\\"cdn-b\\\";etp=1200;rtt=41\"}]", + "attachments": [] + }, + "outcome": { + "outcome": "refuse", + "reason": "audit_document_mismatch", + "at": [ + "notes" + ] + } + }, { "name": "log_not_the_bound_one", "bundle": { diff --git a/wai/live-conformance/stream_audit_verify.py b/wai/live-conformance/stream_audit_verify.py new file mode 100644 index 000000000..de97874ae --- /dev/null +++ b/wai/live-conformance/stream_audit_verify.py @@ -0,0 +1,786 @@ +#!/usr/bin/env python3 +"""Independent verifier of the stream audit corpus (extensions/stream-audit.md). + +Written from the extension's text. It shares no code with wai-rs, which generates the corpus, and +never runs it. Acknowledgements and their item lists are read by the settlement corpus's second +implementation, settlement_verify.py (settlement §3.6, the tree root of jwp-receipts §2.2); claims, +their quartile and their run by the ad-presentation corpus's, ad_presentation_verify.py +(ad-presentation §5, §6); the observation log and a figure's acquisition class by the +live-metering corpus's, live_metering_verify.py (live-metering §3.6, §6.1); and every signature +with ../claim-conformance/_ed25519.py, WAI's strict rule (SPEC §7 *Signatures*) written from RFC +8032. Everything else is here, from the standard library: + +- the channel id, the session and an origin's upstream (§3, settlement §14.2); +- the session record: reading, rules and signature (§5); +- the bundle and its verification in §10.2's order, with each refusal's place; +- each claim's verdict and facts (§10.2 step 9); +- the channel report, its bytes and its recomputation (§11). + +For every session and report it requires the outcome the corpus states; of the corpus, that every +reason of §14 and every code it borrows appears in it and that no other file is there. Then it +mutates copies of the corpus, one defect each, and requires every copy to fail; and it drops each +of its own checks in turn and requires the corpus to catch the weakened verifier. + + python3 stream_audit_verify.py + +Exit status 0 only if every case verifies, every mutated copy fails and every weakened verifier +fails. Python 3.8 or later; standard library only. +""" + +import copy +import hashlib +import json +import os +import re +import struct +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HERE) +sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance")) +import live_events_verify as LEV # noqa: E402 +import live_metering_verify as LMV # noqa: E402 +import settlement_verify as SV # noqa: E402 +import ad_presentation_verify as APV # noqa: E402 +import _ed25519 as ED # noqa: E402 + +EXTENSION = os.path.join(HERE, "..", "extensions", "stream-audit.md") + +Refuse = LEV.Refuse + +SESSION_MAGIC = b"WAS1" # §5.1 +SESSION_DOMAIN = b"wai:audit-session\x01" # §5.4 +CHANNEL_DOMAIN = b"wai:audit-channel\x01" # §3.1 +MARKER_DOMAIN = b"wai:audit-marker\x01" # §6.1 +REPORT_DOMAIN = b"wai:audit-report\x01" # §11.3 +ORIGIN_DOMAIN = b"wai:settle-origin\x01" # settlement §14.2 +SESSION_KEY_DOMAIN = b"wai:ad-presentation-session\x01" # §3.2, ad-presentation §3 +NO_STARTUP = 0xFFFFFFFF +QOE_MIN = 50 +LIVE, ON_DEMAND = 1, 2 +ENDED = ["ended", "user", "hidden", "idle", "error"] # §8.2 +ROLES = ["origin", "delivery", "sink_decode", "display"] # §5.3 +CLASS_NAMES = {1: "live", 2: "fetch", 3: "fill", 4: "ancillary"} # settlement §3.3 +LABELS = {0: "unmetered", 2: "ModelBased", 3: "Estimator", 4: "OnChipCounter", 5: "CalibratedInstrument"} +AUDIBLE, AD = 1, 2 +ZERO = bytes(32) + +SKIP = set() + + +def reasons_of_extension(): + """§14's codes, read from the text.""" + with open(EXTENSION, encoding="utf-8") as f: + text = f.read() + at = text.index("\n## 14. Reasons") + end = text.index("\n## ", at + 1) + return [line.split("`")[1] for line in text[at:end].splitlines() if line.startswith("| `")] + + +REASONS = reasons_of_extension() +REUSED = ["truncated", "malformed", "unknown_version", "bad_key", "bad_signature", "record_missing", "energy_invalid", + "qoe_invalid", "ack_invalid", "self_acknowledged", "items_invalid", "items_mismatch", "ack_outside_period", + "ack_overlap", "ack_chain_broken", "claim_malformed", "claim_duplicate", "claim_asset_mismatch"] + + +class Stop(Exception): + """A refusal and its place (§10.2).""" + + def __init__(self, reason, at): + super().__init__(reason) + self.reason, self.at = reason, at + + +def sha(*parts): + h = hashlib.sha256() + for p in parts: + h.update(p) + return h.digest() + + +def given_digest(b): + return sha(b[:max(len(b) - 64, 0)]) + + +def channel_id(name): + """§3.1, or None for a name that is not 1 to 255 bytes of printable ASCII.""" + b = name.encode("utf-8") + if not 1 <= len(b) <= 255 or any(c < 0x21 or c > 0x7E for c in b): + return None + return sha(CHANNEL_DOMAIN, b) + + +def session_of(channel, key): + return sha(SESSION_KEY_DOMAIN, channel, key)[:16] + + +def upstream_of(origin): + return sha(ORIGIN_DOMAIN, origin) + + +def marker_digest(marker): + return sha(MARKER_DOMAIN, marker) + + +def unhex(s): + if not isinstance(s, str) or len(s) % 2 or not re.fullmatch(r"[0-9a-f]*", s): + return None + return bytes.fromhex(s) + + +def check_sig(key, domain, body, sig): + bad = ED.verify(key, domain + body, sig) + if bad: + raise Refuse(bad) + + +class R: + """A cursor for the record and for live-metering's class reader; running out is `truncated`.""" + + def __init__(self, b, p=0): + self.b, self.p = b, p + + def take(self, n): + if self.p + n > len(self.b): + raise Refuse("truncated") + s = self.b[self.p:self.p + n] + self.p += n + return s + + def u8(self): + return self.take(1)[0] + + def u16(self): + return struct.unpack("I", self.take(4))[0] + + def be64(self): + return struct.unpack(">Q", self.take(8))[0] + + +# ---------------------------------------------------------------- §5 the session record + +def origin_ok(o): + """§5.5.""" + if not 1 <= len(o) <= 2048 or any(c < 0x21 or c > 0x7E for c in o): + return False + for scheme in (b"https://", b"http://"): + if o.startswith(scheme): + rest = o[len(scheme):] + return rest != b"" and not any(c in b"/?#@" for c in rest) + return False + + +def ascending(v): + return all(x < y for x, y in zip(v, v[1:])) + + +def read_session(b): + """§5.5: the record, its rules, its energy entries, then its signature.""" + if len(b) < 4: + raise Refuse("truncated") + if b[:4] != SESSION_MAGIC: + raise Refuse("malformed") + if len(b) < 5: + raise Refuse("truncated") + if b[4] != 1: + raise Refuse("unknown_version") + r = R(b, 5) + s = {"channel": r.take(32), "owner": r.take(32), "player": r.take(32), "session": r.take(16), "presentation": r.u8(), + "start": r.u64(), "end": r.u64(), "ended": r.u8()} + s["origins"] = [r.take(r.u16()) for _ in range(r.u8())] + s["acks"] = [r.take(32) for _ in range(r.u16())] + s["claims"] = [r.take(32) for _ in range(r.u16())] + s["spans"] = [] + for _ in range(r.u32()): + start, length, vis, flags = struct.unpack(" s["start"] else 0 + prev = 0 + for x in s["spans"]: + if x["length"] < 1 or x["visible"] > 10000 or x["flags"] > 3 or x["start"] < prev or x["start"] + x["length"] > limit: + return False + prev = x["start"] + x["length"] + roles = [e["role"] for e in s["energy"]] + return (s["presentation"] in (LIVE, ON_DEMAND) and s["start"] < s["end"] and 1 <= s["ended"] <= 5 + and s["origins"] and all(origin_ok(o) for o in s["origins"]) and ascending(s["origins"]) + and s["acks"] and ascending(s["acks"]) and ascending(s["claims"]) and ascending(s["attachments"]) + and (s["startup_ms"] == NO_STARTUP) == (not s["spans"]) + and (s["stalls"] != 0 or s["stall_ms"] == 0) + and (s["repeat_bytes"] == 0 if s["repeat_objects"] == 0 else s["repeat_bytes"] >= s["repeat_objects"]) + and s["log"] != ZERO and ascending(roles) and all(1 <= x <= 4 for x in roles)) + + +# ---------------------------------------------------------------- §9 the bundle + +def exact(v, members): + return isinstance(v, dict) and set(v) == set(members) + + +def read_bundle(raw): + """§9: the bundle, or Stop(audit_bundle_invalid).""" + def bad(): + return Stop("audit_bundle_invalid", ["bundle"]) + try: + v = json.loads(raw.decode("utf-8")) + except (UnicodeDecodeError, ValueError): + raise bad() + if not exact(v, ["v", "session", "acks", "claims", "log", "notes", "attachments"]) or v["v"] != 1 or isinstance(v["v"], bool): + raise bad() + out = {"session": unhex(v["session"]), "acks": [], "claims": [], "attachments": []} + if out["session"] is None or not isinstance(v["acks"], list) or not isinstance(v["claims"], list) or not isinstance(v["attachments"], list): + raise bad() + for a in v["acks"]: + if not exact(a, ["record", "items"]) or not isinstance(a["items"], list): + raise bad() + rec, items = unhex(a["record"]), [unhex(i) for i in a["items"]] + if rec is None or any(i is None for i in items): + raise bad() + out["acks"].append((rec, items)) + for c in v["claims"]: + if not exact(c, ["claim", "marker", "creative"]) or not isinstance(c["marker"], str) or not isinstance(c["creative"], list) or not c["creative"]: + raise bad() + objects = [] + for o in c["creative"]: + if not exact(o, ["content_hash", "duration_ms"]): + raise bad() + h, d = unhex(o["content_hash"]), o["duration_ms"] + if h is None or len(h) != 32 or not isinstance(d, int) or isinstance(d, bool) or not 1 <= d <= 0xFFFFFFFF: + raise bad() + objects.append({"content_hash": h, "duration_ms": d}) + claim = unhex(c["claim"]) + if claim is None: + raise bad() + out["claims"].append({"claim": claim, "marker": c["marker"].encode("utf-8"), "creative": objects}) + if not isinstance(v["log"], str) or not (v["notes"] is None or isinstance(v["notes"], str)): + raise bad() + out["log"] = v["log"].encode("utf-8") + out["notes"] = None if v["notes"] is None else v["notes"].encode("utf-8") + for a in v["attachments"]: + if not exact(a, ["name", "record"]) or not isinstance(a["name"], str) or unhex(a["record"]) is None: + raise bad() + out["attachments"].append(unhex(a["record"])) + return out + + +# ---------------------------------------------------------------- §10 verification + +def stopped(fn, at): + try: + return fn() + except Refuse as e: + raise Stop(e.reason, at) + + +def read_one_log(b): + """§10.2 step 6: a logs document of exactly one log: its sid and its metrics.""" + try: + doc = json.loads(b.decode("utf-8")) + except (UnicodeDecodeError, ValueError): + raise Refuse("qoe_invalid") + if not isinstance(doc, list) or len(doc) != 1: + raise Refuse("qoe_invalid") + LMV.read_logs(b) + return doc[0][0]["sid"], LMV.session_metrics(doc[0]) + + +def read_notes(b, acknowledged): + """§7.3: a notes document's count of responses, or None for one that does not read.""" + try: + doc = json.loads(b.decode("utf-8")) + except (UnicodeDecodeError, ValueError): + return None + if not isinstance(doc, list): + return None + for n in doc: + if not exact(n, ["content_hash", "cmsd_static", "cmsd_dynamic"]): + return None + h = n["content_hash"] + if not isinstance(h, str) or not re.fullmatch(r"[0-9a-f]{64}", h): + return None + if "notes-acked" not in SKIP and bytes.fromhex(h) not in acknowledged: + return None + if any(not (n[k] is None or isinstance(n[k], str)) for k in ("cmsd_static", "cmsd_dynamic")): + return None + return len(doc) + + +def verify_bundle(raw, owner, channels): + """§10.2: the outcome of a bundle, as §10.3 states it.""" + try: + return accept_json(verify(raw, owner, channels)) + except Stop as e: + return {"outcome": "refuse", "reason": e.reason, "at": e.at} + + +def verify(raw, owner, channels): + # 1. The bundle. + b = read_bundle(raw) + # 2. The record. + s = stopped(lambda: read_session(b["session"]), ["session"]) + # 3. Its binding. + if "owner" not in SKIP and s["owner"] != owner: + raise Stop("audit_wrong_owner", ["session"]) + if "channel" not in SKIP and not any(channel_id(c) == s["channel"] for c in channels): + raise Stop("audit_unknown_channel", ["session"]) + if "session-bound" not in SKIP and session_of(s["channel"], s["player"]) != s["session"]: + raise Stop("audit_session_unbound", ["session"]) + # 4. Each acknowledgement. + upstreams = {upstream_of(o): o for o in s["origins"]} + by_digest = {} + for k, (rec, _) in enumerate(b["acks"]): + by_digest.setdefault(given_digest(rec), k) + media = 1 if s["presentation"] == LIVE else 2 + acks, delivery, acknowledged = [], {}, set() + for i, d in enumerate(s["acks"]): + if d not in by_digest: + raise Stop("record_missing", ["ack", i]) + rec, lists = b["acks"][by_digest[d]] + a = stopped(lambda: SV.read_ack(rec), ["ack", i]) + if "ack-bound" not in SKIP and (a["mode"] != 1 or a["downstream"] != s["player"]): + raise Stop("audit_ack_unbound", ["ack", i]) + if a["upstream"] not in upstreams and "origin" not in SKIP: + raise Stop("audit_origin_unknown", ["ack", i]) + origin = upstreams.get(a["upstream"], b"?") + if "ack-window" not in SKIP and (a["start"] < s["start"] or a["end"] > s["end"]): + raise Stop("ack_outside_period", ["ack", i]) + if "ack-classes" not in SKIP and (a["role"] != 2 or any(e["grant"] != ZERO or e["proof"] != ZERO or e["class"] not in (4, media) for e in a["entries"])): + raise Stop("audit_ack_invalid", ["ack", i]) + if len(lists) > len(a["entries"]): + raise Stop("audit_bundle_invalid", ["ack", i]) + for j, e in enumerate(a["entries"]): + at = ["ack", i, "entry", j] + if j >= len(lists): + raise Stop("record_missing", at) + items = stopped(lambda: SV.read_items(lists[j]), at) + if not SV.recomputes(e, items, lists[j]): + raise Stop("items_mismatch", at) + if "items-start" not in SKIP and any(st != 0 for _, st, _ in items): + raise Stop("audit_ack_invalid", at) + key = (origin, e["class"]) + o, n = delivery.get(key, (0, 0)) + delivery[key] = (o + e["objects"], n + e["bytes"]) + acknowledged.update(h for h, _, _ in items) + acks.append((i, a)) + named, once = set(s["acks"]), set() + for k, (rec, _) in enumerate(b["acks"]): + d = given_digest(rec) + if d not in named or d in once: + raise Stop("audit_bundle_invalid", ["ack", k]) + once.add(d) + # 5. The chains. + chains = {} + for i, a in acks: + chains.setdefault(a["upstream"], []).append((i, a)) + breaks = [] + for chain in chains.values(): + chain.sort(key=lambda x: (x[1]["start"], x[0])) + for n, (i, a) in enumerate(chain): + if n == 0: + if "chain-first" not in SKIP and a["prev"] != ZERO: + breaks.append((i, "ack_chain_broken")) + continue + p = chain[n - 1][1] + if "chain-overlap" not in SKIP and p["end"] > a["start"]: + breaks.append((i, "ack_overlap")) + elif "chain-prev" not in SKIP and a["prev"] != p["digest"]: + breaks.append((i, "ack_chain_broken")) + if breaks: + i, r = min(breaks) + raise Stop(r, ["ack", i]) + # 6. The log. + if "log-digest" not in SKIP and sha(b["log"]) != s["log"]: + raise Stop("audit_document_mismatch", ["log"]) + sid, m = stopped(lambda: read_one_log(b["log"]), ["log"]) + startup = None if s["startup_ms"] == NO_STARTUP else s["startup_ms"] + if "qoe" not in SKIP and (sid != s["session"].hex() or m["vst"] != startup or m["stalls"] != s["stalls"] or m["stall_ms"] != s["stall_ms"]): + raise Stop("audit_qoe_mismatch", ["log"]) + # 7. The notes. + if s["notes"] == ZERO and b["notes"] is None: + notes = 0 + elif s["notes"] != ZERO and b["notes"] is not None and (sha(b["notes"]) == s["notes"] or "notes-digest" in SKIP): + notes = read_notes(b["notes"], acknowledged) + if notes is None: + raise Stop("audit_notes_invalid", ["notes"]) + else: + raise Stop("audit_document_mismatch", ["notes"]) + # 8. The attachments. + given = [sha(x) for x in b["attachments"]] + for i, d in enumerate(s["attachments"]): + if "attach" not in SKIP and d not in given: + raise Stop("record_missing", ["attachment", i]) + for j, d in enumerate(given): + if d not in s["attachments"]: + raise Stop("audit_document_mismatch", ["attachment", j]) + # 9. The claims. + by_claim = {} + for k, c in enumerate(b["claims"]): + by_claim.setdefault(given_digest(c["claim"]), k) + order = [] + for i, d in enumerate(s["claims"]): + if d not in by_claim: + raise Stop("record_missing", ["claim", i]) + order.append(by_claim[d]) + named, once = set(s["claims"]), set() + for k, c in enumerate(b["claims"]): + d = given_digest(c["claim"]) + if "bundle-extra" not in SKIP and (d not in named or d in once): + raise Stop("audit_bundle_invalid", ["claim", k]) + once.add(d) + seen = set() + verdicts = [judge(b["claims"][k], s, acknowledged, seen) for k in order] + return {"session": s, "delivery": delivery, "claims": verdicts, "notes": notes} + + +def judge(c, s, acknowledged, seen): + """§10.2 step 9: one claim's verdict and facts.""" + try: + x = APV.read_claim(c["claim"]) + except APV.Bad as e: + return {"verdict": str(e)} + bad = ED.verify(x["player"], APV.CLAIM_DOMAIN + x["body"], x["sig"]) + if bad: + return {"verdict": bad} + if "claim-bound" not in SKIP and (x["room"] != s["channel"] or x["session"] != s["session"] or x["player"] != s["player"]): + return {"verdict": "audit_claim_unbound"} + if "marker" not in SKIP and x["cue"] != marker_digest(c["marker"]): + return {"verdict": "audit_marker_mismatch"} + if "duplicate" not in SKIP and (x["cue"], x["slot"]) in seen: + return {"verdict": "claim_duplicate"} + seen.add((x["cue"], x["slot"])) + d = sum(o["duration_ms"] for o in c["creative"]) + if "asset" not in SKIP and (x["creative_hash"] != APV.creative_digest(c["creative"]) or any(sp["start"] + sp["length"] > d for sp in x["spans"]) + or x["objects"] != APV.touched(c["creative"], x["spans"])): + return {"verdict": "claim_asset_mismatch"} + if "acked" not in SKIP and any(h not in acknowledged for h in x["objects"]): + return {"verdict": "audit_ad_not_acknowledged"} + run, viewable = APV.runs(x, {"rule": APV.VIDEO, "require": APV.CLAIM}) + return {"verdict": "presented", "quartile": APV.quartile(x["spans"], d), "run_ms": run, "viewable_ms": viewable, "viewable": run >= 2000} + + +def presented(s): + f = [0, 0, 0, 0] + for x in s["spans"]: + f[0] += x["length"] + f[1] += x["length"] if x["visible"] >= 5000 else 0 + f[2] += x["length"] if x["flags"] & AUDIBLE else 0 + f[3] += x["length"] if x["flags"] & AD else 0 + return f + + +def tag(cls): + return 0 if cls is None else cls[0] + + +def accept_json(v): + s = v["session"] + p = presented(s) + return { + "outcome": "accept", "digest": s["digest"].hex(), "channel": s["channel"].hex(), + "presentation": "live" if s["presentation"] == LIVE else "on_demand", "start": s["start"], "end": s["end"], + "ended": ENDED[s["ended"] - 1], "presented_ms": p[0], "viewable_ms": p[1], "audible_ms": p[2], "ad_ms": p[3], + "delivery": [{"origin": o.decode(), "class": CLASS_NAMES[c], "objects": n, "bytes": by} for (o, c), (n, by) in sorted(v["delivery"].items())], + "repeats": {"objects": s["repeat_objects"], "bytes": s["repeat_bytes"]}, + "startup_ms": None if s["startup_ms"] == NO_STARTUP else s["startup_ms"], "stalls": s["stalls"], "stall_ms": s["stall_ms"], + "energy": [{"role": ROLES[e["role"] - 1], "class": LABELS[tag(e["class"])], "joules_micro": e["joules"]} for e in s["energy"]], + "claims": v["claims"], "notes": v["notes"], "attachments": len(s["attachments"]), + } + + +# ---------------------------------------------------------------- §11 the report + +def report(name, owner, frm, to, accepted): + """§11.1, §11.2: the report over the accepted sessions of the channel, the owner and the period.""" + channel = channel_id(name) + sessions = {} + for a in accepted: + s = a["session"] + if s["channel"] == channel and s["owner"] == owner and frm <= s["start"] < to: + sessions[s["digest"]] = a + sessions = [sessions[d] for d in sorted(sessions)] + if "report-dup" not in SKIP and len({a["session"]["session"] for a in sessions}) != len(sessions): + raise Stop("audit_session_duplicate", ["report"]) + pres, ends = [0, 0, 0, 0], [0] * 5 + claims = shown = viewable = 0 + quartiles, refused = [0] * 5, {} + with_span = startup = stalls = stall_ms = 0 + delivery, rep, energy = {}, [0, 0], {} + unmetered, absent = [0] * 4, [0] * 4 + for a in sessions: + s = a["session"] + pres = [x + y for x, y in zip(pres, presented(s))] + ends[s["ended"] - 1] += 1 + for c in a["claims"]: + claims += 1 + if c["verdict"] == "presented": + shown += 1 + if 1 <= c["quartile"] <= 5: + quartiles[c["quartile"] - 1] += 1 + viewable += 1 if c["viewable"] else 0 + else: + refused[c["verdict"]] = refused.get(c["verdict"], 0) + 1 + if s["spans"]: + with_span += 1 + startup += s["startup_ms"] + stalls += s["stalls"] + stall_ms += s["stall_ms"] + for k, (n, by) in a["delivery"].items(): + o0, b0 = delivery.get(k, (0, 0)) + delivery[k] = (o0 + n, b0 + by) + rep[0] += s["repeat_objects"] + rep[1] += s["repeat_bytes"] + for r in range(1, 5): + es = [e for e in s["energy"] if e["role"] == r] + if not es: + absent[r - 1] += 1 + elif es[0]["class"] is None and "unmetered-apart" not in SKIP: + unmetered[r - 1] += 1 + else: + k = (r, tag(es[0]["class"])) + f, j = energy.get(k, (0, 0)) + energy[k] = (f + 1, j + es[0]["joules"]) + n = len(sessions) + sup = n < QOE_MIN and "suppress" not in SKIP + + def q(v): + return None if sup else v + return { + "v": 1, "channel": channel.hex(), "owner": owner.hex(), "from": frm, "to": to, + "sessions": [a["session"]["digest"].hex() for a in sessions], + "presented": {"presented_ms": pres[0], "viewable_ms": pres[1], "audible_ms": pres[2], "ad_ms": pres[3]}, + "ended": dict(zip(ENDED, ends)), + "ads": {"claims": claims, "presented": shown, "quartiles": quartiles, "viewable": viewable, "refused": refused}, + "rebuffering": {"sessions": n, "suppressed": sup, "presented": q(with_span), "startup_ms": q(startup), "stalls": q(stalls), "stall_ms": q(stall_ms)}, + "delivery": [{"origin": o.decode(), "class": CLASS_NAMES[c], "objects": k, "bytes": by} for (o, c), (k, by) in sorted(delivery.items())], + "repeats": {"objects": rep[0], "bytes": rep[1]}, + "energy": [{"role": ROLES[r - 1], "class": LABELS[t], "figures": f, "joules_micro": j} for (r, t), (f, j) in sorted(energy.items())], + "unmetered": dict(zip(ROLES, unmetered)), "absent": dict(zip(ROLES, absent)), + } + + +def report_bytes(v): + """§11.3: members in bytewise order, no white space, no escape but \\" and \\\\ (the report is ASCII).""" + return json.dumps(v, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("ascii") + + +def check_report(served, bundles, name, owner, channels): + """§11.4.""" + accepted = [] + for k, raw in enumerate(bundles): + try: + accepted.append(verify(raw, owner, channels)) + except Stop as e: + raise Stop(e.reason, ["bundle", k] + e.at) + if not exact(served, ["report", "sig"]): + raise Stop("audit_report_mismatch", ["report"]) + r, sig = served["report"], unhex(served["sig"]) + frm, to, names = r.get("from") if isinstance(r, dict) else None, r.get("to") if isinstance(r, dict) else None, r.get("sessions") if isinstance(r, dict) else None + if sig is None or len(sig) != 64 or not isinstance(frm, int) or not isinstance(to, int) or not isinstance(names, list): + raise Stop("audit_report_mismatch", ["report"]) + names = [unhex(x) for x in names] + if any(x is None or len(x) != 32 for x in names): + raise Stop("audit_report_mismatch", ["report"]) + recomputed = report(name, owner, frm, to, accepted) + digests = {a["session"]["digest"] for a in accepted} + for i, d in enumerate(names): + if "report-missing" not in SKIP and d not in digests: + raise Stop("record_missing", ["report", "session", i]) + if "report-bytes" not in SKIP and report_bytes(r) != report_bytes(recomputed): + raise Stop("audit_report_mismatch", ["report"]) + if "report-signature" not in SKIP and ED.verify(owner, REPORT_DOMAIN + report_bytes(r), sig): + raise Stop("bad_signature", ["report"]) + + +# ---------------------------------------------------------------- the corpus + +def bundle_bytes(v): + if "bundle_text" in v: + return v["bundle_text"].encode("utf-8") + return json.dumps(v["bundle"]).encode("utf-8") + + +def report_outcome(doc, v, owner, channels): + bundles = [] + for n in v["sessions"]: + for s in doc["sessions"]: + if s["name"] == n: + bundles.append(bundle_bytes(s)) + break + try: + check_report(v["served"], bundles, v["channel"], owner, channels) + return {"outcome": "accept", "digest": sha(report_bytes(v["served"]["report"])).hex()} + except Stop as e: + return {"outcome": "refuse", "reason": e.reason, "at": e.at} + + +def check(doc, quiet=False): + """Every vector's outcome: the failures.""" + fails = [] + owner, channels = bytes.fromhex(doc["owner"]), doc["channels"] + for v in doc["sessions"]: + got = verify_bundle(bundle_bytes(v), owner, channels) + if got != v["outcome"]: + fails.append("session %s: stated %s, got %s" % (v["name"], json.dumps(v["outcome"])[:300], json.dumps(got)[:300])) + for v in doc["reports"]: + got = report_outcome(doc, v, owner, channels) + if got != v["outcome"]: + fails.append("report %s: stated %s, got %s" % (v["name"], v["outcome"], got)) + if not quiet: + for f in fails: + print("FAIL " + f) + return fails + + +def reasons_given(doc): + seen = set() + for section in ("sessions", "reports"): + for v in doc[section]: + if v["outcome"].get("reason"): + seen.add(v["outcome"]["reason"]) + for c in v["outcome"].get("claims", []): + seen.add(c["verdict"]) + return seen + + +def flip_hex(h, at): + b = bytearray(bytes.fromhex(h)) + b[at % len(b)] ^= 1 + return b.hex() + + +def mutations(doc): + """Copies of the corpus, each with one defect a conforming verifier notices.""" + def find(d, section, name): + return next(v for v in d[section] if v["name"] == name) + + def m_sig(d): + v = find(d, "sessions", "live_two_origins") + v["bundle"]["session"] = flip_hex(v["bundle"]["session"], -1) + + def m_figure(d): + find(d, "sessions", "live_two_origins")["outcome"]["presented_ms"] += 1 + + def m_verdict(d): + find(d, "sessions", "claim_verdicts")["outcome"]["claims"][0]["quartile"] = 5 + + def m_drop_ack(d): + find(d, "sessions", "on_demand_hidden")["bundle"]["acks"].pop() + + def m_item(d): + v = find(d, "sessions", "live_two_origins") + v["bundle"]["acks"][0]["items"][0] = flip_hex(v["bundle"]["acks"][0]["items"][0], 5) + + def m_log(d): + find(d, "sessions", "nothing_presented")["bundle"]["log"] += " " + + def m_marker(d): + find(d, "sessions", "live_two_origins")["bundle"]["claims"][0]["marker"] += "x" + + def m_reason(d): + find(d, "sessions", "ack_chain_broken")["outcome"]["reason"] = "ack_overlap" + + def m_place(d): + find(d, "sessions", "items_not_whole")["outcome"]["at"] = ["ack", 0] + + def m_channels(d): + d["channels"] = [c for c in d["channels"] if c != "film-library"] + + def m_owner(d): + d["owner"] = flip_hex(d["owner"], 3) + + def m_report(d): + find(d, "reports", "news_east")["served"]["report"]["ads"]["presented"] += 1 + + def m_report_sessions(d): + find(d, "reports", "fifty")["sessions"].pop() + + def m_notes(d): + v = find(d, "sessions", "live_two_origins") + v["bundle"]["notes"] = v["bundle"]["notes"].replace("rtt=40", "rtt=41") + + def m_creative(d): + find(d, "sessions", "live_two_origins")["bundle"]["claims"][1]["creative"][0]["duration_ms"] += 1 + + return [m_sig, m_figure, m_verdict, m_drop_ack, m_item, m_log, m_marker, m_reason, m_place, m_channels, m_owner, + m_report, m_report_sessions, m_notes, m_creative] + + +CHECKS = ["session-rules", "energy", "session-signature", "owner", "channel", "session-bound", "ack-bound", "origin", + "ack-window", "ack-classes", "items-start", "chain-first", "chain-overlap", "chain-prev", "log-digest", "qoe", + "notes-digest", "notes-acked", "attach", "bundle-extra", "claim-bound", "marker", "duplicate", "asset", "acked", + "report-dup", "report-missing", "report-bytes", "report-signature", "suppress", "unmetered-apart"] + + +def main(corpus_dir): + with open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8") as f: + doc = json.load(f) + ok = True + files = sorted(os.listdir(corpus_dir)) + if files != ["README.md", "vectors.json"]: + print("FAIL the corpus directory holds %s" % files) + ok = False + fails = check(doc) + print("%s %d sessions and %d reports get the outcomes the corpus states" % ("PASS" if not fails else "FAIL", len(doc["sessions"]), len(doc["reports"]))) + ok = ok and not fails + seen = reasons_given(doc) + missing = [r for r in REASONS + REUSED if r not in seen] + print("%s every reason of §14 and every borrowed code appears%s" % ("PASS" if not missing else "FAIL", "" if not missing else ": missing " + ", ".join(missing))) + ok = ok and not missing + survived = [] + for m in mutations(doc): + d = copy.deepcopy(doc) + m(d) + if not check(d, quiet=True): + survived.append(m.__name__) + n = len(mutations(doc)) + print("%s %d of %d mutated copies of the corpus fail%s" % ("PASS" if not survived else "FAIL", n - len(survived), n, "" if not survived else ": " + ", ".join(survived))) + ok = ok and not survived + uncaught = [] + for c in CHECKS: + SKIP.add(c) + try: + if not check(doc, quiet=True): + uncaught.append(c) + finally: + SKIP.discard(c) + print("%s the corpus catches each of %d checks dropped%s" % ("PASS" if not uncaught else "FAIL", len(CHECKS), "" if not uncaught else ": not caught " + ", ".join(uncaught))) + ok = ok and not uncaught + return 0 if ok else 1 + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("usage: stream_audit_verify.py ") + sys.exit(2) + sys.exit(main(sys.argv[1])) diff --git a/wai/wai-rs/src/stream_audit/corpus.rs b/wai/wai-rs/src/stream_audit/corpus.rs index af39d5ebf..9fecc991e 100644 --- a/wai/wai-rs/src/stream_audit/corpus.rs +++ b/wai/wai-rs/src/stream_audit/corpus.rs @@ -623,6 +623,18 @@ fn vectors() -> (Vec, Vec<(String, Built)>) { x.acks = vec![first, second]; v.push(refused("ack_chain_broken", x.reseal().bundle(), "ack_chain_broken")); + let mut x = c.clone(); + let items = [Item { content_hash: h("p0"), start: 5, end: object("p0").len() as u64 }]; + let e = Entry::of(settlement::class::ANCILLARY, ZERO, ZERO, &items); + let partial = Ack { mode: settlement::mode::ACK, role: settlement::role::SINK, upstream: up_a, downstream: player, start: cs, end: ce, prev: ZERO, entries: vec![e], sig: [0; 64] }.sign(k); + x.acks = vec![(partial, vec![settlement::item_list(&items)])]; + v.push(refused("ack_item_not_whole_object", x.reseal().bundle(), "audit_ack_invalid")); + let mut x = c.clone(); + x.acks = vec![ack(k, up_a, player, cs, ce, sha256(&[b"an acknowledgement before the first"]), &[(4, &["p0"])])]; + v.push(refused("ack_first_names_a_prev", x.reseal().bundle(), "ack_chain_broken")); + let mut t = a.bundle(); + t.notes = t.notes.map(|n| n.replace("rtt=40", "rtt=41")); + v.push(refused("notes_not_the_bound_ones", t, "audit_document_mismatch")); let mut t = c.bundle(); t.log.push(' '); v.push(refused("log_not_the_bound_one", t, "audit_document_mismatch")); -- 2.54.0 (Apple Git-157) From 27d76115e0595329d44becf27fedf3c05c605a8f Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 25 Oct 2026 21:33:10 -0400 Subject: [PATCH 085/242] wai: key_release_verify.py, the key-release corpus's second implementation Written from extensions/key-release.md, sharing no code with wai-rs: it reads the scheme, delivery, decision and reason tables from the extension, derives each list from its CPIX document with the standard library's XML parser, recomputes every decision of every log, and requires every mutated copy of the corpus to fail. Co-Authored-By: Claude Opus 5.5 --- wai/live-conformance/key_release_verify.py | 865 +++++++++++++++++++++ 1 file changed, 865 insertions(+) create mode 100644 wai/live-conformance/key_release_verify.py diff --git a/wai/live-conformance/key_release_verify.py b/wai/live-conformance/key_release_verify.py new file mode 100644 index 000000000..081348f26 --- /dev/null +++ b/wai/live-conformance/key_release_verify.py @@ -0,0 +1,865 @@ +#!/usr/bin/env python3 +"""Independent verifier of the key-release corpus (extensions/key-release.md). + +Written from the extension's text. It shares no code with wai-rs, which generates the corpus, +and never runs it. It checks Ed25519 signatures with ../claim-conformance/_ed25519.py, WAI's +strict rule (SPEC §7 *Signatures*) written from RFC 8032 with the standard library, and +computes BLAKE3 with ../claim-conformance/_blake3.py, written from its specification. It reads +CPIX documents with the standard library's XML parser. Everything else is here: + +- the scheme, delivery and decision codes, the deliveries' schemes and the reasons, read + from the extension's tables (§3.1, §3.2, §3.3, §7.2, §11); +- the commitment list (§4): its layout, rules, signature and identity, and its derivation from + a CPIX document (§4.4); +- the entitlement (§5) and the device's proof over a request (§6.1); +- the release decision and the licence it permits (§7); +- the proxy form of the wai.video.keys receipt (§8): its JSON carriage, signing payload, + identity and own rules; +- the release ticket (§9.3) and verification of a log (§10). + +For every vector it requires the outcome, reason and place, and every fact the corpus states; +of the corpus, that every reason of §11 and every decision of §7.2 appears in it and that no +other file is there. Then it mutates copies of the corpus, one defect each, and requires every +copy to fail. + + python3 key_release_verify.py + +Exit status 0 only if every vector verifies and every mutated copy fails. Python 3.8 or later; +standard library only. +""" + +import base64 +import calendar +import copy +import datetime +import hashlib +import json +import os +import re +import struct +import sys +import xml.etree.ElementTree as ET + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance")) +import _ed25519 as ED # noqa: E402 +import _blake3 as B3 # noqa: E402 + +EXTENSION = os.path.join(HERE, "..", "extensions", "key-release.md") + +LIST_MAGIC, ENT_MAGIC, TICKET_MAGIC = b"WKL1", b"WKE1", b"WKT1" +LIST_DOMAIN = b"wai:video-keys-list\x01" # §4.2 +LIST_ID_DOMAIN = b"wai:video-keys-list-id\x01" +ENT_DOMAIN = b"wai:video-keys-entitlement\x01" # §5.2 +REQUEST_DOMAIN = b"wai:video-keys-request\x01" # §6.1 +RECEIPT_DOMAIN = b"wai:video-keys-proxy-receipt\x01" # §8.3 +ID_DOMAIN = b"wai:video-keys-receipt-id\x01" +TICKET_DOMAIN = b"wai:video-keys-ticket\x01" # §9.3 +COMMIT_DOMAIN = b"wai:video-keys-commit\x01" # §4.1, form 1's commitment +CPIX = "urn:dashif:org:cpix" # §4.4 +PSKC = "urn:ietf:params:xml:ns:keyprov:pskc" +FRESH = 300 # §7.2 check 2 +MAX_KEYS, MAX_LIST, MAX_TICKET = 64, 4096, 300 +U64, U32, U16 = 2 ** 64 - 1, 2 ** 32 - 1, 2 ** 16 - 1 +FLOOR = 288676 # §8.4 rule 10 +CLASS_TAG = {"ModelBased": 2, "Estimator": 3, "OnChipCounter": 4, "CalibratedInstrument": 5} # energy-measurement §2.7 +FILTERING = {"Unknown": 0, "Off": 1, "On": 2} +KINDS = {"streaming": 1, "offline": 2, "renewal": 3} # §6.1 +WHITE_SPACE = set(map(chr, [0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x20, 0x85, 0xA0, 0x1680] + list(range(0x2000, 0x200B)) + + [0x2028, 0x2029, 0x202F, 0x205F, 0x3000])) + + +# ---------------------------------------------------------------- the extension's tables + +def _text(): + with open(EXTENSION, encoding="utf-8") as f: + return f.read() + + +def table(heading): + """The body rows of the first table under `heading`, each a list of its cells.""" + text = _text() + at = text.index("\n" + heading + "\n") + len(heading) + 2 + end = text.find("\n#", at) + rows = [] + for line in text[at:end if end >= 0 else len(text)].splitlines(): + if line.startswith("|") and not set(line) <= set("|- "): + rows.append([c.strip() for c in line.strip("|").split("|")]) + return rows[1:] + + +def code(cell): + return cell.strip().strip("`") + + +SCHEMES = {code(r[1]): (int(r[0]), int(r[4])) for r in table("### 3.1 Schemes")} # label: (code, octets) +DELIVERIES = {code(r[1]): int(r[0]) for r in table("### 3.2 Key deliveries")} # kind: code +CARRIES = {} +for _r in table("### 3.3 Which deliveries carry which schemes"): + _named = re.findall(r"`([^`]+)`", _r[1]) + CARRIES[code(_r[0])] = set(SCHEMES) if _r[1].startswith("all") else {s for s in _named if s in SCHEMES} +DECISIONS = {code(r[1]): int(r[2]) for r in table("### 7.2 The checks, in order")} # label: code +REASONS = [code(r[0]) for r in table("## 11. Reasons")] +SCHEME_OF = {c: label for label, (c, _) in SCHEMES.items()} +DELIVERY_OF = {c: label for label, c in DELIVERIES.items()} + + +class Refuse(Exception): + def __init__(self, reason, at=None): + super().__init__(reason) + self.reason, self.at = reason, at + + +def sha(*parts): + h = hashlib.sha256() + for p in parts: + h.update(p) + return h.digest() + + +def b3(*parts): + return B3.blake3(b"".join(parts)) + + +def be(n, v): + return v.to_bytes(n, "big") + + +def string(s): + b = s.encode("utf-8") + return be(4, len(b)) + b + + +def opt(b): + return b"\x00" if b is None else b"\x01" + b + + +def commitment(key): + """§4.1: form 1's key commitment.""" + return b3(COMMIT_DOMAIN, be(4, len(key)), key) + + +class Reader: + def __init__(self, b): + self.b, self.at = b, 0 + + def take(self, n): + if self.at + n > len(self.b): + raise ValueError("short") + s = self.b[self.at:self.at + n] + self.at += n + return s + + def int(self, n): + return int.from_bytes(self.take(n), "big") + + def string(self): + return self.take(self.int(4)).decode("utf-8") + + def done(self): + return self.at == len(self.b) + + +def period_bytes(p): + """§4.1: a period's tag and fields; None is tag 0.""" + if p is None: + return b"\x00" + tag = (1 if p["index"] is not None else 0) + (2 if p["start"] is not None else 0) + out = bytes([tag]) + if p["index"] is not None: + out += be(4, p["index"]) + if p["start"] is not None: + out += be(8, p["start"]) + be(8, p["end"]) + return out + + +def read_period(r): + tag = r.int(1) + if tag == 0: + return None + if tag > 3: + raise ValueError("tag") + index = r.int(4) if tag & 1 else None + start, end = (r.int(8), r.int(8)) if tag & 2 else (None, None) + return {"index": index, "start": start, "end": end} + + +# ---------------------------------------------------------------- §4 the commitment list + +def read_list(b): + """§4.1 and §4.3: the list, or Refuse.""" + try: + r = Reader(b) + if r.take(4) != LIST_MAGIC or r.int(1) != 1: + raise ValueError("magic") + content, required = r.string(), r.string() + flag = r.int(1) + if flag > 1: + raise ValueError("flag") + cpix = r.take(32) if flag else None + issued, n = r.int(8), r.int(4) + if not 1 <= n <= MAX_LIST or not content or not required: + raise ValueError("count") + keys = [] + for _ in range(n): + kid, scheme, commit = r.take(16), r.int(1), r.take(32) + p = read_period(r) + if scheme not in SCHEME_OF or commit == bytes(32) or (keys and keys[-1]["kid"] >= kid): + raise ValueError("key") + if p is not None and p["start"] is not None and p["start"] >= p["end"]: + raise ValueError("window") + keys.append({"kid": kid, "scheme": SCHEME_OF[scheme], "commit": commit, "period": p}) + pk = r.take(32) + sid = r.string() + signed_end = r.at + sig = r.take(64) + if not r.done(): + raise ValueError("trailing") + except (ValueError, UnicodeDecodeError): + raise Refuse("list_malformed") + if ED.verify(pk, LIST_DOMAIN + b[:signed_end], sig): + raise Refuse("list_bad_signature") + return {"content": content, "required": required, "cpix": cpix, "keys": {k["kid"]: k for k in keys}, + "identity": b3(LIST_ID_DOMAIN, b), "signer": pk, "signer_id": sid} + + +# ---------------------------------------------------------------- §4.4 a CPIX document + +UUID = re.compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$") +TIME = re.compile(r"^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$") + + +def q(name, ns=CPIX): + return "{%s}%s" % (ns, name) + + +def uuid(s): + return bytes.fromhex(s.replace("-", "")) if s is not None and UUID.match(s) else None + + +def seconds(s): + m = TIME.match(s or "") + if not m: + return None + try: + t = calendar.timegm(datetime.datetime(*map(int, m.groups())).timetuple()) + except ValueError: + return None + return t if t >= 0 else None + + +def b64(text): + s = "".join(c for c in (text or "") if c not in " \t\n\r") + try: + v = base64.b64decode(s, validate=True) + except ValueError: + return None + return v if base64.b64encode(v).decode() == s else None + + +def derive(data): + """§4.4 steps 1 to 5: (content_id, digest, keys by KID), or Refuse.""" + # Step 1. + head = data[:200].decode("utf-8", "replace") + m = re.match(r"^?<\?xml[^>]*encoding\s*=\s*[\"']([^\"']+)[\"']", head) + if b" U32: + raise Refuse("cpix_unsupported") + index = int(index) + start, end = p.get("start"), p.get("end") + if (start is None) != (end is None): + raise Refuse("cpix_unsupported") + if start is not None: + start, end = seconds(start), seconds(end) + if start is None or end is None: + raise Refuse("cpix_unsupported") + if start >= end: + raise Refuse("cpix_malformed") + if index is None and start is None: + raise Refuse("cpix_unsupported") + return {"index": index, "start": start, "end": end} + + +# ---------------------------------------------------------------- §5 the entitlement, §6 the proof + +def read_entitlement(b): + """§5.1's layout, or None.""" + try: + r = Reader(b) + if r.take(4) != ENT_MAGIC or r.int(1) != 1: + return None + e = {"session": r.take(16), "holder": r.take(32), "class": r.string(), "nb": r.int(8), "na": r.int(8), + "offline": r.int(8), "renewals": r.int(4), "authority": r.take(32), "authority_id": r.string()} + end = r.at + sig = r.take(64) + if not r.done(): + return None + except (ValueError, UnicodeDecodeError): + return None + e["holds"] = (bool(e["class"]) and e["nb"] <= e["na"] and (e["offline"] > 0 or e["renewals"] == 0) + and ED.verifies(e["authority"], ENT_DOMAIN + b[:end], sig)) + return e + + +def proof_payload(ent, r): + """§6.1.""" + d = r["delivery"] + out = [REQUEST_DOMAIN, sha(ent), string(r["content_id"]), bytes([DELIVERIES[d["kind"]]]), opt(d["system_id"]), + be(4, len(r["keys"]))] + out += [k["kid"] for k in r["keys"]] + out += [bytes([KINDS[r["kind"]]]), opt(r["renews"]), r["request_sha256"], be(8, r["request_t"])] + return b"".join(out) + + +# ---------------------------------------------------------------- §8 the receipt + +MEMBERS = ["form", "content_id", "list", "cpix_sha256", "required_entitlement", "delivery", "keys", "kind", "renews", + "entitlement", "request_sha256", "request_t", "proof", "issued", "decision", "licence_expiry", "renewal", + "upstream_status", "response_sha256", "released", "joules_micro", "energy_provenance", "signer_pubkey", + "signer_id", "sig"] +HEX = re.compile(r"^(?:[0-9a-f]{2})*$") + + +def is_int(v, top=U64): + return isinstance(v, int) and not isinstance(v, bool) and 0 <= v <= top + + +def hexb(v, n=None, null=False): + if v is None and null: + return None + if not isinstance(v, str) or not HEX.match(v) or (n is not None and len(v) != 2 * n): + raise ValueError("hex") + return bytes.fromhex(v) + + +def nint(v, top=U64): + if v is None: + return None + if not is_int(v, top): + raise ValueError("int") + return v + + +def nstr(v): + if v is None or isinstance(v, str): + return v + raise ValueError("str") + + +def read_receipt(o): + """§8.2: the receipt, or Refuse receipt_malformed.""" + try: + if not isinstance(o, dict): + raise ValueError("object") + label = o.get("energy_provenance") + want = list(MEMBERS) + if label == "OnChipCounter": + want += ["energy_uncertainty", "energy_counter_filtering"] + elif label == "CalibratedInstrument": + want += ["energy_uncertainty", "energy_calibration_ref"] + elif label is not None and label not in CLASS_TAG: + raise ValueError("class") + if set(o) != set(want) or o["form"] != 2 or not is_int(o["form"]): + raise ValueError("members") + cls = None + if label is not None: + cls = {"label": label} + if label in ("OnChipCounter", "CalibratedInstrument"): + u = o["energy_uncertainty"] + if not isinstance(u, dict) or set(u) != {"relative_ppm", "window_us"} or not is_int(u["relative_ppm"], U32) \ + or not is_int(u["window_us"]): + raise ValueError("uncertainty") + cls.update(ppm=u["relative_ppm"], window=u["window_us"]) + if label == "OnChipCounter": + if o["energy_counter_filtering"] not in FILTERING: + raise ValueError("filtering") + cls["filtering"] = FILTERING[o["energy_counter_filtering"]] + if label == "CalibratedInstrument": + if not isinstance(o["energy_calibration_ref"], str): + raise ValueError("ref") + cls["ref"] = o["energy_calibration_ref"] + d = o["delivery"] + if not isinstance(d, dict) or set(d) != {"kind", "system_id"} or d["kind"] not in DELIVERIES: + raise ValueError("delivery") + keys = [] + if not isinstance(o["keys"], list): + raise ValueError("keys") + for k in o["keys"]: + if not isinstance(k, dict) or set(k) != {"kid", "scheme", "period", "key_commit"}: + raise ValueError("key") + if k["scheme"] is not None and k["scheme"] not in SCHEMES: + raise ValueError("scheme") + p = k["period"] + if p is not None: + if not isinstance(p, dict) or set(p) != {"index", "start", "end"}: + raise ValueError("period") + p = {"index": nint(p["index"], U32), "start": nint(p["start"]), "end": nint(p["end"])} + if (p["start"] is None) != (p["end"] is None) or (p["index"] is None and p["start"] is None): + raise ValueError("period") + keys.append({"kid": hexb(k["kid"], 16), "scheme": k["scheme"], "period": p, + "key_commit": hexb(k["key_commit"], 32, True)}) + if o["kind"] not in KINDS or o["decision"] not in DECISIONS or not isinstance(o["released"], bool): + raise ValueError("labels") + for k in ("content_id", "signer_id"): + if not isinstance(o[k], str): + raise ValueError(k) + r = { + "content_id": o["content_id"], "list": hexb(o["list"], 32, True), "cpix_sha256": hexb(o["cpix_sha256"], 32, True), + "required": nstr(o["required_entitlement"]), + "delivery": {"kind": d["kind"], "system_id": hexb(d["system_id"], 16, True)}, "keys": keys, "kind": o["kind"], + "renews": hexb(o["renews"], 32, True), "entitlement": hexb(o["entitlement"], None, True), + "request_sha256": hexb(o["request_sha256"], 32), "request_t": nint(o["request_t"]), "proof": hexb(o["proof"], 64, True), + "issued": nint(o["issued"]), "decision": o["decision"], "licence_expiry": nint(o["licence_expiry"]), + "renewal": nint(o["renewal"], U32), "upstream_status": nint(o["upstream_status"], U16), + "response_sha256": hexb(o["response_sha256"], 32, True), "released": o["released"], + "joules_micro": nint(o["joules_micro"]), "class": cls, "signer_pubkey": hexb(o["signer_pubkey"], 32), + "signer_id": o["signer_id"], "sig": hexb(o["sig"], 64), + } + if r["request_t"] is None or r["issued"] is None or r["joules_micro"] is None: + raise ValueError("int") + except (ValueError, KeyError, TypeError): + raise Refuse("receipt_malformed") + return r + + +def class_bytes(c): + """energy-measurement §2.7's signed encoding.""" + if c is None: + return b"\x00" + out = bytes([CLASS_TAG[c["label"]]]) + if c["label"] == "OnChipCounter": + out += struct.pack(">IQ", c["ppm"], c["window"]) + bytes([c["filtering"]]) + elif c["label"] == "CalibratedInstrument": + ref = c["ref"].encode() + out += struct.pack(">IQ", c["ppm"], c["window"]) + struct.pack(">I", len(ref)) + ref + return out + + +def payload(r): + """§8.3.""" + d = r["delivery"] + out = [RECEIPT_DOMAIN, string(r["content_id"]), opt(r["list"]), opt(r["cpix_sha256"]), + opt(None if r["required"] is None else string(r["required"])), + bytes([DELIVERIES[d["kind"]]]), opt(d["system_id"]), be(4, len(r["keys"]))] + for k in r["keys"]: + out += [k["kid"], bytes([0 if k["scheme"] is None else SCHEMES[k["scheme"]][0]]), period_bytes(k["period"]), + opt(k["key_commit"])] + e = r["entitlement"] + out += [bytes([KINDS[r["kind"]]]), opt(r["renews"]), opt(None if e is None else be(4, len(e)) + e), + r["request_sha256"], be(8, r["request_t"]), opt(r["proof"]), + be(8, r["issued"]), bytes([DECISIONS[r["decision"]]]), + opt(None if r["licence_expiry"] is None else be(8, r["licence_expiry"])), + opt(None if r["renewal"] is None else be(4, r["renewal"])), + opt(None if r["upstream_status"] is None else be(2, r["upstream_status"])), opt(r["response_sha256"]), + bytes([1 if r["released"] else 0]), be(8, r["joules_micro"]), class_bytes(r["class"]), + r["signer_pubkey"], string(r["signer_id"])] + return b"".join(out) + + +def identity(r): + return b3(ID_DOMAIN, payload(r), r["sig"]) + + +def own_rules(r): + """§8.4, rules in order: None, or the refusal.""" + bad = ED.verify(r["signer_pubkey"], payload(r), r["sig"]) # rule 1 + if bad: + return bad + keys, kind, d = r["keys"], r["kind"], r["delivery"] + kids = [k["kid"] for k in keys] + if not 1 <= len(keys) <= MAX_KEYS or any(a >= b for a, b in zip(kids, kids[1:])): # rule 2 + return "receipt_invalid" + drm = d["kind"] == "drm" + if (d["system_id"] is not None) != drm or d["system_id"] == bytes(16) or (d["kind"] == "hls-key-uri" and len(keys) != 1): + return "receipt_invalid" # rule 3 + if any(k["scheme"] is not None and k["scheme"] not in CARRIES[d["kind"]] for k in keys): + return "scheme_delivery_mismatch" # rule 4 + if r["list"] is None: # rule 5 + if r["cpix_sha256"] is not None or r["required"] is not None or any( + k["scheme"] is not None or k["period"] is not None or k["key_commit"] is not None for k in keys): + return "receipt_invalid" + elif r["required"] is None: + return "receipt_invalid" + if (r["entitlement"] is None) != (r["proof"] is None): # rule 6 + return "receipt_invalid" + if r["entitlement"] is None and r["decision"] != "DeniedEntitlementInvalid": + return "receipt_invalid" + if (r["renews"] is not None) != (kind == "renewal"): + return "receipt_invalid" + granted, rel = r["decision"] == "Granted", r["released"] + if (rel and not granted) or any((k["key_commit"] is not None) != rel for k in keys): # rule 7 + return "receipt_invalid" + st = r["upstream_status"] # rule 8 + if drm: + if (st is not None) != granted or (r["response_sha256"] is not None) != granted: + return "receipt_invalid" + if rel != (st is not None and 200 <= st <= 299) or (st is not None and not 100 <= st <= 599): + return "receipt_invalid" + elif st is not None or r["response_sha256"] is not None or rel != granted: + return "receipt_invalid" + licence = rel and kind != "streaming" # rule 9 + if (r["licence_expiry"] is not None) != licence or (r["renewal"] is not None) != licence: + return "receipt_invalid" + if licence and (r["licence_expiry"] < r["issued"] or (r["renewal"] == 0) != (kind == "offline")): + return "receipt_invalid" + c, j = r["class"], r["joules_micro"] # rule 10 + if c is None: + return None if j == 0 else "receipt_invalid" + if j == 0: + return "receipt_invalid" + if c["label"] in ("OnChipCounter", "CalibratedInstrument"): + if c["ppm"] == 0 or c["window"] == 0 or j * c["ppm"] < FLOOR: + return "receipt_invalid" + if c["label"] == "CalibratedInstrument" and all(ch in WHITE_SPACE for ch in c["ref"]): + return "receipt_invalid" + return None + + +# ---------------------------------------------------------------- §7 the decision + +def decide(authorities, r, lst, earlier): + """§7.2 and §7.3: (decision, licence_expiry, renewal) of receipt r's request.""" + def denied(d): + return d, None, None + eb, proof, issued = r["entitlement"], r["proof"], r["issued"] + e = read_entitlement(eb) if eb is not None else None + if (e is None or proof is None or not e["holds"] or e["authority"] not in authorities + or not ED.verifies(e["holder"], proof_payload(eb, r), proof)): + return denied("DeniedEntitlementInvalid") # check 1 + if abs(issued - r["request_t"]) > FRESH: + return denied("DeniedStale") # check 2 + if any(p["proof"] == proof for p in earlier): + return denied("DeniedReplayed") # check 3 + if lst is None or any(k["kid"] not in lst["keys"] for k in r["keys"]): + return denied("DeniedUnknownKey") # check 4 + if e["class"] != lst["required"]: + return denied("DeniedNoEntitlement") # check 5 + if issued < e["nb"]: + return denied("DeniedNotYetValid") # check 6 + if issued > e["na"]: + return denied("DeniedExpired") # check 7 + for k in r["keys"]: # check 8 + p = lst["keys"][k["kid"]]["period"] + if p is not None and p["start"] is not None and (p["start"] > e["na"] or p["end"] <= e["nb"]): + return denied("DeniedPeriodOutside") + if r["kind"] != "streaming" and e["offline"] == 0: + return denied("DeniedOfflineNotEntitled") # check 9 + expiry = min(issued + e["offline"], e["na"]) + if r["kind"] == "streaming": + return "Granted", None, None + if r["kind"] == "offline": + return "Granted", expiry, 0 + kids = [k["kid"] for k in r["keys"]] + prior = None + for p in earlier: # check 10 + if p["identity"] != r["renews"]: + continue + pe = read_entitlement(p["entitlement"]) if p["entitlement"] is not None else None + if (p["released"] and p["kind"] != "streaming" and p["content_id"] == r["content_id"] + and [k["kid"] for k in p["keys"]] == kids and pe is not None + and pe["session"] == e["session"] and pe["holder"] == e["holder"]): + prior = p + break + if prior is None: + return denied("DeniedRenewalUnknown") + if (prior["renewal"] or 0) + 1 > e["renewals"]: + return denied("DeniedRenewalsExhausted") # check 11 + if prior["licence_expiry"] is None or issued > prior["licence_expiry"]: + return denied("DeniedRenewalLapsed") # check 12 + return "Granted", expiry, (prior["renewal"] or 0) + 1 + + +# ---------------------------------------------------------------- §9.3 the ticket + +def read_ticket(b): + try: + r = Reader(b) + if r.take(4) != TICKET_MAGIC or r.int(1) != 1: + return None + t = {"receipt": r.take(32), "delivery": r.int(1), "session_type": r.int(1)} + n = r.int(4) + if t["delivery"] not in (1, 2) or t["session_type"] not in (1, 2) or not 1 <= n <= MAX_KEYS: + return None + t["kids"] = [r.take(16) for _ in range(n)] + if any(a >= b for a, b in zip(t["kids"], t["kids"][1:])): + return None + t["expires"], t["pk"] = r.int(8), r.take(32) + end = r.at + sig = r.take(64) + if not r.done() or not ED.verifies(t["pk"], TICKET_DOMAIN + b[:end], sig): + return None + except ValueError: + return None + return t + + +def ticket_follows(t, r): + return (r["released"] and r["delivery"]["kind"] != "drm" and t["receipt"] == r["identity"] + and DELIVERY_OF.get(t["delivery"]) == r["delivery"]["kind"] + and t["session_type"] == (1 if r["kind"] == "streaming" else 2) + and t["kids"] == [k["kid"] for k in r["keys"]] and t["pk"] == r["signer_pubkey"] + and 1 <= t["expires"] - r["issued"] <= MAX_TICKET) + + +# ---------------------------------------------------------------- §10 verification + +def verify(v, doc, files): + """§10.2 over vector v: what the corpus states for it.""" + try: + return {"outcome": "accept", "receipts": verify_log(v, doc, files)} + except Refuse as s: + return {"outcome": "refuse", "reason": s.reason, "at": s.at} + + +def verify_log(v, doc, files): + authorities = [bytes.fromhex(a) for a in v["authorities"]] + proxies = [bytes.fromhex(p) for p in v["proxies"]] + lists, by_content = [], {} + for i, name in enumerate(v["lists"]): # step 1 + try: + lst = read_list(bytes.fromhex(doc["lists"][name])) + except Refuse as s: + raise Refuse(s.reason, ["list", i]) + if lst["content"] in by_content: + raise Refuse("list_malformed", ["list", i]) + by_content[lst["content"]] = lst + lists.append(lst) + by_id = {lst["identity"]: lst for lst in lists} + for j, c in enumerate(v["cpix"]): # step 2 + try: + content, digest, keys = derive(files[c["file"]]) + except Refuse as s: + raise Refuse(s.reason, ["cpix", j]) + lst = lists[c["list"]] if c["list"] < len(lists) else None + if lst is None or lst["content"] != content or lst["cpix"] != digest or lst["keys"] != keys: + raise Refuse("cpix_mismatch", ["cpix", j]) + log, facts = [], [] + for i, o in enumerate(v["log"]): # step 3 + at = ["receipt", i] + try: + r = read_receipt(o) + except Refuse as s: + raise Refuse(s.reason, at) + bad = own_rules(r) + if bad: + raise Refuse(bad, at) + if r["signer_pubkey"] not in proxies: + raise Refuse("signer_unknown", at) + r["identity"] = identity(r) + if any(p["identity"] == r["identity"] for p in log): + raise Refuse("receipt_duplicate", at) + lst = None + if r["list"] is not None: + lst = by_id.get(r["list"]) + if lst is None: + raise Refuse("list_unknown", at) + if lst["content"] != r["content_id"]: + raise Refuse("list_mismatch", at) + elif r["content_id"] in by_content: + raise Refuse("list_mismatch", at) + if lst is not None: + if r["cpix_sha256"] != lst["cpix"] or r["required"] != lst["required"]: + raise Refuse("list_mismatch", at) + for k in r["keys"]: + lk = lst["keys"].get(k["kid"]) + if lk is None: + ok = k["scheme"] is None and k["period"] is None and k["key_commit"] is None + else: + ok = (k["scheme"] == lk["scheme"] and k["period"] == lk["period"] + and k["key_commit"] in (None, lk["commit"])) + if not ok: + raise Refuse("list_mismatch", at) + d, expiry, renewal = decide(authorities, r, lst, log) + if d != r["decision"] or (r["released"] and r["kind"] != "streaming" + and (r["licence_expiry"], r["renewal"]) != (expiry, renewal)): + raise Refuse("decision_mismatch", at) + facts.append({"identity": r["identity"].hex(), "decision": r["decision"], "released": r["released"], + "keys": [{"kid": k["kid"].hex(), "key_commit": k["key_commit"].hex()} for k in r["keys"] + if k["key_commit"] is not None], + "licence_expiry": r["licence_expiry"], "renewal": r["renewal"]}) + log.append(r) + for j, h in enumerate(v["tickets"]): # step 4 + t = read_ticket(bytes.fromhex(h)) + r = next((p for p in log if t is not None and p["identity"] == t["receipt"]), None) + if t is None or r is None or not ticket_follows(t, r): + raise Refuse("ticket_invalid", ["ticket", j]) + return facts + + +def check(doc, files, quiet=False): + """Every vector against its statement: the failures.""" + fails = [] + for v in doc.get("vectors", []): + try: + got = verify(v, doc, files) + except Exception as e: # a crash on a mutated copy is a failure to read it + got, bad = None, ["the vector does not verify: %s: %s" % (type(e).__name__, e)] + else: + bad = [k for k in got if v.get(k) != got[k]] + if not quiet: + what = "?" if got is None else got["outcome"] + (" %s at %s" % (got["reason"], got["at"]) if got["outcome"] == "refuse" else "") + print(("PASS" if not bad else "FAIL") + " log %s: %s" % (v.get("name"), what)) + fails += ["log %s: %s differs from the corpus" % (v.get("name"), k) for k in bad] + return fails + + +def flip(h, at): + b = bytearray(bytes.fromhex(h)) + b[at] ^= 0x01 + return b.hex() + + +def mutations(doc, files): + """Copies of the corpus, each with one defect a conforming verifier must catch.""" + vn = {v["name"]: k for k, v in enumerate(doc["vectors"])} + + def mutate(label, f): + d, fs = copy.deepcopy(doc), dict(files) + f(d, d["vectors"], fs) + return label, d, fs + + def receipt(name, i): + return lambda d, vs, fs: vs[vn[name]]["log"][i] + + def edit_receipt(name, i, key, value): + return lambda d, vs, fs: vs[vn[name]]["log"][i].update({key: value}) + + def ent_byte(d, vs, fs): + r = vs[vn["refusals"]]["log"][4] + r["entitlement"] = flip(r["entitlement"], 60) + + return [ + mutate("a byte of a release's signature", lambda d, vs, fs: edit_receipt("release_drm", 0, "sig", flip(vs[vn["release_drm"]]["log"][0]["sig"], 3))(d, vs, fs)), + mutate("a release's issued time moved", lambda d, vs, fs: edit_receipt("release_drm", 1, "issued", vs[vn["release_drm"]]["log"][1]["issued"] + 1)(d, vs, fs)), + mutate("a stated decision", lambda d, vs, fs: vs[vn["refusals"]]["receipts"][3].update(decision="DeniedExpired")), + mutate("a stated licence expiry", lambda d, vs, fs: vs[vn["offline"]]["receipts"][0].update(licence_expiry=1)), + mutate("a stated key commitment", + lambda d, vs, fs: vs[vn["release_drm"]]["receipts"][0]["keys"][0].update(key_commit="00" * 32)), + mutate("the granted request removed from before its replay", lambda d, vs, fs: (vs[vn["refusals"]]["log"].pop(4), + vs[vn["refusals"]]["receipts"].pop(4))), + mutate("a renewal moved before the licence it renews", lambda d, vs, fs: vs[vn["offline"]]["log"].insert(0, vs[vn["offline"]]["log"].pop(1))), + mutate("the untrusted authority trusted", lambda d, vs, fs: vs[vn["refusals"]]["authorities"].append( + d["principals"]["rogue_authority"])), + mutate("a byte of an entitlement inside a release", ent_byte), + mutate("a key of the film document changed", + lambda d, vs, fs: fs.update({"film.cpix.xml": fs["film.cpix.xml"].replace(b"", b"AAAA", 1)})), + mutate("the forged list replaced by the genuine one", lambda d, vs, fs: vs[vn["n_list_forged"]].update(lists=["film"])), + mutate("the stated reason of a negative control", lambda d, vs, fs: vs[vn["n_misreported_grant"]].update(reason="list_mismatch")), + mutate("the stated place of a negative control", lambda d, vs, fs: vs[vn["n_duplicate"]].update(at=["receipt", 0])), + mutate("a byte of a ticket's expiry", lambda d, vs, fs: vs[vn["release_hls_key_uri"]].update( + tickets=[vs[vn["release_hls_key_uri"]]["tickets"][0][:-200] + "ff" + vs[vn["release_hls_key_uri"]]["tickets"][0][-198:]])), + mutate("a period of the channel list stated otherwise", lambda d, vs, fs: vs[vn["rotation"]]["log"][0]["keys"][0].update( + period={"index": 2, "start": None, "end": None})), + mutate("a refusal's scheme stated for a key not in the list", lambda d, vs, fs: next( + k for k in vs[vn["refusals"]]["log"][6]["keys"] if k["scheme"] is None).update(scheme="cenc")), + mutate("a CPIX document removed", lambda d, vs, fs: fs.pop("channel.cpix.xml")), + ] + + +def main(corpus_dir): + fails = [] + st = ED._self_test() + print(("PASS" if not st else "FAIL") + " Ed25519 strict rule self-test " + ",".join(st)) + fails += ["ed25519 self-test"] if st else [] + ok = (len(SCHEMES), len(DELIVERIES), len(DECISIONS), len(REASONS)) == (7, 3, 13, 16) and len(CARRIES) == 3 + print(("PASS" if ok else "FAIL") + " the extension's tables read: %d schemes, %d deliveries, %d decisions, %d reasons" + % (len(SCHEMES), len(DELIVERIES), len(DECISIONS), len(REASONS))) + fails += [] if ok else ["the extension's tables do not read"] + with open(os.path.join(corpus_dir, "vectors.json"), encoding="utf-8") as f: + doc = json.load(f) + named = sorted({c["file"] for v in doc["vectors"] for c in v["cpix"]}) + files = {} + for name in named: + with open(os.path.join(corpus_dir, name), "rb") as f: + files[name] = f.read() + fails += check(doc, files) + extra = sorted(set(os.listdir(corpus_dir)) - set(named) - {"vectors.json", "README.md"}) + print(("PASS" if not extra else "FAIL") + " no file but vectors.json, README.md and the CPIX documents the vectors name") + fails += ["an extra file: %s" % n for n in extra] + seen = {v.get("reason") for v in doc["vectors"]} + missing = [r for r in REASONS if r not in seen] + print(("PASS" if not missing else "FAIL") + " every reason of §11 appears in the corpus") + fails += ["no case gives %s" % r for r in missing] + decided = {r["decision"] for v in doc["vectors"] for r in v.get("receipts", [])} + undecided = [d for d in DECISIONS if d not in decided] + print(("PASS" if not undecided else "FAIL") + " every decision of §7.2 appears in an accepted log") + fails += ["no accepted receipt is %s" % d for d in undecided] + for label, d, fs in mutations(doc, files): + caught = check(d, fs, quiet=True) + print(("PASS" if caught else "FAIL") + " mutated copy fails: %s%s" % (label, " (%s)" % caught[0][:100] if caught else "")) + if not caught: + fails.append("a mutated copy verifies: %s" % label) + for f in fails: + print("FAIL " + f) + print(("PASS" if not fails else "FAIL") + " key-release corpus: %d logs, %d CPIX documents" % (len(doc["vectors"]), len(files))) + return 1 if fails else 0 + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("usage: key_release_verify.py ", file=sys.stderr) + sys.exit(2) + sys.exit(main(sys.argv[1])) -- 2.54.0 (Apple Git-157) From c70aedef607d88fed8cd959d697f2e382536841e Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Sun, 25 Oct 2026 21:34:50 -0400 Subject: [PATCH 086/242] =?UTF-8?q?wai:=20stitched=20presentation=20?= =?UTF-8?q?=E2=80=94=20player-signed=20claims=20of=20stitched=20and=20inte?= =?UTF-8?q?rstitial=20ads,=20the=20signed=20pod-input=20record,=20ad=20fra?= =?UTF-8?q?mes=20in=20live-compose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wai.video.stitched_presentation (extensions/stitched-presentation.md): a WPS1 claim names an ad by the marker the stream carries for it (an EXT-X-DATERANGE tag, an interstitial's asset, a DASH event) in the manifest it states the SHA-256 of; a WPI1 pod-input record, signed by the stitcher or a proxy in front of the ad server, binds the response digest, the chosen ads, their creatives and prices, the avail, the frame and the measurement terms; a verifier recomputes each ad's presentations, quartiles and the spend that counts by currency. A form-1 wai.video.ssai receipt gains a JSON carriage (its signed bytes unchanged) so it can be a record's response. live-compose gains the squeeze and double-box ad frames (§5.9). Corpus live-conformance/stitched/, its independent verifier stitched_presentation_verify.py, and the browser producer web/ads/stitched.js with tests against the corpus. Co-Authored-By: Claude Opus 5.5 --- .../crates/joule-moq-web/web/ads/stitched.js | 371 +++++ .../joule-moq-web/web/ads/stitched.test.mjs | 137 ++ wai/extensions/live-compose.md | 71 +- wai/extensions/stitched-presentation.md | 675 ++++++++ wai/live-conformance/compose/vectors.json | 1484 +++++++++++++++++ wai/live-conformance/live_compose_verify.py | 71 +- wai/live-conformance/stitched/README.md | 132 ++ wai/live-conformance/stitched/double_box.m3u8 | 19 + .../stitched/double_box.vmap.xml | 2 + .../stitched/form1_prefetch.m3u8 | 29 + .../stitched/form1_prefetch.receipt.json | 39 + .../stitched/hls_interstitial.assets.json | 1 + .../stitched/hls_interstitial.bid.json | 1 + .../stitched/hls_interstitial.m3u8 | 10 + .../stitched/hls_interstitial.vast.xml | 42 + .../stitched/hls_stitched.viewer1.m3u8 | 33 + .../stitched/hls_stitched.viewer1.vast.xml | 43 + .../stitched/hls_stitched.viewer2.m3u8 | 31 + .../stitched/hls_stitched.viewer2.vast.xml | 43 + wai/live-conformance/stitched/squeezeback.mpd | 2 + .../stitched/squeezeback.vast.xml | 23 + wai/live-conformance/stitched/vectors.json | 939 +++++++++++ .../stitched_presentation_verify.py | 1046 ++++++++++++ wai/wai-rs/Cargo.toml | 21 + .../src/bin/wai_stitched_presentation.rs | 441 +++++ wai/wai-rs/src/lib.rs | 10 + .../src/live_ads/presentation_corpus.rs | 6 +- wai/wai-rs/src/live_compose/corpus.rs | 29 + wai/wai-rs/src/live_compose/layout.rs | 145 +- wai/wai-rs/src/stitched_presentation.rs | 1200 +++++++++++++ .../src/stitched_presentation/corpus.rs | 934 +++++++++++ .../src/stitched_presentation/markers.rs | 530 ++++++ wai/wai-rs/src/wai_video_ssai.rs | 201 +++ wai/wai-rs/tests/stitched_presentation.rs | 55 + 34 files changed, 8798 insertions(+), 18 deletions(-) create mode 100644 joule-code/joule-code-rs/crates/joule-moq-web/web/ads/stitched.js create mode 100644 joule-code/joule-code-rs/crates/joule-moq-web/web/ads/stitched.test.mjs create mode 100644 wai/extensions/stitched-presentation.md create mode 100644 wai/live-conformance/stitched/README.md create mode 100644 wai/live-conformance/stitched/double_box.m3u8 create mode 100644 wai/live-conformance/stitched/double_box.vmap.xml create mode 100644 wai/live-conformance/stitched/form1_prefetch.m3u8 create mode 100644 wai/live-conformance/stitched/form1_prefetch.receipt.json create mode 100644 wai/live-conformance/stitched/hls_interstitial.assets.json create mode 100644 wai/live-conformance/stitched/hls_interstitial.bid.json create mode 100644 wai/live-conformance/stitched/hls_interstitial.m3u8 create mode 100644 wai/live-conformance/stitched/hls_interstitial.vast.xml create mode 100644 wai/live-conformance/stitched/hls_stitched.viewer1.m3u8 create mode 100644 wai/live-conformance/stitched/hls_stitched.viewer1.vast.xml create mode 100644 wai/live-conformance/stitched/hls_stitched.viewer2.m3u8 create mode 100644 wai/live-conformance/stitched/hls_stitched.viewer2.vast.xml create mode 100644 wai/live-conformance/stitched/squeezeback.mpd create mode 100644 wai/live-conformance/stitched/squeezeback.vast.xml create mode 100644 wai/live-conformance/stitched/vectors.json create mode 100644 wai/live-conformance/stitched_presentation_verify.py create mode 100644 wai/wai-rs/src/bin/wai_stitched_presentation.rs create mode 100644 wai/wai-rs/src/stitched_presentation.rs create mode 100644 wai/wai-rs/src/stitched_presentation/corpus.rs create mode 100644 wai/wai-rs/src/stitched_presentation/markers.rs create mode 100644 wai/wai-rs/tests/stitched_presentation.rs diff --git a/joule-code/joule-code-rs/crates/joule-moq-web/web/ads/stitched.js b/joule-code/joule-code-rs/crates/joule-moq-web/web/ads/stitched.js new file mode 100644 index 000000000..89b7dc6f6 --- /dev/null +++ b/joule-code/joule-code-rs/crates/joule-moq-web/web/ads/stitched.js @@ -0,0 +1,371 @@ +// The reference producer of WAI stitched presentation claims +// (wai/extensions/stitched-presentation.md): a player's claim that it presented an ad another +// party wrote into the stream, stitched into its segments or served as an interstitial. +// +// hlsAds, interstitialAds, mpdAds the markers and ad identities a manifest carries (§3) +// markerBody, markerDigest a marker's body and digest (§3.1) +// adFrame, shareBp a composited frame's ad region and its share (§6) +// regionVisibleBp the fraction of the ad's region in view, as displayed (§6) +// sessionOf, redemptionContext the session a key names (§5.2) and a token's context (§5.5) +// claimBody, signClaim the claim's bytes (§5.1) and its signature (§5.3) +// +// Spans come from ad-presentation's SpanRecorder (./presentation.js), over the ad's own media +// time. Nothing here needs a player of a particular kind: an integration hands it the manifest +// it fetched, the ad's media position and the observations ad-presentation §10 lists. The +// platform's WebCrypto does the hashing and signing; no dependency. + +import { CREATIVE_DOMAIN, NOT_EXPOSED, creativeDigest, sha256, touched } from "./presentation.js"; + +export { creativeDigest, touched, NOT_EXPOSED, CREATIVE_DOMAIN }; + +const enc = new TextEncoder(); +const subtle = globalThis.crypto.subtle; + +export const CLAIM_MAGIC = enc.encode("WPS1"); +export const CLAIM_DOMAIN = enc.encode("wai:stitched-presentation-claim\x01"); +export const SESSION_DOMAIN = enc.encode("wai:stitched-presentation-session\x01"); +export const ATTEST_DOMAIN = enc.encode("wai:stitched-presentation-attest\x01"); +export const MARKER_DOMAIN = enc.encode("wai:stitched-marker\x01"); +export const CARRIAGE = { DATERANGE: 1, INTERSTITIAL: 2, EVENT: 3 }; +export const INTERSTITIAL_CLASS = "com.apple.hls.interstitial"; // allow-name: the interstitial date-range class, a protocol identifier + +const concat = (...parts) => { + const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0)); + let at = 0; + for (const p of parts) { + out.set(p, at); + at += p.length; + } + return out; +}; +const u16le = (v) => Uint8Array.of(v & 0xff, (v >>> 8) & 0xff); +const u32le = (v) => { + const b = new Uint8Array(4); + new DataView(b.buffer).setUint32(0, v >>> 0, true); + return b; +}; +const bytes = (s) => (typeof s === "string" ? enc.encode(s) : s); +const long = (s) => { + const b = bytes(s); + return concat(u16le(b.length), b); +}; + +// ------------------------------------------------------------------ §3.1 markers + +/** A marker's body (§3.1): its fields as long fields, and an interstitial's index. */ +export function markerBody(m) { + if (m.carriage === CARRIAGE.EVENT) return concat(long(m.period ?? ""), long(m.scheme), long(m.value ?? ""), long(m.id)); + const b = concat(long(m.id), long(m.start)); + return m.carriage === CARRIAGE.INTERSTITIAL ? concat(b, u16le(m.index)) : b; +} + +/** The marker digest (§3.1). */ +export async function markerDigest(carriage, body) { + return sha256(MARKER_DOMAIN, Uint8Array.of(carriage), body); +} + +// ------------------------------------------------------------------ §3.2, §3.3 HLS + +/** An attribute list (§3.2): a Map of name to { value, quoted }, or null. */ +export function attributeList(s) { + const out = new Map(); + let rest = s; + for (;;) { + const eq = rest.indexOf("="); + if (eq < 1) return null; + const name = rest.slice(0, eq); + if (!/^[A-Z0-9-]+$/.test(name) || out.has(name)) return null; + let after = rest.slice(eq + 1); + if (after.startsWith('"')) { + const close = after.indexOf('"', 1); + if (close < 0) return null; + const v = after.slice(1, close); + if (/[\r\n]/.test(v)) return null; + out.set(name, { value: v, quoted: true }); + after = after.slice(close + 1); + } else { + const end = after.indexOf(",") < 0 ? after.length : after.indexOf(","); + const v = after.slice(0, end); + if (!v || /["\s]/.test(v)) return null; + out.set(name, { value: v, quoted: false }); + after = after.slice(end); + } + if (!after) return out; + if (!after.startsWith(",") || after === ",") return null; + rest = after.slice(1); + } +} + +function dateRanges(playlist) { + const lines = playlist.split("\n").map((l) => (l.endsWith("\r") ? l.slice(0, -1) : l)); + if (lines[0] !== "#EXTM3U") return null; + return lines + .filter((l) => l.startsWith("#EXT-X-DATERANGE:")) + .map((l) => attributeList(l.slice("#EXT-X-DATERANGE:".length))) + .filter((a) => a && a.get("ID")?.quoted && a.get("START-DATE")?.quoted); +} + +/** Carriage 1 (§3.2): the ads a Media Playlist's date ranges carry under `attr`. */ +export function hlsAds(playlist, attr) { + const tags = dateRanges(playlist); + if (!tags) return null; + return tags + .filter((a) => a.has(attr)) + .map((a) => ({ marker: { carriage: CARRIAGE.DATERANGE, id: a.get("ID").value, start: a.get("START-DATE").value }, adId: a.get(attr).value })); +} + +/** Carriage 2 (§3.3): the interstitial date ranges of a playlist, and the ads an asset list + * served for one of them names, by index. */ +export function interstitials(playlist) { + const tags = dateRanges(playlist); + if (!tags) return null; + return tags.filter((a) => a.get("CLASS")?.quoted && a.get("CLASS").value === INTERSTITIAL_CLASS).map((a) => ({ id: a.get("ID").value, start: a.get("START-DATE").value })); +} + +export function interstitialAds(dateRange, assetList) { + const v = JSON.parse(assetList); + if (!v || !Array.isArray(v.ASSETS)) return null; + return v.ASSETS.map((a, index) => ({ marker: { carriage: CARRIAGE.INTERSTITIAL, id: dateRange.id, start: dateRange.start, index }, adId: a.URI })); +} + +// ------------------------------------------------------------------ §3.4, §3.5 DASH + +const WS = " \t\r\n"; +const NAMED = { lt: "<", gt: ">", amp: "&", quot: '"', apos: "'" }; + +function unescape(t) { + return t.replace(/&([^;]*);?/g, (all, r) => { + if (!all.endsWith(";")) throw new Error("reference"); + if (r in NAMED) return NAMED[r]; + if (/^#x[0-9a-fA-F]+$/.test(r)) return String.fromCodePoint(parseInt(r.slice(2), 16)); + if (/^#[0-9]+$/.test(r)) return String.fromCodePoint(parseInt(r.slice(1), 10)); + throw new Error("reference"); + }); +} + +const localName = (n) => n.slice(n.lastIndexOf(":") + 1); + +/** An XML document over the subset of §3.5: its root element, or null. */ +export function readXml(text) { + let p = 0; + const ws = () => { + while (p < text.length && WS.includes(text[p])) p++; + }; + const eat = (t) => (text.startsWith(t, p) ? ((p += t.length), true) : false); + const until = (t) => { + const j = text.indexOf(t, p); + if (j < 0) throw new Error("unterminated"); + const out = text.slice(p, j); + p = j + t.length; + return out; + }; + const misc = () => { + for (;;) { + ws(); + if (eat(""); + else if (text.startsWith(""); + else return; + } + }; + const name = () => { + let j = p; + while (j < text.length && !(WS + "/>=").includes(text[j])) j++; + const n = text.slice(p, j); + if (!/^[\p{L}_:][\p{L}\p{N}_.:-]*$/u.test(n)) throw new Error("name"); + p = j; + return n; + }; + const element = (depth) => { + if (depth > 64 || !eat("<")) throw new Error("element"); + const q = name(); + const attrs = new Map(); + for (;;) { + const before = p; + ws(); + if (eat("/>")) return { name: localName(q), attrs, children: [] }; + if (eat(">")) break; + if (p === before) throw new Error("attribute"); + const an = name(); + ws(); + if (!eat("=")) throw new Error("attribute"); + ws(); + const quote = eat('"') ? '"' : eat("'") ? "'" : null; + if (!quote) throw new Error("attribute"); + const raw = until(quote); + if (raw.includes("<") || attrs.has(localName(an))) throw new Error("attribute"); + attrs.set(localName(an), unescape(raw)); + } + const children = []; + for (;;) { + if (eat("")) throw new Error("end tag"); + return { name: localName(q), attrs, children }; + } + if (eat("")); + else if (eat(""); + else if (text.startsWith(""); + else if (text.startsWith("<", p)) children.push(element(depth + 1)); + else { + const j = text.indexOf("<", p); + if (j < 0) throw new Error("unterminated"); + children.push(unescape(text.slice(p, j))); + p = j; + } + } + }; + try { + if (text.startsWith("")) return null; + misc(); + const root = element(1); + misc(); + return p === text.length ? root : null; + } catch { + return null; + } +} + +const childrenNamed = (e, n) => e.children.filter((c) => typeof c === "object" && c.name === n); + +/** Carriage 3 (§3.4): the ads an MPD's events carry under `attr`. */ +export function mpdAds(mpd, attr) { + const root = readXml(mpd); + if (!root || root.name !== "MPD") return null; + const out = []; + for (const period of childrenNamed(root, "Period")) { + for (const es of childrenNamed(period, "EventStream")) { + if (!es.attrs.has("schemeIdUri")) continue; + for (const ev of childrenNamed(es, "Event")) { + if (!ev.attrs.has("id") || !ev.attrs.has(attr)) continue; + out.push({ + marker: { carriage: CARRIAGE.EVENT, period: period.attrs.get("id") ?? "", scheme: es.attrs.get("schemeIdUri"), value: es.attrs.get("value") ?? "", id: ev.attrs.get("id") }, + adId: ev.attrs.get(attr), + }); + } + } + } + return out; +} + +// ------------------------------------------------------------------ §6 composited frames + +const fit = (bw, bh, a, b) => (bw * b <= bh * a ? [bw, Math.max(1, Math.floor((bw * b) / a))] : [Math.max(1, Math.floor((bh * a) / b)), bh]); +const REFERENCE = { 2: [1, 1], 3: [3, 4] }; + +/** live-compose §5.9's ad frame of a record's frame (§4.3): { program, ad, region }, each + * rectangle [x, y, w, h]; null for the full picture. */ +export function adFrame(f) { + if (f.kind === 0) return null; + const { width: w, height: h } = f; + if (f.kind === 3) { + const [pw, ph] = [f.windowW, f.windowH]; + const left = f.corner === 1 || f.corner === 3; + const top = f.corner === 1 || f.corner === 2; + const y = top ? 0 : h - ph; + const side = [left ? pw : 0, y, w - pw, ph]; + const band = [0, top ? ph : 0, w, h - ph]; + return { program: [left ? 0 : w - pw, y, pw, ph], ad: [0, 0, w, h], region: top ? [side, band] : [band, side] }; + } + const bw = Math.floor((w - f.gap) / 2); + const boxes = [ + [0, 0, bw, h], + [bw + f.gap, 0, w - bw - f.gap, h], + ]; + const tile = (b) => { + if (f.aspect === 0) return b; + const [a, r] = REFERENCE[f.aspect] ?? [16, 9]; + const [tw, th] = fit(b[2], b[3], a, r); + return [b[0] + Math.floor((b[2] - tw) / 2), b[1] + Math.floor((b[3] - th) / 2), tw, th]; + }; + const [program, ad] = f.corner === 1 ? [tile(boxes[0]), tile(boxes[1])] : [tile(boxes[1]), tile(boxes[0])]; + return { program, ad, region: [ad] }; +} + +/** The share of the picture the ad's region holds, in basis points (§6). */ +export function shareBp(f) { + const fr = adFrame(f); + if (!fr) return 10_000; + const area = fr.region.reduce((n, r) => n + r[2] * r[3], 0); + return Math.floor((area * 10_000) / (f.width * f.height)); +} + +/** + * The fraction of the ad's region in view, in basis points (§6): the share of the region's + * area, as the picture is displayed, that lies within the visible part of the player. + * `displayed` is where the picture's pixels are drawn on the page and `visible` the part of + * the player in view (an IntersectionObserver entry's intersectionRect), both + * { x, y, width, height } in CSS pixels; `region` is in the picture's pixels, of `width` × + * `height`. With no region, the fraction of the whole picture. + */ +export function regionVisibleBp(region, width, height, displayed, visible) { + const rects = region ?? [[0, 0, width, height]]; + const sx = displayed.width / width; + const sy = displayed.height / height; + let total = 0; + let seen = 0; + for (const [x, y, w, h] of rects) { + const r = { x: displayed.x + x * sx, y: displayed.y + y * sy, w: w * sx, h: h * sy }; + total += r.w * r.h; + const ix = Math.max(0, Math.min(r.x + r.w, visible.x + visible.width) - Math.max(r.x, visible.x)); + const iy = Math.max(0, Math.min(r.y + r.h, visible.y + visible.height) - Math.max(r.y, visible.y)); + seen += ix * iy; + } + return total > 0 ? Math.min(10_000, Math.floor((seen * 10_000) / total + 1e-9)) : 0; +} + +// ------------------------------------------------------------------ §5 the claim + +/** The session a session key names (§5.2). */ +export async function sessionOf(key) { + return (await sha256(SESSION_DOMAIN, key)).subarray(0, 16); +} + +/** The redemption context a token answers (§5.5). */ +export async function redemptionContext(c) { + const body = markerBody(c.marker); + const ad = bytes(c.adId); + return sha256(ATTEST_DOMAIN, Uint8Array.of(c.marker.carriage), await markerDigest(c.marker.carriage, body), c.session, c.creativeHash, c.player, Uint8Array.of(ad.length), ad); +} + +/** The claim's bytes before `sig` (§5.1). */ +export function claimBody(c) { + const ad = bytes(c.adId); + if (ad.length < 1 || ad.length > 255) throw new Error("an ad identity of 1 to 255 bytes"); + const spans = c.spans.flatMap((s) => [u32le(s.startMs), u32le(s.lengthMs), u16le(s.visibleBp), Uint8Array.of(s.flags)]); + const att = c.attestation ?? new Uint8Array(0); + return concat( + CLAIM_MAGIC, + Uint8Array.of(1, c.marker.carriage), + c.manifest, + c.session, + c.creativeHash, + c.player, + Uint8Array.of(c.geometry), + u16le(c.shareBp), + u32le(c.frames ?? NOT_EXPOSED), + long(markerBody(c.marker)), + Uint8Array.of(ad.length), + ad, + u16le(c.objects.length), + ...c.objects, + u16le(c.spans.length), + ...spans, + u16le(att.length), + att, + ); +} + +/** The claim, signed (§5.3). */ +export async function signClaim(c, privateKey) { + const body = claimBody(c); + const sig = new Uint8Array(await subtle.sign({ name: "Ed25519" }, privateKey, concat(CLAIM_DOMAIN, body))); + return concat(body, sig); +} + +/** SHA-256 of a manifest's bytes as fetched: the claim's `manifest` (§5.1). */ +export async function manifestDigest(b) { + return sha256(bytes(b)); +} diff --git a/joule-code/joule-code-rs/crates/joule-moq-web/web/ads/stitched.test.mjs b/joule-code/joule-code-rs/crates/joule-moq-web/web/ads/stitched.test.mjs new file mode 100644 index 000000000..49a5737c4 --- /dev/null +++ b/joule-code/joule-code-rs/crates/joule-moq-web/web/ads/stitched.test.mjs @@ -0,0 +1,137 @@ +// The reference producer of stitched presentation claims (wai/extensions/stitched-presentation.md +// §8), against the corpus wai/live-conformance/stitched/ (`node --test web/ads/stitched.test.mjs`): +// markers read from the corpus's own manifests, observation sequences recorded into spans, the +// ad's region measured in a composited frame, and the claims written and signed from them, byte +// for byte as the corpus holds them. +import test from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import * as ap from "./presentation.js"; +import * as st from "./stitched.js"; + +const DIR = new URL("../../../../../../wai/live-conformance/stitched/", import.meta.url); +const file = (n) => readFileSync(new URL(n, DIR), "utf8"); +const doc = JSON.parse(file("vectors.json")); +const vector = (name) => doc.vectors.find((v) => v.name === name); +const hex = (s) => Uint8Array.from(s.match(/../g) ?? [], (h) => parseInt(h, 16)); +const toHex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join(""); +const creative = (k) => doc.creatives[k].objects.map((o) => ({ contentHash: hex(o.content_hash), durationMs: o.duration_ms })); + +/** Session key `i` of the corpus: SHA-256 of its label and `i` (its README). */ +const key = async (i) => ap.sessionKeyFromSeed(await ap.sha256(new TextEncoder().encode("wai:stitched-presentation/corpus/player\x00"), Uint8Array.of(i))); + +/** Observations every 100 ms of the ad's media time from `from` to `to`, at normal speed. */ +function play(rec, from, to, visibleBp, audible = true) { + for (let p = from; p <= to; p += 100) rec.observe({ positionMs: p, playing: true, visibleBp, audible }); +} + +function ended(rec, at, visibleBp) { + rec.observe({ positionMs: at, playing: false, visibleBp, audible: true }); + return rec.spans(); +} + +async function claim({ player, found, manifest, objects, spans, geometry = ap.GEOMETRY.AD, shareBp = 10_000, attestation }) { + const k = await key(player); + const c = { + marker: found.marker, + adId: found.adId, + manifest: await st.manifestDigest(manifest), + session: await st.sessionOf(k.publicKey), + creativeHash: await st.creativeDigest(objects), + player: k.publicKey, + geometry, + shareBp, + frames: st.NOT_EXPOSED, + objects: st.touched(objects, spans), + spans, + attestation, + }; + return { hex: toHex(await st.signClaim(c, k.privateKey)), claim: c }; +} + +test("a stitched ad under a date range: the marker read from the playlist, the corpus's claim", async () => { + const playlist = file("hls_stitched.viewer1.m3u8"); + const ads = st.hlsAds(playlist, "X-AD-ID"); + assert.deepEqual(ads.map((a) => a.adId), ["srv-v1-0", "srv-v1-1"], "the date ranges that carry an ad identity, and not the break's own"); + const rec = new ap.SpanRecorder(); + play(rec, 0, 15_000, 10_000); + const spans = ended(rec, 15_000, 10_000); + const got = await claim({ player: 1, found: ads[0], manifest: playlist, objects: creative(0), spans }); + assert.equal(got.hex, vector("hls_stitched").claims[0]); +}); + +test("scrolled partly out of view to the third quartile: a span at each change", async () => { + const playlist = file("hls_stitched.viewer1.m3u8"); + const rec = new ap.SpanRecorder(); + play(rec, 0, 3_900, 10_000); + play(rec, 4_000, 7_900, 6_000); + play(rec, 8_000, 11_250, 3_000); + rec.observe({ positionMs: 11_250, playing: true, visibleBp: 3_000, audible: true }); + const spans = ended(rec, 11_250, 3_000); + assert.deepEqual(spans.map((s) => [s.startMs, s.lengthMs, s.visibleBp]), [[0, 4_000, 10_000], [4_000, 4_000, 6_000], [8_000, 3_250, 3_000]]); + const got = await claim({ player: 1, found: st.hlsAds(playlist, "X-AD-ID")[1], manifest: playlist, objects: creative(1), spans }); + assert.equal(got.hex, vector("hls_stitched").claims[1]); +}); + +test("an interstitial's asset: its date range from the playlist, its identity from the asset list", async () => { + const v = vector("hls_interstitial"); + const [dr] = st.interstitials(file("hls_interstitial.m3u8")); + assert.equal(dr.id, "break-300"); + const list = file("hls_interstitial.assets.json"); + const ads = st.interstitialAds(dr, list); + const want = hex(v.claims[0]); + // The token is the issuer's (the corpus signs it with RFC 9578's test key); the claim carries + // it as the long field before its signature. + const tokenLen = 98 + 256; + const token = want.subarray(want.length - 64 - tokenLen, want.length - 64); + const rec = new ap.SpanRecorder(); + play(rec, 0, 15_000, 10_000); + const spans = ended(rec, 15_000, 10_000); + const got = await claim({ player: 11, found: ads[0], manifest: list, objects: creative(3), spans, geometry: ap.GEOMETRY.SCREEN, attestation: token }); + assert.equal(got.hex, v.claims[0]); + const challenge = ap.tokenChallenge(2, doc.issuers[0].name, await st.redemptionContext(got.claim)); + assert.equal(toHex(token.subarray(34, 66)), toHex(await ap.sha256(challenge)), "the token answers this presentation's own challenge"); +}); + +test("a squeeze: the ad's region from the frame, 70% of it in view, the corpus's claim", async () => { + const mpd = file("squeezeback.mpd"); + const [found] = st.mpdAds(mpd, "messageData"); + assert.equal(found.adId, "srv-sq-1"); + const frame = { kind: 3, width: 1920, height: 1080, corner: 4, windowW: 1440, windowH: 810, gap: 0, aspect: 0 }; + const { region } = st.adFrame(frame); + assert.deepEqual(region, [[0, 0, 1920, 270], [0, 270, 480, 810]]); + assert.equal(st.shareBp(frame), 4375); + // The picture drawn at its own size, and the viewport cut 513 pixels down: the whole top band + // and 243 of the side band's 810 rows are in view, 635 040 of 907 200 pixels. + const displayed = { x: 0, y: 0, width: 1920, height: 1080 }; + const vis = st.regionVisibleBp(region, 1920, 1080, displayed, { x: 0, y: 0, width: 1920, height: 513 }); + assert.equal(vis, 7_000); + assert.equal(st.regionVisibleBp(null, 1920, 1080, displayed, { x: 0, y: 0, width: 1920, height: 513 }), 4_750, "the whole picture, not the region"); + const rec = new ap.SpanRecorder(); + play(rec, 0, 20_000, vis); + const spans = ended(rec, 20_000, vis); + const got = await claim({ player: 22, found, manifest: mpd, objects: creative(5), spans, shareBp: st.shareBp(frame) }); + assert.equal(got.hex, vector("squeezeback").claims[1]); +}); + +test("a double box: the ad's box, and its share", async () => { + const frame = { kind: 4, width: 1920, height: 1080, corner: 1, windowW: 0, windowH: 0, gap: 16, aspect: 1 }; + assert.deepEqual(st.adFrame(frame).ad, [968, 272, 952, 535]); + assert.equal(st.shareBp(frame), 2456); + const playlist = file("double_box.m3u8"); + const [found] = st.hlsAds(playlist, "X-AD-ID"); + const rec = new ap.SpanRecorder(); + play(rec, 0, 10_000, 10_000); + const got = await claim({ player: 31, found, manifest: playlist, objects: creative(6), spans: ended(rec, 10_000, 10_000), shareBp: st.shareBp(frame) }); + assert.equal(got.hex, vector("double_box").claims[0]); +}); + +test("the readers refuse what the subset and the attribute list leave out", () => { + assert.equal(st.readXml(""), null); + assert.equal(st.readXml(''), null); + assert.equal(st.readXml(" "), null); + assert.deepEqual(st.readXml("").children, [" b "]); + assert.equal(st.attributeList('ID="a",,X=1'), null); + assert.equal(st.attributeList("ID=a b"), null); + assert.equal(st.hlsAds("not a playlist", "X-AD-ID"), null); +}); diff --git a/wai/extensions/live-compose.md b/wai/extensions/live-compose.md index 4ddd17086..eac398384 100644 --- a/wai/extensions/live-compose.md +++ b/wai/extensions/live-compose.md @@ -3,7 +3,9 @@ > Status: Draft. Registers the record `wai.live.compose`: the deterministic composition > of a live room's participants. A signed **composition descriptor** fixes a canvas, a > background, a layout (a grid, with a featured tile, or a grid under a -> picture-in-picture window), the grant attributes it reads and its limits. The +> picture-in-picture window, or an ad frame: a program squeezed into a corner with an ad +> in the L around it, or a program and an ad in two boxes side by side), the grant +> attributes it reads and its limits. The > **participant set** at a room-clock instant is derived from the room's lifecycle > replay ([live-rooms](live-rooms.md) §9): who publishes, each one's video and audio > source and its dimensions, the attributes of its grant in effect, and its join order. @@ -70,16 +72,16 @@ dispatchable. | `fps` | 1 | the canvas's frame rate, in frames per second (§3.4) | | `background` | 3 | the canvas's colour: red, green and blue, 8 bits each, in sRGB (IEC 61966-2-1) | | `layer` | 1 | the highest layer of a source composed (§4.2) | -| `layout` | 1 | 1 grid, 2 picture in picture (§5) | +| `layout` | 1 | 1 grid, 2 picture in picture, 3 squeeze, 4 double box (§5; 3 and 4 are the ad frames of §5.9) | | `gap` | 2 | u16: the pixels between two grid tiles | | `aspect` | 1 | the grid tiles' aspect: 0 auto, 1 video (16:9), 2 square (1:1), 3 portrait (3:4) (§5.3) | | `fill` | 1 | 0 default, 1 fill, 2 cover, 3 contain (§5.6) | | `featured_attr` | short | the attribute key that marks the featured participant; empty for none (§4.3) | | `order_attr` | short | the attribute key that orders participants; empty for none | | `omit_stopped` | 1 | 1: a participant whose video is stopped has no tile; 0: it has one (§5.2) | -| `pip_attr` | short | the attribute key that marks the PiP participant; empty for none | -| `corner` | 1 | the PiP window's corner: 1 top left, 2 top right, 3 bottom left, 4 bottom right | -| `pip_width`, `pip_height` | 2 each | u16: the PiP window's size; 0 derives it (§5.5) | +| `pip_attr` | short | the attribute key that marks the PiP participant, or in an ad frame the ad participant (§5.9); empty for none | +| `corner` | 1 | the PiP window's corner, or a squeeze's program window's: 1 top left, 2 top right, 3 bottom left, 4 bottom right; a double box's program side: 1 left, 2 right | +| `pip_width`, `pip_height` | 2 each | u16: the PiP window's size, 0 derives it (§5.5); a squeeze's program window's size (§5.9) | | `offset` | 2 | u16: the window's distance, in pixels, from both edges of its corner | | `behaviour` | 1 | what the PiP participant does when it is the only one composed: 1 static, 2 dynamic (§5.5) | | `channels` | 1 | the mix's channels: 1 mono, 2 stereo (§6) | @@ -123,6 +125,10 @@ of these rules: `behaviour` 0. A picture in picture has a `corner` of 1 to 4 and a `behaviour` of 1 or 2, an `offset` below both `width` and `height`, `pip_width` + `offset` at most `width`, and `pip_height` + `offset` at most `height`. +- An ad frame (§5.9) has a `pip_attr` that is not empty, `max_tiles` 2, and `offset` and + `behaviour` 0. A squeeze has a `corner` of 1 to 4, `pip_width` from 1 to `width` − 1 and + `pip_height` from 1 to `height` − 1; a double box has a `corner` of 1 or 2, `pip_width` + and `pip_height` 0, and `gap` + 2 at most `width`. - `channels` is 1 or 2, and `sample_rate` 8 000 to 192 000. - `max_tiles` and `max_voices` are each 1 to 64. - With *k* the least integer whose square is at least `max_tiles`: `gap` is below @@ -250,7 +256,7 @@ above 0, rounds down. **fit**(*bw*, *bh*, *a*, *b*), the largest rectangle of as ### 5.2 Which participants have tiles -From S (§4.4): +An ad frame's tiles are §5.9's. Otherwise, from S (§4.4): 1. **V** is S, less every participant whose video is stopped when `omit_stopped` is 1. 2. For a picture in picture, the **PiP participant** P is the first of V whose `pip_attr` @@ -324,7 +330,8 @@ is **wider** when *sw* × *th* ≥ *sh* × *tw*. (*tx*, *ty* + ⌊(*th* − *dh*) / 2⌋, *tw*, *dh*) with *dh* = max(1, ⌊*tw* × *sh* / *sw*⌋); any other into (*tx* + ⌊(*tw* − *dw*) / 2⌋, *ty*, *dw*, *th*) with *dw* = max(1, ⌊*th* × *sw* / *sh*⌋). -- **Default**: cover for a grid tile and the PiP window, contain for the featured tile. +- **Default**: cover for a grid tile, the PiP window and an ad tile, contain for the + featured tile and a program tile (§5.9). Each mode gives a **source rectangle** and a **destination rectangle**: the part of the source drawn, and where on the canvas it is drawn. A tile of stopped video has neither, and @@ -338,8 +345,9 @@ The layout of a descriptor and a participant set gives: - **the order**: S, every participant, by key; - **the tiles**, in drawing order: the featured tile, then the grid's in G's order, then - the PiP window. Each has its participant, its **place** (`grid`, `featured` or `pip`), - its **z** (0, or 1 for the PiP window, drawn over the others), the tile, and, unless its + the PiP window; or an ad frame's, in §5.9's order. Each has its participant, its + **place** (`grid`, `featured`, `pip`, `program` or `ad`), its **z** (0, or 1 for the PiP + window and a squeeze's program window, drawn over the others), the tile, and, unless its video is stopped, its source and destination rectangles; - **the voices** (§6). @@ -358,6 +366,48 @@ sink sees otherwise, so a sink that joins late lays the room out as one that was throughout, and a grant exchange that changes an attribute re-lays the room out at the exchange's tick, with no participant leaving. +### 5.9 Ad frames + +An ad frame composes a program and an ad into one picture: a **squeeze** (layout 3) shrinks +the program into a window in one corner and shows the ad in the L around it; a **double box** +(layout 4) shows the program and the ad in two boxes side by side. The frame is an integer +function of the canvas and the fields below, so the part of the picture the ad shows in is +fixed by what the descriptor's signer signed, and a record of another extension that states +the same fields fixes the same part (stitched-presentation §6). + +**The frame** of a canvas of `width` × `height`, with *W* its width and *H* its height: + +- **Squeeze**: with *pw* = `pip_width` and *ph* = `pip_height`, the **program window** is *pw* + × *ph* in the corner `corner` names: at (0, 0) top left, (*W* − *pw*, 0) top right, (0, *H* − + *ph*) bottom left and (*W* − *pw*, *H* − *ph*) bottom right. The **ad tile** is the whole + canvas. The **ad region** is the canvas less the window, two rectangles: the **side band**, + beside the window and as high as it, (*pw*, *y*, *W* − *pw*, *ph*) for a window on the + left and (0, *y*, *W* − *pw*, *ph*) for one on the right, with *y* the window's top; and + the **full band**, the rest of the rows, (0, *ph*, *W*, *H* − *ph*) for a window at the top + and (0, 0, *W*, *H* − *ph*) for one at the bottom. +- **Double box**: with *bw* = ⌊(*W* − `gap`) / 2⌋, the left box is (0, 0, *bw*, *H*) and the + right box (*bw* + `gap`, 0, *W* − *bw* − `gap`, *H*). The program's box is the left for a + `corner` of 1 and the right for 2, and the ad's the other. Each tile is its box under + `aspect` auto; otherwise it is fit(*box width*, *box height*, *a*, *b*) centred in its box, + with *a*:*b* the reference aspect of §5.3. The **program tile** and the **ad tile** are + those, and the ad region is the ad tile alone. + +The ad region's rectangles are in order of their top edge, and none of them overlaps the +program's tile. Its **share** of the picture is ⌊10 000 × *A* / (*W* × *H*)⌋ basis points, +with *A* the sum of its rectangles' areas. + +**The participants.** From V (§5.2 step 1), the **ad participant** is the first whose +`pip_attr` attribute is `true`, and the **program participant** is the first of the others +once the featured participant (§5.2 step 3) is moved to their front. Then: + +- with both, the tiles are, for a squeeze, the ad tile (place `ad`, z 0) and then the + program window (place `program`, z 1, drawn over it); for a double box, the program tile + and then the ad tile (both z 0); +- with the program participant alone, one tile, the canvas, of place `program`; with the ad + participant alone, one tile, the canvas, of place `ad`; with neither, none. + +No other participant has a tile; the voices are §6's, as for every layout. + ## 6. The audio mix The **voices** are the members of S (before any is omitted for its video) that have an @@ -541,7 +591,8 @@ verifier raises (live-rooms §11 and the live-events codes it lists), at `room`. shapes and of extreme aspects; a featured participant present, absent, kept past the tile limit, and first in order of two; ties in order broken by join order and by key; stopped video shown and omitted; picture in picture in every corner under both - behaviours, alone static and alone dynamic, with derived sizes and stopped video; and + behaviours; squeezes in every corner and double boxes on both sides, with the program + alone, the ad alone, and a featured program, alone static and alone dynamic, with derived sizes and stopped video; and attributes that do not read. - **mixes**: blocks of PCM and the mix of them (§6): passing through, both widths into both, saturation high and low and only at the end, pans and gains at their limits, diff --git a/wai/extensions/stitched-presentation.md b/wai/extensions/stitched-presentation.md new file mode 100644 index 000000000..c1a0b6639 --- /dev/null +++ b/wai/extensions/stitched-presentation.md @@ -0,0 +1,675 @@ +# WAI Extension: Stitched Presentation + +> Status: Draft. Registers the record `wai.video.stitched_presentation`: a player's signed +> claim, `WPS1`, that it presented one ad another party wrote into the stream, either stitched +> into the segments of an HLS or DASH presentation or served as an HLS interstitial, beside the +> **pod-input record**, `WPI1`, that the stitcher or a proxy in front of the ad server signs for +> each ad decision. A claim names its ad by the **marker** the stream carries for it (an +> `EXT-X-DATERANGE` tag, an asset of an interstitial's asset list, or an `Event` of a DASH +> `EventStream`) and the ad identity that marker carries, in the manifest whose SHA-256 it +> states; it carries ad-presentation's spans of visible and audible play, its viewability rules +> and quartile, and its device attestation; and, for an ad shown in a composited frame (a +> squeeze or a double box of [live-compose](live-compose.md) §5.9), the share of the picture the +> ad's region holds. The pod-input record states the digest of the ad server's response, the +> ads it chose with their markers, creatives and prices, the avail, the frame and the +> measurement terms. A verifier recomputes, from the claims that verify, each ad's +> presentations, the quartiles they reached and the spend that counts; the choice stays the ad +> server's. No new transport. The reference implementation is the `stitched_presentation` +> module of `wai-rs` (feature `stitched_presentation`); the corpus is +> [`live-conformance/stitched/`](../live-conformance/stitched/), and its second implementation +> is `live-conformance/stitched_presentation_verify.py`. +> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. +> +> Texts read for this revision, each at its source: +> +> - *Video Ad Serving Template (VAST)* 4.3, December 2022: §3.1 to §3.4.4, §3.4.8, §3.6 to +> §3.7.1 and §3.8 to §3.8.1, beside the sections [ad-presentation](ad-presentation.md) read. +> - *Server-Side Ad Insertion and OTT Guidance*, August 2021, §1.2, §2 and §3. +> - For HLS and DASH, the texts [live-ads](live-ads.md) read, at the revisions it names: +> `draft-pantos-hls-rfc8216bis-22` §4.2 (attribute lists), §4.4.5.1 (`EXT-X-DATERANGE`) and +> Appendix D (interstitials and the asset list); ISO/IEC 23009-1:2026's MPD schema (its +> `EventStream` and `Event`). Nothing here depends on a part of them live-ads did not read. +> - RFC 8259 (JSON) and XML 1.0, for the subset of §3.5. +> +> A section number of one of these texts is that revision's. + +## 1. Scope + +In scope: the marker of an ad in a stream, its body and digest, and how each is read from a +playlist, an asset list or an MPD (§3); the pod-input record, its rules and signature, and a +form-1 `wai.video.ssai` receipt as its response (§4); the stitched claim, its session, its +rules, its signature and its attestation challenge (§5); composited frames (§6); verification: +each record's and each claim's verdict, each ad's presentations and quartiles, and the spend +that counts, by currency (§7); and the reference producer, the tool and the service (§8). + +Out of scope: a transport; how a player's session key reaches the signer (§4.6 says what the +signer does with it, not how it arrives); the ad decision itself, which stays the ad server's; +how a stitcher conditions a creative into segments; the contents of a winning bid's document +and of a VMAP document, which a record binds by digest and this revision does not read; the +SCTE-35 messages a stream may carry; a billing period, an invoice or a payment, which an +operator's system makes from what §7 states; and the detection of invalid traffic, which stays +statistical and the measurer's (ad-presentation §13). + +Relationship to the core spec: `wai.video.stitched_presentation` is a **record** +([SPEC](../SPEC.md) §5): a signed record a WAI tool emits and verifies. It names no payload +format and is not dispatchable. + +Relationship to [ad-presentation](ad-presentation.md): that extension's claim names an +impression of a break-form receipt of a room; it puts the stitched form of `wai.video.ssai` +out of its scope, since such receipts name no session of a room. This extension brings stitched +and interstitial ads into scope with a claim of its own. It takes from ad-presentation, by +reference: the creative digest (§4 there); the span and its rules (§5.2, §5.4 rules 4 to 6); +the viewability rules, the qualifying run and the quartile (§6); the token, its challenge and +its issuer's verifier (§7); and the presentation fields of a policy (§8), here carried in the +record. It changes nothing in ad-presentation. + +Relationship to [live-ads](live-ads.md) and `wai.video.ssai`: a room's breaks keep live-ads' +policy and break-form receipts. A stitcher that runs `wai.video.ssai`'s own deterministic +decision seals a form-1 receipt as before; a record whose response is that receipt (§4.5) lets +a verifier set the receipt's stated quartiles and billable spend beside the ones the players' +claims give. Every receipt of either form keeps its bytes. + +Relationship to [live-compose](live-compose.md): a record's frame (§4.1) is live-compose +§5.9's ad frame, and the ad's region and its share are that section's. + +## 2. Terms + +- **Stream**: an HLS presentation or a DASH presentation, live or on demand. +- **Stitcher**: the server that writes ads into a stream: into its segments (server-side ad + insertion), or as interstitials a player fetches (server-guided ad insertion). +- **Ad server**: the party whose response decides which ads fill a break. +- **Response**: the ad server's answer, as the signer received it: a VAST document, a VMAP + document, or a form-1 `wai.video.ssai` receipt (§4.5). +- **Signer**: the key that signs a pod-input record: the stitcher's, or that of a proxy that + forwards the stitcher's ad requests to the ad server and their responses back. +- **Break**, **avail**: a span of the stream that ads fill, and its duration. +- **Manifest**: a document a player reads a marker from: a Media Playlist, an asset list or an + MPD (§3). +- **Marker**: the fields that place one ad in a stream (§3). +- **Ad identity**: the string a marker carries for its ad (§3), 1 to 255 bytes of UTF-8. +- **Player**, **session key**, **creative**, **span**, **geometry**, **token**, **issuer**, + **basis point**: as in ad-presentation §2. +- **Record**: a pod-input record (§4). **Claim**: a stitched claim (§5). +- **Presented**: the verdict of a claim that passes every step of §7.3. + +Integers are little-endian unless this text says otherwise. A digest is SHA-256 (FIPS 180-4), +32 bytes. Ed25519 is RFC 8032's, verified strictly ([SPEC](../SPEC.md) §7 *Signatures*): a key +that fails step 1 or 2 is `bad_key`, and a signature that fails a later step is +`bad_signature`. A *short* field is a u8 length and that many bytes; a *long* field is a u16 +length and that many bytes. A *text* field is a short field of 1 to 255 bytes of UTF-8. + +## 3. Markers + +### 3.1 The marker, its body and its digest + +A marker is one of three kinds, by its **carriage**: + +| carriage | where the stream carries it | its fields | +|---|---|---| +| 1 | an `EXT-X-DATERANGE` tag of a Media Playlist (§3.2) | the tag's `ID` and `START-DATE` | +| 2 | an asset of an HLS interstitial's asset list (§3.3) | the interstitial date range's `ID` and `START-DATE`, and the asset's index in its asset list | +| 3 | an `Event` of a DASH `EventStream` (§3.4) | the `id` of its `Period`, the `schemeIdUri` and `value` of its `EventStream`, and its own `id` | + +A marker's **body** is its fields in that order, each a long field of its bytes as the +manifest writes them (an attribute value with its quotes removed; an XML attribute value with +its references replaced, §3.5), with an absent `Period` `id` or `EventStream` `value` empty; +and, for carriage 2, the asset's index as a u16 after the two long fields. A body of a carriage +**reads** when it is exactly those fields, with no byte after them. + +The **marker digest** is SHA-256 over `wai:stitched-marker` (ASCII) and 0x01, the carriage (one +byte) and the body. + +An HLS date range's `ID` names it within its playlist, and its `START-DATE` is fixed for the +`ID` (`draft-pantos-hls-rfc8216bis-22` §4.4.5.1); together they place an ad on the stream's +timeline, and stay the same across the reloads of a live playlist. A DASH event is named by its +scheme, its value and its `id` within its Period. + +### 3.2 Carriage 1: a date range + +A Media Playlist is read as its lines: its bytes are UTF-8, split at each line feed with a +carriage return before it dropped, and its first line is `#EXTM3U`. Each line that begins +`#EXT-X-DATERANGE:` is a **date range tag**, and the rest of the line its **attribute list**: +`NAME=VALUE` pairs separated by single commas, each name of `A`–`Z`, `0`–`9` and `-` and given +once; a value is a quoted string, `"` then bytes other than `"`, CR and LF then `"`, or else one +or more bytes other than `"`, `,` and white space. A tag whose list does not read so, or whose +`ID` or `START-DATE` is not a quoted string, names no ad. (These are §4.2's rules for an +attribute list, as live-ads reads them.) + +An ad of carriage 1 is a date range tag that carries the attribute the record's `id_attr` names +(§4.1). Its marker is the tag's `ID` and `START-DATE`, and its ad identity is that attribute's +value: a quoted string's contents, or the value as written. A stitcher writes the ad identity +in an attribute of the date range of each ad it stitches; the attribute's name is the +stitcher's (a client attribute, `X-` and a name, by §4.4.5.1), and a record names it. + +### 3.3 Carriage 2: an interstitial's asset + +An **interstitial date range** is a date range tag (§3.2) whose `CLASS` is the quoted string +`com.apple.hls.interstitial` (Appendix D.2, as live-ads §5.1 writes it). +Its asset list is the JSON object (RFC 8259) Appendix D.2 defines: its `ASSETS` member is an +array of objects, each with a string member `URI` (live-ads §7.2). An ad of carriage 2 is the +asset at index *k* of the asset list a player was served for an interstitial date range. Its +marker is the date range's `ID` and `START-DATE` and *k*, and its ad identity is the asset's +`URI`. + +The asset list is the manifest a claim of carriage 2 names (§5.1), since each viewer may be +served another one under the same date range; the playlist that carries the date range is held +beside it (§7.3 step 6). + +### 3.4 Carriage 3: an event + +An MPD is read as XML by the subset of §3.5. Its root element is `MPD`; each child `Period` of +the root, each child `EventStream` of a Period that has a `schemeIdUri`, and each child `Event` +of such an EventStream that has an `id` is considered. An ad of carriage 3 is such an Event +that carries the attribute the record's `id_attr` names. Its marker is its Period's `id`, its +EventStream's `schemeIdUri` and `value`, and its own `id`; its ad identity is the value of that +attribute. + +### 3.5 The XML subset + +An XML document (an MPD of §3.4, a VAST document of §7.2) is read over this subset of XML 1.0, +and one outside it does not read: + +- its bytes are UTF-8 with no byte order mark; +- before and after its one root element there are only white space, comments and processing + instructions (an XML declaration among them); a document type declaration, or any other + markup that begins ` 3 or p["fill"] > 3: + if p["layout"] not in (GRID, PIP, SQUEEZE, DOUBLE_BOX) or p["aspect"] > 3 or p["fill"] > 3: return False for k in ("featured_attr", "order_attr", "pip_attr", "gain_attr", "pan_attr"): if p[k] != "" and not is_key(p[k].encode()): @@ -148,6 +148,11 @@ def fields_ok(p): if p["layout"] == GRID: if p["pip_attr"] != "" or p["corner"] or p["pip_width"] or p["pip_height"] or p["offset"] or p["behaviour"]: return False + elif p["layout"] in (SQUEEZE, DOUBLE_BOX): + if p["pip_attr"] == "" or p["max_tiles"] != 2 or p["offset"] or p["behaviour"]: + return False + if not frame_fields_ok(w, h, p["layout"], p["corner"], p["pip_width"], p["pip_height"], g): + return False else: o = p["offset"] if not (1 <= p["corner"] <= 4 and p["behaviour"] in (STATIC, DYNAMIC) and o < w and o < h @@ -337,6 +342,8 @@ def layout(p, entrants): for m in members if m["audio"]][:p["max_voices"]] # §5.2 shown = [m for m in members if not (p["omit_stopped"] and m["video"] is None)] + if p["layout"] in (SQUEEZE, DOUBLE_BOX): + return {"order": [m["principal"].hex() for m in members], "tiles": frame_tiles(p, shown), "voices": voices} pip_m = next((m for m in shown if m["pip"]), None) if p["layout"] == PIP else None rest = [m for m in shown if m is not pip_m] f = next((i for i, m in enumerate(rest) if m["featured"]), None) @@ -354,7 +361,7 @@ def layout(p, entrants): def tile_of(p, m, rect, place, z): - mode = p["fill"] if p["fill"] != DEFAULT else (CONTAIN if place == "featured" else COVER) + mode = p["fill"] if p["fill"] != DEFAULT else (CONTAIN if place in ("featured", "program") else COVER) out = {"principal": m["principal"].hex(), "place": place, "z": z, "tile": list(rect), "dst": None, "src": None} if m["video"] is not None: dst, src = show(rect, m["video"], mode) @@ -398,6 +405,60 @@ def pip_tile(p, m): return tile_of(p, m, (x, y, pw, ph), "pip", 1) +# ---------------------------------------------------------------- §5.9 ad frames + +def frame_fields_ok(w, h, kind, corner, pw, ph, gap): + """§3.3's rules on an ad frame's own fields.""" + if kind == SQUEEZE: + return 1 <= corner <= 4 and 1 <= pw < w and 1 <= ph < h + if kind == DOUBLE_BOX: + return corner in (1, 2) and pw == 0 and ph == 0 and gap + 2 <= w + return False + + +def ad_frame(w, h, kind, corner, pw, ph, gap, aspect): + """§5.9: (program tile, ad tile, ad region) of a squeeze or a double box.""" + if kind == SQUEEZE: + left, top = corner in (1, 3), corner in (1, 2) + x, y = (0 if left else w - pw), (0 if top else h - ph) + side = (pw if left else 0, y, w - pw, ph) + band = (0, ph if top else 0, w, h - ph) + return (x, y, pw, ph), (0, 0, w, h), [side, band] if top else [band, side] + bw = (w - gap) // 2 + boxes = [(0, 0, bw, h), (bw + gap, 0, w - bw - gap, h)] + + def tile(b): + return b if aspect == AUTO else centred(b, *fit(b[2], b[3], *reference(aspect))) + prog, ad = (boxes[0], boxes[1]) if corner == 1 else (boxes[1], boxes[0]) + return tile(prog), tile(ad), [tile(ad)] + + +def share_bp(region, w, h): + """§5.9: the region's share of the picture, in basis points.""" + return sum(r[2] * r[3] for r in region) * 10000 // (w * h) + + +def frame_tiles(p, shown): + """§5.9: the ad participant, the program participant, and their tiles.""" + ad = next((m for m in shown if m["pip"]), None) + rest = [m for m in shown if m is not ad] + f = next((i for i, m in enumerate(rest) if m["featured"]), None) + if f is not None: + rest.insert(0, rest.pop(f)) + prog = rest[0] if rest else None + w, h = p["width"], p["height"] + if prog is not None and ad is not None: + pt, at, _ = ad_frame(w, h, p["layout"], p["corner"], p["pip_width"], p["pip_height"], p["gap"], p["aspect"]) + if p["layout"] == SQUEEZE: + return [tile_of(p, ad, at, "ad", 0), tile_of(p, prog, pt, "program", 1)] + return [tile_of(p, prog, pt, "program", 0), tile_of(p, ad, at, "ad", 0)] + if prog is not None: + return [tile_of(p, prog, (0, 0, w, h), "program", 0)] + if ad is not None: + return [tile_of(p, ad, (0, 0, w, h), "ad", 0)] + return [] + + # ---------------------------------------------------------------- §6 the mix def mix(channels, frames, inputs): @@ -828,6 +889,10 @@ def mutations(doc): mutate("order_attribute: an entrant's order", lambda d: case(d, "layouts", "order_attribute")["entrants"][2]["attrs"][0].__setitem__(1, "9")), mutate("stopped_omitted: omit_stopped off", lambda d: case(d, "layouts", "stopped_omitted")["params"].update(omit_stopped=False)), mutate("pip_alone_dynamic: stated static", lambda d: case(d, "layouts", "pip_alone_dynamic")["params"].update(behaviour=STATIC)), + mutate("squeeze_top_left: the program window under the ad", lambda d: case(d, "layouts", "squeeze_top_left")["tiles"][-1].update(z=0)), + mutate("squeeze_bottom_right: the window in another corner", lambda d: case(d, "layouts", "squeeze_bottom_right")["params"].update(corner=1)), + mutate("double_box_left: the program and the ad swapped", lambda d: case(d, "layouts", "double_box_left")["tiles"].reverse()), + mutate("squeeze_featured_program: the first in order as the program", lambda d: case(d, "layouts", "squeeze_featured_program")["entrants"][2].update(attrs=[])), mutate("attr_order_leading_zero: stated compose_invalid", lambda d: case(d, "layouts", "attr_order_leading_zero").update(reason="compose_invalid")), mutate("voices: a gain", lambda d: case(d, "layouts", "voices")["voices"][0].update(gain=4097)), mutate("sum_saturates_high: an output sample", lambda d: case(d, "mixes", "sum_saturates_high").update(pcm=flip(case(d, "mixes", "sum_saturates_high")["pcm"], 0))), diff --git a/wai/live-conformance/stitched/README.md b/wai/live-conformance/stitched/README.md new file mode 100644 index 000000000..49d6f7d37 --- /dev/null +++ b/wai/live-conformance/stitched/README.md @@ -0,0 +1,132 @@ +# live-conformance/stitched — stitched presentation + +The corpus of [`extensions/stitched-presentation.md`](../../extensions/stitched-presentation.md) +§10: streams with ads another party stitched into their segments or served as interstitials; +the manifests the players read (HLS Media Playlists, an asset list, an MPD); the ad servers' +responses (VAST documents and a form-1 `wai.video.ssai` receipt); the `WPI1` pod-input record +each break's signer signed; the `WPS1` claims the players signed, good ones and a control of +each check; what a verifier states of them. + +**Presentation equivalence.** For each vector, a conforming verifier given its `records` and +`claims`, the files its `manifests`, `responses` and `receipts` name, and the corpus's +`creatives`, `issuers` and `signers` (§7.1), states exactly: + +- `record_facts`, each record in the order given: its `digest` (`null` when it does not + read) and its `verdict`, `accepted` or a reason; for an accepted record, `response_checked`, + its `ads` (each `slot`, its `own_claims` by index, `presentations`, `verdict`, `quartiles`, + `presented_spend_micros` and `stated_quartile`), its `presented_spend_micros` and its + `stated_spend_micros`; +- `claim_facts`, each claim in the order given: its `digest`, its `verdict`, `presented` or a + reason, the `record` and `slot` it names (from §7.3 step 4 on), and, for an own claim, the + `quartile` it reaches, its `run_ms` and its `viewable_ms`; +- `spend`, the presented spend by currency. + +`record_about` and `claim_about` say what each record and claim is, and are not checked. The +files a vector's `bound` names are documents a record binds by digest and no verifier reads +(a winning bid, a VMAP response); they are here so that anyone recomputes the digests. A +verifier that states another verdict or fact does not conform. + +**Provenance.** Generated here: `wai_stitched_presentation vectors gen` writes every file from +streams authored in code, from fixed seeds. Each session key is SHA-256 of +`wai:stitched-presentation/corpus/player`, a zero byte and its number (`players` lists their +public keys); each signer's key the same with `signer` (signers 1 and 2 are the trusted ones, +`signers`; signer 9 is not); each media object's content hash SHA-256 of a fixed label and two +numbers; each token's nonce SHA-256, and its salt SHA-384, of a fixed label and its number. The +attestation issuer's key is the test key RFC 9578 Appendix A.2 publishes, as in +[`../presentation/`](../presentation/). Every playlist, asset list, MPD, VAST document, bid +document and receipt is a fixture written for its case under example domains; none was fetched +from a service. Ed25519, BLAKE3 and RSASSA-PSS over a given salt are deterministic, so every +file is the same bytes on every machine. The generator asserts that each record and claim gets +the verdict it was written to have and that the corpus gives every reason of §9, before it +writes. No recorded data. + +## Layout + +- `vectors.json`: `creatives`, each its `objects` (`content_hash`, `duration_ms`); `issuers`; + `signers` (Ed25519 public keys, hexadecimal); `players` (informative); and `vectors`. +- `NAME.*`: each vector's documents: `.m3u8` Media Playlists, `.assets.json` an asset list, + `.mpd` an MPD, `.vast.xml` VAST 4.3 responses, `.vmap.xml` a VMAP response, `.bid.json` a + winning bid's document, `.receipt.json` a form-1 receipt in its JSON carriage (§4.5). + +The vectors: + +- `hls_stitched`: two viewers' personalised playlists, each ad under a date range whose + `X-AD-ID` carries its `AdServingId`; a record per viewer priced from its VAST `Pricing` + (CPM 25.00, 18.00 and 30.00 USD). Presented: viewer 1's two ads (the second to its third + quartile) and viewer 2's first. Viewer 2's second ad was fetched ahead of the break and never + presented: `claim_absent`, no spend. Record controls: `pod_signer_unknown`, + `pod_response_mismatch` (a price the response does not give; a universal creative identifier + it does not carry), `pod_duplicate`, `pod_input_malformed`, `bad_signature`. Claim controls: + `claim_duplicate`, `claim_key_unbound`, `claim_session_unbound`, `claim_no_decision`, + `claim_manifest_unknown`, `claim_marker_absent`, `stitched_claim_malformed` (truncated; a + signed skip with no resumption), `bad_signature`. +- `hls_interstitial`: one shared playlist with an interstitial date range, and the asset list a + viewer was served; records under the cross-media rule with an audible run and attestation, + ad-i1 at a winning bid's price (its document bound by digest), ad-i2 at CPM 20.00. + Presented: viewer 11's two, attested on a television. Controls: `attestation_absent`, + `attestation_invalid` (another presentation's token). +- `squeezeback`: a squeeze on a DASH channel, the program in a 1440 × 810 window in the bottom + right corner and the ad in the L around it (4375 basis points of the picture), marked by an + event whose `messageData` carries the ad's `AdServingId`; one shared record. Presented: + viewers 21, 22 (70% of the region in view) and 23 (to the midpoint): three presentations. + Controls: `claim_frame_mismatch`, `claim_not_viewable`, `claim_duplicate`, + `claim_asset_mismatch`. +- `double_box`: a double box on an HLS channel, its record in EUR at a price the signer states, + its VMAP response bound and not read. Presented: viewer 31 over the ad's box, viewer 32 over + the player's area. +- `form1_prefetch`: a stitcher's own decision sealed in a form-1 receipt whose beacons counted + ad-f2 as started when its first segment was fetched ahead of the break; viewer 41 left after + ad-f1. Stated: quartiles 5 and 1, 5 000 000 µ-units; presented: ad-f1 alone, 3 000 000. + Control: `pod_response_mismatch` (the same receipt at another price). + +## Running + +``` +cd ../../wai-rs +cargo run --no-default-features --features stitched_presentation --bin wai_stitched_presentation -- vectors verify ../live-conformance/stitched +cargo run --no-default-features --features stitched_presentation --bin wai_stitched_presentation -- vectors gen ../live-conformance/stitched # regenerate +cargo test --no-default-features --features stitched_presentation --test stitched_presentation +cd ../live-conformance +python3 -I stitched_presentation_verify.py stitched +node --test ../../joule-code/joule-code-rs/crates/joule-moq-web/web/ads/stitched.test.mjs +``` + +`vectors verify` and `tests/stitched_presentation.rs` require every file to regenerate byte for +byte, no other file but this one to be there, and `wai-rs` to give every vector its facts; the +test also requires §9 to list exactly the codes the reference adds. +`stitched_presentation_verify.py` is the second implementation, written from the extension's +text and sharing no code with `wai-rs`: it reads every manifest, response, record and claim +itself (with the spans' quartile and run and RSASSA-PSS of `ad_presentation_verify.py`, a +form-1 receipt's leaves of `live_ads_verify.py` and the ad frame of `live_compose_verify.py`), +requires every reason to appear, then mutates copies of the corpus, one defect each, each of +which must fail, and drops each of its own checks in turn, each weakened verifier of which the +corpus must catch. The browser client's reference producer requires the corpus's claims from +the corpus's manifests and observation sequences. Pass = an exit status of 0 from each. + +## The tool's inputs + +`wai_stitched_presentation verify ` (and `POST /v1/verify` of the service) reads one +JSON object: + +| member | value | +|---|---| +| `records` | the pod-input records, hexadecimal | +| `claims` | the claims, hexadecimal, at most 2^20 | +| `manifests` | the manifests' texts, as strings | +| `responses` | the VAST and VMAP documents' texts, as strings | +| `receipts` | form-1 receipts in their JSON carriage (§4.5), as objects | +| `creatives` | as `vectors.json`'s | +| `issuers` | as `vectors.json`'s | +| `signers` | the accepted signers' keys, hexadecimal | + +and prints `record_facts`, `claim_facts` and `spend` as this corpus states them, with the +`capability`. `wai_stitched_presentation sign-pod --key ` reads a draft +of a record: the fields of §4.1 by name (`carriage`, `session` in hexadecimal or absent for a +shared break, `avail_id`, `avail_ms`, `response_format`, `response_file` (a path beside the +draft whose SHA-256 is taken) or `response` (hexadecimal), `id_kind`, `id_attr`, `currency`, +`exponent`, `frame` (its fields, or absent for the full picture) and `requirement` +(`require`, `rule`, `token_type`, `issuer`)), and `ads`, each with `marker` as its fields +(`id` and `start` for carriage 1; and `index` for 2; `period`, `scheme`, `value` and `id` for +3), `ad_id`, `serving_id`, `creative_id`, `registry`, `creative_hash`, `duration_ms`, +`price_micros`, `price_source` and `evidence`. It refuses a draft that breaks §4.4, and prints +the signed record in hexadecimal. diff --git a/wai/live-conformance/stitched/double_box.m3u8 b/wai/live-conformance/stitched/double_box.m3u8 new file mode 100644 index 000000000..fd623f780 --- /dev/null +++ b/wai/live-conformance/stitched/double_box.m3u8 @@ -0,0 +1,19 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:5 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PROGRAM-DATE-TIME:2026-10-25T12:14:52.000Z +#EXTINF:4.000, +program/0.m4s +#EXTINF:4.000, +program/1.m4s +#EXT-X-DATERANGE:ID="db-break",CLASS="urn:example:break",START-DATE="2026-10-25T12:15:00.000Z",PLANNED-DURATION=10.000 +#EXT-X-DATERANGE:ID="db-1",CLASS="urn:example:ad",START-DATE="2026-10-25T12:15:00.000Z",DURATION=10.000,X-AD-ID="srv-db-1" +#EXT-X-DISCONTINUITY +#EXTINF:5.000, +ads/870777575450fa85b8d8e996f2cc78676b269b306921ebc9feed2a3e74d4b4a8.m4s +#EXTINF:5.000, +ads/abf6e1676be9383b7b2192ca27e431e2d8f3a2107c19d93ada212f90ee497c13.m4s +#EXT-X-DISCONTINUITY +#EXTINF:4.000, +program/2.m4s diff --git a/wai/live-conformance/stitched/double_box.vmap.xml b/wai/live-conformance/stitched/double_box.vmap.xml new file mode 100644 index 000000000..f29458818 --- /dev/null +++ b/wai/live-conformance/stitched/double_box.vmap.xml @@ -0,0 +1,2 @@ + + diff --git a/wai/live-conformance/stitched/form1_prefetch.m3u8 b/wai/live-conformance/stitched/form1_prefetch.m3u8 new file mode 100644 index 000000000..9f613e150 --- /dev/null +++ b/wai/live-conformance/stitched/form1_prefetch.m3u8 @@ -0,0 +1,29 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:5 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PROGRAM-DATE-TIME:2026-10-25T12:19:52.000Z +#EXTINF:4.000, +program/0.m4s +#EXTINF:4.000, +program/1.m4s +#EXT-X-DATERANGE:ID="f1-break",CLASS="urn:example:break",START-DATE="2026-10-25T12:20:00.000Z",PLANNED-DURATION=30.000 +#EXT-X-DATERANGE:ID="f1-ad-0",CLASS="urn:example:ad",START-DATE="2026-10-25T12:20:00.000Z",DURATION=15.000,X-AD-ID="srv-f1-0" +#EXT-X-DISCONTINUITY +#EXTINF:5.000, +ads/77c807ae2513651a5aa4328029ecf1f7c14824ac70155ed739e90ad648905c46.m4s +#EXTINF:5.000, +ads/921eb679dcc0008c174c42686ec8c1fd40e93c978223d12e90e2a9e864749259.m4s +#EXTINF:5.000, +ads/19010782cd315059b9f603e1a81f2d3c619eb7cb32693ea4f8a043a635d70fa9.m4s +#EXT-X-DATERANGE:ID="f1-ad-1",CLASS="urn:example:ad",START-DATE="2026-10-25T12:20:15.000Z",DURATION=15.000,X-AD-ID="srv-f1-1" +#EXT-X-DISCONTINUITY +#EXTINF:5.000, +ads/058b32345e0c139843d5795b3beb9b4f3a89ebc09c5afbad4c27f384f011f864.m4s +#EXTINF:5.000, +ads/0f74b2befd5f848930e6ad70805fad903ab68654eeee4bd47768200f5a1a7067.m4s +#EXTINF:5.000, +ads/28f2065e0b7e2480b11447cace2569a2131257012096da3a95004dacdbcd8d17.m4s +#EXT-X-DISCONTINUITY +#EXTINF:4.000, +program/2.m4s diff --git a/wai/live-conformance/stitched/form1_prefetch.receipt.json b/wai/live-conformance/stitched/form1_prefetch.receipt.json new file mode 100644 index 000000000..e44c2627f --- /dev/null +++ b/wai/live-conformance/stitched/form1_prefetch.receipt.json @@ -0,0 +1,39 @@ +{ + "form": 1, + "avail_id": "f1-break", + "manifest_hash": "e3ca1172f496bbc0dfa72abe61540b05091e8fb799d19ceddc31fac875aa33b4", + "pod_hash": "a8002ba9cab3b3c148f8092550600405825346ee2376c33aa703a56ceb5e36a4", + "impressions": [ + { + "slot": 0, + "ad_id": "ad-f1", + "creative_hash": "9300e637c151e8585af0813f477696c97bebc09d94472b61109841d8fdb22135", + "duration_ms": 15000, + "price_micros": 3000000, + "quartiles_reached": 5 + }, + { + "slot": 1, + "ad_id": "ad-f2", + "creative_hash": "6556c8bc5c8c5e27f028eef55945ef4dd80e88422c8f761aed913fb815818d7e", + "duration_ms": 15000, + "price_micros": 2000000, + "quartiles_reached": 1 + } + ], + "merkle_root": "567832e10c49897fd3d7baaa4031e6de3a46c01c702b8779d3e6cc21b401c707", + "budget_spent_micros": 5000000, + "work_decide_ops": 2, + "joules_micro": 0, + "energy_provenance": null, + "grant": { + "grant_hash": "a44f5e5c71bf3d1c33767885a870d6726d31fc78be0dca85535e27e26b02a86a", + "capability": "video.ssai", + "joule_ceiling_micro": 1000000, + "funds_ceiling": 10000000 + }, + "parent_receipt_hash": null, + "signer_pubkey": "d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d356", + "signer_id": "did:example:stitcher", + "sig": "81ed4d1369a099504f2ac0aa7a41f23498d2f760ab8f7a60a774c28d977e8bdbd7414f0fb1b09378c897ad4e83b9edb7f8b85854db536946d1b8a3d09a0bed09" +} diff --git a/wai/live-conformance/stitched/hls_interstitial.assets.json b/wai/live-conformance/stitched/hls_interstitial.assets.json new file mode 100644 index 000000000..b778cd089 --- /dev/null +++ b/wai/live-conformance/stitched/hls_interstitial.assets.json @@ -0,0 +1 @@ +{"ASSETS":[{"URI":"https://ads.example/c/6b1796fc83afb2a8b367120977058454df135b6140a98f56150ef0b31fad7b7a.m3u8","DURATION":15.000},{"URI":"https://ads.example/c/30d041b4a7efc371a7eee6d2c4fab9200f4cc21b5f32152e50921e5d83230b93.m3u8","DURATION":15.000}]} \ No newline at end of file diff --git a/wai/live-conformance/stitched/hls_interstitial.bid.json b/wai/live-conformance/stitched/hls_interstitial.bid.json new file mode 100644 index 000000000..4082cf040 --- /dev/null +++ b/wai/live-conformance/stitched/hls_interstitial.bid.json @@ -0,0 +1 @@ +{"auction":"break-300","winner":"ad-i1","price_cpm":"42.00","currency":"USD"} diff --git a/wai/live-conformance/stitched/hls_interstitial.m3u8 b/wai/live-conformance/stitched/hls_interstitial.m3u8 new file mode 100644 index 000000000..d8dba367a --- /dev/null +++ b/wai/live-conformance/stitched/hls_interstitial.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:5 +#EXT-X-MEDIA-SEQUENCE:60 +#EXT-X-PROGRAM-DATE-TIME:2026-10-25T12:04:56.000Z +#EXTINF:4.000, +program/60.m4s +#EXT-X-DATERANGE:ID="break-300",CLASS="com.apple.hls.interstitial",START-DATE="2026-10-25T12:05:00.000Z",DURATION=30.000,X-ASSET-LIST="https://ads.example/break-300.json?_HLS_interstitial_id=break-300",X-RESUME-OFFSET=30.000 +#EXTINF:4.000, +program/61.m4s diff --git a/wai/live-conformance/stitched/hls_interstitial.vast.xml b/wai/live-conformance/stitched/hls_interstitial.vast.xml new file mode 100644 index 000000000..6d08edaba --- /dev/null +++ b/wai/live-conformance/stitched/hls_interstitial.vast.xml @@ -0,0 +1,42 @@ + + + + + example ad server + ad-i1 + srv-300-1 + + + + UID-I1 + + 00:00:15.000 + + + + + + + + + + + example ad server + ad-i2 + srv-300-2 + + 20.00 + + + UID-I2 + + 00:00:15.000 + + + + + + + + + diff --git a/wai/live-conformance/stitched/hls_stitched.viewer1.m3u8 b/wai/live-conformance/stitched/hls_stitched.viewer1.m3u8 new file mode 100644 index 000000000..b60a1a095 --- /dev/null +++ b/wai/live-conformance/stitched/hls_stitched.viewer1.m3u8 @@ -0,0 +1,33 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:5 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PROGRAM-DATE-TIME:2026-10-25T12:01:52.000Z +#EXTINF:4.000, +program/0.m4s +#EXTINF:4.000, +program/1.m4s +#EXT-X-DATERANGE:ID="break-120",CLASS="urn:example:break",START-DATE="2026-10-25T12:02:00.000Z",PLANNED-DURATION=30.000 +#EXT-X-DATERANGE:ID="v1-ad-0",CLASS="urn:example:ad",START-DATE="2026-10-25T12:02:00.000Z",DURATION=15.000,X-AD-ID="srv-v1-0" +#EXT-X-DISCONTINUITY +#EXTINF:4.000, +ads/912c17fb35083738d6eb0bf29548911111b41da0a5cb6b512ac98f17dcf91f21.m4s +#EXTINF:4.000, +ads/d3e30b815bc076686aba9bcea30afd9caa7c41a72ef35bfb9a98c6869d1e12f7.m4s +#EXTINF:4.000, +ads/c31ec984541ddb7ff258804c0fb0e9b66e94fe6528fb5ee1ba25a70144407063.m4s +#EXTINF:3.000, +ads/202266273b25c5dd444eab6c903ad7438cd19f5bcace495a941e97e34184fed9.m4s +#EXT-X-DATERANGE:ID="v1-ad-1",CLASS="urn:example:ad",START-DATE="2026-10-25T12:02:15.000Z",DURATION=15.000,X-AD-ID="srv-v1-1" +#EXT-X-DISCONTINUITY +#EXTINF:4.000, +ads/bea9d70662ff2ffaa4c1f8fef5dfed6b3dfba0e28664eb9602b87a5c7f9d43dd.m4s +#EXTINF:4.000, +ads/7ad95f226ee22f864b1818023079aab7a3a38303c3aa028edd992db97b09c08f.m4s +#EXTINF:4.000, +ads/d4236e07b3ad5d7ca4aa508317c25e41968a9a7650bf7905ec288f94434adb8c.m4s +#EXTINF:3.000, +ads/01eb03276ebac720029d3e9593bd0f1f1c2e0c7e7b584062b144be66261627ba.m4s +#EXT-X-DISCONTINUITY +#EXTINF:4.000, +program/2.m4s diff --git a/wai/live-conformance/stitched/hls_stitched.viewer1.vast.xml b/wai/live-conformance/stitched/hls_stitched.viewer1.vast.xml new file mode 100644 index 000000000..53f53cabc --- /dev/null +++ b/wai/live-conformance/stitched/hls_stitched.viewer1.vast.xml @@ -0,0 +1,43 @@ + + + + + example ad server + ad-a + srv-v1-0 + + 25.00 + + + UID-A + + 00:00:15.000 + + + + + + + + + + + example ad server + ad-b + srv-v1-1 + + 18.00 + + + UID-B + + 00:00:15.000 + + + + + + + + + diff --git a/wai/live-conformance/stitched/hls_stitched.viewer2.m3u8 b/wai/live-conformance/stitched/hls_stitched.viewer2.m3u8 new file mode 100644 index 000000000..80585723b --- /dev/null +++ b/wai/live-conformance/stitched/hls_stitched.viewer2.m3u8 @@ -0,0 +1,31 @@ +#EXTM3U +#EXT-X-VERSION:7 +#EXT-X-TARGETDURATION:5 +#EXT-X-MEDIA-SEQUENCE:0 +#EXT-X-PROGRAM-DATE-TIME:2026-10-25T12:01:52.000Z +#EXTINF:4.000, +program/0.m4s +#EXTINF:4.000, +program/1.m4s +#EXT-X-DATERANGE:ID="break-120",CLASS="urn:example:break",START-DATE="2026-10-25T12:02:00.000Z",PLANNED-DURATION=30.000 +#EXT-X-DATERANGE:ID="v2-ad-0",CLASS="urn:example:ad",START-DATE="2026-10-25T12:02:00.000Z",DURATION=15.000,X-AD-ID="srv-v2-0" +#EXT-X-DISCONTINUITY +#EXTINF:5.000, +ads/ee9c1789553becff0906a1f2513fa7bb542e3518fc8a3594ea5d33bc44b02a9d.m4s +#EXTINF:5.000, +ads/0d3581e40066cd47665fcf783e7b9b7cbbc0c586e227cf06b759d68f995a1aca.m4s +#EXTINF:5.000, +ads/95e9337a233c5d3ba4d2d346e0dd219a8cf83aec52c6f3aad711a57941baac78.m4s +#EXT-X-DATERANGE:ID="v2-ad-1",CLASS="urn:example:ad",START-DATE="2026-10-25T12:02:15.000Z",DURATION=15.000,X-AD-ID="srv-v2-1" +#EXT-X-DISCONTINUITY +#EXTINF:4.000, +ads/bea9d70662ff2ffaa4c1f8fef5dfed6b3dfba0e28664eb9602b87a5c7f9d43dd.m4s +#EXTINF:4.000, +ads/7ad95f226ee22f864b1818023079aab7a3a38303c3aa028edd992db97b09c08f.m4s +#EXTINF:4.000, +ads/d4236e07b3ad5d7ca4aa508317c25e41968a9a7650bf7905ec288f94434adb8c.m4s +#EXTINF:3.000, +ads/01eb03276ebac720029d3e9593bd0f1f1c2e0c7e7b584062b144be66261627ba.m4s +#EXT-X-DISCONTINUITY +#EXTINF:4.000, +program/2.m4s diff --git a/wai/live-conformance/stitched/hls_stitched.viewer2.vast.xml b/wai/live-conformance/stitched/hls_stitched.viewer2.vast.xml new file mode 100644 index 000000000..1355ee476 --- /dev/null +++ b/wai/live-conformance/stitched/hls_stitched.viewer2.vast.xml @@ -0,0 +1,43 @@ + + + + + example ad server + ad-c + srv-v2-0 + + 30.00 + + + UID-C + + 00:00:15.000 + + + + + + + + + + + example ad server + ad-b + srv-v2-1 + + 18.00 + + + UID-B + + 00:00:15.000 + + + + + + + + + diff --git a/wai/live-conformance/stitched/squeezeback.mpd b/wai/live-conformance/stitched/squeezeback.mpd new file mode 100644 index 000000000..5d14fd826 --- /dev/null +++ b/wai/live-conformance/stitched/squeezeback.mpd @@ -0,0 +1,2 @@ + + diff --git a/wai/live-conformance/stitched/squeezeback.vast.xml b/wai/live-conformance/stitched/squeezeback.vast.xml new file mode 100644 index 000000000..1d4461fd7 --- /dev/null +++ b/wai/live-conformance/stitched/squeezeback.vast.xml @@ -0,0 +1,23 @@ + + + + + example ad server + ad-s + srv-sq-1 + + 12.00 + + + UID-S + + 00:00:20.000 + + + + + + + + + diff --git a/wai/live-conformance/stitched/vectors.json b/wai/live-conformance/stitched/vectors.json new file mode 100644 index 000000000..765f7c1fd --- /dev/null +++ b/wai/live-conformance/stitched/vectors.json @@ -0,0 +1,939 @@ +{ + "about": "extensions/stitched-presentation.md §10: streams with ads another party stitched or interstitials it served, the manifests the players read, the ad servers' responses, each break's signed pod-input record and the claims the players signed; each record's and claim's verdict, each ad's presentations and quartiles, and the spend that counts by currency", + "creatives": [ + { + "objects": [ + { + "content_hash": "912c17fb35083738d6eb0bf29548911111b41da0a5cb6b512ac98f17dcf91f21", + "duration_ms": 4000 + }, + { + "content_hash": "d3e30b815bc076686aba9bcea30afd9caa7c41a72ef35bfb9a98c6869d1e12f7", + "duration_ms": 4000 + }, + { + "content_hash": "c31ec984541ddb7ff258804c0fb0e9b66e94fe6528fb5ee1ba25a70144407063", + "duration_ms": 4000 + }, + { + "content_hash": "202266273b25c5dd444eab6c903ad7438cd19f5bcace495a941e97e34184fed9", + "duration_ms": 3000 + } + ] + }, + { + "objects": [ + { + "content_hash": "bea9d70662ff2ffaa4c1f8fef5dfed6b3dfba0e28664eb9602b87a5c7f9d43dd", + "duration_ms": 4000 + }, + { + "content_hash": "7ad95f226ee22f864b1818023079aab7a3a38303c3aa028edd992db97b09c08f", + "duration_ms": 4000 + }, + { + "content_hash": "d4236e07b3ad5d7ca4aa508317c25e41968a9a7650bf7905ec288f94434adb8c", + "duration_ms": 4000 + }, + { + "content_hash": "01eb03276ebac720029d3e9593bd0f1f1c2e0c7e7b584062b144be66261627ba", + "duration_ms": 3000 + } + ] + }, + { + "objects": [ + { + "content_hash": "ee9c1789553becff0906a1f2513fa7bb542e3518fc8a3594ea5d33bc44b02a9d", + "duration_ms": 5000 + }, + { + "content_hash": "0d3581e40066cd47665fcf783e7b9b7cbbc0c586e227cf06b759d68f995a1aca", + "duration_ms": 5000 + }, + { + "content_hash": "95e9337a233c5d3ba4d2d346e0dd219a8cf83aec52c6f3aad711a57941baac78", + "duration_ms": 5000 + } + ] + }, + { + "objects": [ + { + "content_hash": "4b4b43423a097fadccde97ce3195f9e0b103a90da450128b158128a1b84c2a27", + "duration_ms": 5000 + }, + { + "content_hash": "5532613ee736e9a1ddbf1036b9a8afdd8b447ec4b4a0cafc5bc58cbc76e74824", + "duration_ms": 5000 + }, + { + "content_hash": "dc0901e3efbd5a512795275e1854f93946d138d6e4e5fdefcbe516d30bb42fa7", + "duration_ms": 5000 + } + ] + }, + { + "objects": [ + { + "content_hash": "2b3af11a3fa068a4d364daf2f64674b457b1735df987762344e6d7f90b6003ca", + "duration_ms": 5000 + }, + { + "content_hash": "211282a57549ba20a9f3df0eca2c05743ef9b138e0e595547d435c39cfe83fe7", + "duration_ms": 5000 + }, + { + "content_hash": "d6f72c30e1b680929bb04696ea3175d8566623e5ce65fe33e69f76272bdbbe6e", + "duration_ms": 5000 + } + ] + }, + { + "objects": [ + { + "content_hash": "d79c9b64605b5f6c256493d4a0698a1210d95df1ca557ac73fcaec4eeb76d7fd", + "duration_ms": 5000 + }, + { + "content_hash": "91daf7a0ea77e64ac03ad3ee0f27a914473a931e2bb39f08f97e81a8b237619c", + "duration_ms": 5000 + }, + { + "content_hash": "2861506d722cae31e49b4432990477decb59324c719b70112121c43a52dfa633", + "duration_ms": 5000 + }, + { + "content_hash": "b7404e121039cdf1501cd7970b9881a66bf65538be5df8656ba560bc08ac46c5", + "duration_ms": 5000 + } + ] + }, + { + "objects": [ + { + "content_hash": "870777575450fa85b8d8e996f2cc78676b269b306921ebc9feed2a3e74d4b4a8", + "duration_ms": 5000 + }, + { + "content_hash": "abf6e1676be9383b7b2192ca27e431e2d8f3a2107c19d93ada212f90ee497c13", + "duration_ms": 5000 + } + ] + }, + { + "objects": [ + { + "content_hash": "77c807ae2513651a5aa4328029ecf1f7c14824ac70155ed739e90ad648905c46", + "duration_ms": 5000 + }, + { + "content_hash": "921eb679dcc0008c174c42686ec8c1fd40e93c978223d12e90e2a9e864749259", + "duration_ms": 5000 + }, + { + "content_hash": "19010782cd315059b9f603e1a81f2d3c619eb7cb32693ea4f8a043a635d70fa9", + "duration_ms": 5000 + } + ] + }, + { + "objects": [ + { + "content_hash": "058b32345e0c139843d5795b3beb9b4f3a89ebc09c5afbad4c27f384f011f864", + "duration_ms": 5000 + }, + { + "content_hash": "0f74b2befd5f848930e6ad70805fad903ab68654eeee4bd47768200f5a1a7067", + "duration_ms": 5000 + }, + { + "content_hash": "28f2065e0b7e2480b11447cace2569a2131257012096da3a95004dacdbcd8d17", + "duration_ms": 5000 + } + ] + } + ], + "issuers": [ + { + "name": "issuer.example", + "token_type": 2, + "keys": [ + "30820152303d06092a864886f70d01010a3030a00d300b0609608648016503040202a11a301806092a864886f70d010108300b0609608648016503040202a2030201300382010f003082010a0282010100cb1aed6b6a95f5b1ce013a4cfcab25b94b2e64a23034e4250a7eab43c0df3a8c12993af12b111908d4b471bec31d4b6c9ad9cdda90612a2ee903523e6de5a224d6b02f09e5c374d0cfe01d8f529c500a78a2f67908fa682b5a2b430c81eaf1af72d7b5e794fc98a3139276879757ce453b526ef9bf6ceb99979b8423b90f4461a22af37aab0cf5733f7597abe44d31c732db68a181c6cbbe607d8c0e52e0655fd9996dc584eca0be87afbcd78a337d17b1dba9e828bbd81e291317144e7ff89f55619709b096cbb9ea474cead264c2073fe49740c01f00e109106066983d21e5f83f086e2e823c879cd43cef700d2a352a9babd612d03cad02db134b7e225a5f0203010001" + ] + } + ], + "signers": [ + "d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d356", + "2a8287b15cf9da772d527432bb5468a927a65746b1018b8c295c427b5a34c93c" + ], + "players": { + "1": "077b90947e7a9f3b0bc23c352ea07057b502c9b70728025505b53548a4237820", + "2": "93a7863232559bda7ddfc9d7da8033632ecc4cfeec25e03d6b2bee63e47e9084", + "3": "a4f7d399487ea03932ccae38cc687b9330cf12fe5f8fdd1a0b53befc90f36a39", + "11": "60ef283a69a3cfc38a785886ba8c8e3cd1d76c4d1d5ee23bb51559cad82bb40c", + "12": "19c3fed060d13a80c5bea6096a336cbe22497798912abb27c7c8edc8760fc1e3", + "21": "b38061e5837e743faff21e534f226cdf34f186675bc6b0bac1f6483dbbf2eeee", + "22": "91d542e13ec9188131b12de8c5f6c9e39ea865e6de855503ff3d5ab9c2086f3f", + "23": "634d08ef5b8e7e524bf1910e9869c1023c1e7dfc79a1d0422c7ab02c471a7bf1", + "24": "22837459cdac2d2b15fd13e52b0a36c0f9895a6738f46b19a2954f29b41d4d19", + "25": "a826da85ecd14b7396ae4bb74162dc510d82067623dcbbf5f4614d9652c838f2", + "26": "b971161e6f8753f8146f7e75e913943c4b7190b8c2f2b36c794bcd31e619e5a4", + "31": "6e6f0d61db1bed7bfaf01088224317706db3f45f9e3819302d89829adc8f9b48", + "32": "5b40e268c3ab78910157a9f7af1cd5f150a5cd75eb09cb1a641680d935fe4bf3", + "41": "5e5ed077b4b876f9853c7e460a83b2156e5b82420af023ccb91afe103a0753fa" + }, + "vectors": [ + { + "name": "hls_stitched", + "about": "two viewers' personalised HLS playlists with ads stitched in, each ad under an EXT-X-DATERANGE tag that carries its AdServingId in X-AD-ID; a pod-input record per viewer, priced from the VAST response; viewer 2 left before its second ad, whose segments had been fetched ahead of the break and which was never presented; and a control of each record and claim check", + "manifests": [ + "hls_stitched.viewer1.m3u8", + "hls_stitched.viewer2.m3u8" + ], + "responses": [ + "hls_stitched.viewer1.vast.xml", + "hls_stitched.viewer2.vast.xml" + ], + "receipts": [], + "bound": [], + "records": [ + "57504931010156645ebb9462ddb09e26cff1e7e8447f09627265616b2d3132303075000001cf3c3d8941beeb35865743f3398478aaebe74a403b786a76299d3b90afdb1bfb0107582d41442d494455534402000000000000000000000000000101000000000200b195172b8234e8dc41063a320853260c9cf5924e7d835d308cfe2349f2da6810087372762d76312d30087372762d76312d30055549442d411072656769737472792e6578616d706c656b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f983a0000a025260000000000010000000000000000000000000000000000000000000000000000000000000000019651875bac4a32151061316fca8beb19063bf0df0fb17b31851592e00f4c6cf6087372762d76312d31087372762d76312d31055549442d421072656769737472792e6578616d706c654bbe86d73166f8e342b72f079d4ce024b629c8eb91441e1b657ce508f893c403983a000040771b0000000000010000000000000000000000000000000000000000000000000000000000000000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d356e7a13f3e8103a2e6880e511cfc05dbc2ad582f74832ae2e013f8e1ab49804de8f08e8ab6fc04e64a59d6c8591ce75225abd7cdce445f45423f2e8a7a2cf49001", + "5750493101010f50ecce4cb883245212478634405cf809627265616b2d31323030750000010bf4c17e230a65bc3625b3a478624417a88def83127307396e417ec700895b290107582d41442d494455534402000000000000000000000000000101000000000200dd7ba1e2fc373e8f5c9fad750699f500a3a026a093f2735e6b5281c5dccb2726087372762d76322d30087372762d76322d30055549442d431072656769737472792e6578616d706c652f1672bb5a483ee85fb425b728a8433b3dcac2e9e357ef9d2a9eb8c32537afae983a0000c0c62d0000000000010000000000000000000000000000000000000000000000000000000000000000010d81873406d07a0d71369783b2d498d9589f10dbdca5f74b854e51809368d1ea087372762d76322d31087372762d76322d31055549442d421072656769737472792e6578616d706c654bbe86d73166f8e342b72f079d4ce024b629c8eb91441e1b657ce508f893c403983a000040771b0000000000010000000000000000000000000000000000000000000000000000000000000000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d35657e70fa2e13e4e331a131517d6318c174e93a2c697e4e07facfc13ac4df9d8447b6ff3b91d76e0ceb43608df0877d3139a0f721ab74382dd07028a7e08e98200", + "5750493101014c2683757dfeb3d06193c157084f5d4f09627265616b2d3132303075000001cf3c3d8941beeb35865743f3398478aaebe74a403b786a76299d3b90afdb1bfb0107582d41442d4944555344020000000000000000000000000001010000000001003285a54bde955238198df7545bb8e99cdd070b31a8ac5039ecfdb39bd6763a5e087372762d76352d30087372762d76312d30055549442d411072656769737472792e6578616d706c656b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f983a0000a0252600000000000100000000000000000000000000000000000000000000000000000000000000004e9a26bc29f4b406968d8f2b7aaf190aecdfa47ff01654322f269b8c2ae166f3197d08ef73578d5351e2691e4334c968305cf8843df7a9f4f06766b94ff3cc4f8a99eafba8e81d43d4a8951fa8a369db7167ff8fcdfb302d06c6c156ce898503", + "5750493101018b914fea67b025272b94e5841f018e9509627265616b2d3132303075000001cf3c3d8941beeb35865743f3398478aaebe74a403b786a76299d3b90afdb1bfb0107582d41442d4944555344020000000000000000000000000001010000000001006f8e0981f94244a4c2bbeb2a1d5bd406100c24d1ae9aa951be85e7049090ccaa087372762d76312d30087372762d76312d30055549442d411072656769737472792e6578616d706c656b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f983a000040ac270000000000010000000000000000000000000000000000000000000000000000000000000000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d356322f11a92370b9eae290935346150b40636d405b3945e80d197b6e19aa70498736dee4c50c94955862ba532ecf3dcb785f25cbc9d1742983b1871717abc4e000", + "57504931010156645ebb9462ddb09e26cff1e7e8447f0f627265616b2d3132302d616761696e3075000001cf3c3d8941beeb35865743f3398478aaebe74a403b786a76299d3b90afdb1bfb0107582d41442d494455534402000000000000000000000000000101000000000200b195172b8234e8dc41063a320853260c9cf5924e7d835d308cfe2349f2da6810087372762d76312d30087372762d76312d30055549442d411072656769737472792e6578616d706c656b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f983a0000a025260000000000010000000000000000000000000000000000000000000000000000000000000000019651875bac4a32151061316fca8beb19063bf0df0fb17b31851592e00f4c6cf6087372762d76312d31087372762d76312d31055549442d421072656769737472792e6578616d706c654bbe86d73166f8e342b72f079d4ce024b629c8eb91441e1b657ce508f893c403983a000040771b0000000000010000000000000000000000000000000000000000000000000000000000000000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d35644d3e0ff8ddff6674f916f30a3cac7ad470e5526c5bc6efe47507911035a0a979f5b25cf6c2a7537785e766e1a1f7844e26973d4a931403c23159dfd1c8ca205", + "57504931010194ea7af4ab6462a9139e80112a5d66bf09627265616b2d3132303075000001cf3c3d8941beeb35865743f3398478aaebe74a403b786a76299d3b90afdb1bfb0107582d41442d494455534402000000000000000000000000000101000000000100bffa7d2c74419d4383f5888a2f743f25420ce43eec3e57e3f51b7cd1a2cedc26087372762d76392d30087372762d76312d30055549442d5a1072656769737472792e6578616d706c656b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f983a0000a025260000000000010000000000000000000000000000000000000000000000000000000000000000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d3562d96c329e500941a6db7cdb68ce86923f05a437c2966e193c069b7bbd3e84b0642345fb9a8221871ec8cc60a3100b7f267357131e8867519da7a55845073860b", + "5750493101011b2c428f9899286c2c8520bb994c39ea09627265616b2d3132303075000001cf3c3d8941beeb35865743f3398478aaebe74a403b786a76299d3b90afdb1bfb0107582d41442d494455534402000000000000000000000000000000000000000200b195172b8234e8dc41063a320853260c9cf5924e7d835d308cfe2349f2da6810087372762d76312d30087372762d76312d30055549442d411072656769737472792e6578616d706c656b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f983a0000a025260000000000010000000000000000000000000000000000000000000000000000000000000000019651875bac4a32151061316fca8beb19063bf0df0fb17b31851592e00f4c6cf6087372762d76312d31087372762d76312d31055549442d421072656769737472792e6578616d706c654bbe86d73166f8e342b72f079d4ce024b629c8eb91441e1b657ce508f893c403983a000040771b0000000000010000000000000000000000000000000000000000000000000000000000000000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d356bb3905bcbca26c1c2ddbebda6041152f24e5c7489f38436bea340fd9a3ca5ca790421114f095fa6075303ee0493516a7c8ca5d3bf82329281f38f39648314009", + "575049310101b72312d9edc9c63f03e0855ab0750a3709627265616b2d3132303075000001cf3c3d8841beeb35865743f3398478aaebe74a403b786a76299d3b90afdb1bfb0107582d41442d494455534402000000000000000000000000000101000000000100cd139f1158fe12f5d1da06f2e78fae01c70403c56befab6b62e291eae2f437cc087372762d76382d30087372762d76312d30055549442d411072656769737472792e6578616d706c656b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f983a0000a025260000000000010000000000000000000000000000000000000000000000000000000000000000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d356444e4aa5a2a3cbca1a5ebc748597a12d67dea37df5f52a80422d0b8ea61de923d31a60924c89825219de14baceb6070691c507941d69c1b273cec5b26779850f" + ], + "record_about": [ + "viewer 1's break: ad-a and ad-b, priced from the response's Pricing at CPM 25.00 and 18.00 USD", + "viewer 2's break: ad-c and ad-b; ad-b's segments were requested ahead of the break, and viewer 2 left before it", + "a record signed by a key the verifier does not accept", + "a record whose ad-a is priced 2 600 000 µ where the response's Pricing gives 2 500 000", + "viewer 1's decision recorded a second time for the same markers", + "a record naming a universal creative identifier the response's ad does not carry", + "a record whose impressions would count without a claim", + "a record with one bit of its body flipped after signing" + ], + "claims": [ + "575053310101cf8a363de2f29da94143204903f9aa14076a960ffbd1c5ad04dddb330f61f78c56645ebb9462ddb09e26cff1e7e8447f6b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f077b90947e7a9f3b0bc23c352ea07057b502c9b70728025505b53548a4237820011027ffffffff2300070076312d61642d301800323032362d31302d32355431323a30323a30302e3030305a087372762d76312d300400912c17fb35083738d6eb0bf29548911111b41da0a5cb6b512ac98f17dcf91f21d3e30b815bc076686aba9bcea30afd9caa7c41a72ef35bfb9a98c6869d1e12f7c31ec984541ddb7ff258804c0fb0e9b66e94fe6528fb5ee1ba25a70144407063202266273b25c5dd444eab6c903ad7438cd19f5bcace495a941e97e34184fed9010000000000983a00001027010000458ccbf9285a44351d2253b0fb9867630c43f6575a3bbc3efbd155d612c089c5300409282fcd46eea4ae00ac378ec44f3728d14287820ca4acc6592a1cd67b09", + "575053310101cf8a363de2f29da94143204903f9aa14076a960ffbd1c5ad04dddb330f61f78c56645ebb9462ddb09e26cff1e7e8447f4bbe86d73166f8e342b72f079d4ce024b629c8eb91441e1b657ce508f893c403077b90947e7a9f3b0bc23c352ea07057b502c9b70728025505b53548a4237820011027ffffffff2300070076312d61642d311800323032362d31302d32355431323a30323a31352e3030305a087372762d76312d310300bea9d70662ff2ffaa4c1f8fef5dfed6b3dfba0e28664eb9602b87a5c7f9d43dd7ad95f226ee22f864b1818023079aab7a3a38303c3aa028edd992db97b09c08fd4236e07b3ad5d7ca4aa508317c25e41968a9a7650bf7905ec288f94434adb8c030000000000a00f0000102701a00f0000a00f0000701701401f0000b20c0000b80b010000b003b7fe8bcf6078ffa2478e1154190829d4397cd0b11971ffa12ca922f0164db8ee1fc703ef1bd7b186def147efae8ddf12a18499d27837f68520a074e9fd0a", + "575053310101715f927defe392611515f513064f8b71d7b1d95b262347d2bf91da4275b5bab70f50ecce4cb883245212478634405cf82f1672bb5a483ee85fb425b728a8433b3dcac2e9e357ef9d2a9eb8c32537afae93a7863232559bda7ddfc9d7da8033632ecc4cfeec25e03d6b2bee63e47e9084011027ffffffff2300070076322d61642d301800323032362d31302d32355431323a30323a30302e3030305a087372762d76322d300300ee9c1789553becff0906a1f2513fa7bb542e3518fc8a3594ea5d33bc44b02a9d0d3581e40066cd47665fcf783e7b9b7cbbc0c586e227cf06b759d68f995a1aca95e9337a233c5d3ba4d2d346e0dd219a8cf83aec52c6f3aad711a57941baac78010000000000983a00001027010000a62d6f5ab7decb5905a82ed145d7c1d167a68cefd67ee4149e88cebf0c7841f4ad63e74566d3da47570cae4ccd9bfa0d068802a202ef0c81bc9957a4537dd901", + "575053310101cf8a363de2f29da94143204903f9aa14076a960ffbd1c5ad04dddb330f61f78c56645ebb9462ddb09e26cff1e7e8447f6b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f077b90947e7a9f3b0bc23c352ea07057b502c9b70728025505b53548a4237820011027ffffffff2300070076312d61642d301800323032362d31302d32355431323a30323a30302e3030305a087372762d76312d300400912c17fb35083738d6eb0bf29548911111b41da0a5cb6b512ac98f17dcf91f21d3e30b815bc076686aba9bcea30afd9caa7c41a72ef35bfb9a98c6869d1e12f7c31ec984541ddb7ff258804c0fb0e9b66e94fe6528fb5ee1ba25a70144407063202266273b25c5dd444eab6c903ad7438cd19f5bcace495a941e97e34184fed9010000000000983a00001027010000458ccbf9285a44351d2253b0fb9867630c43f6575a3bbc3efbd155d612c089c5300409282fcd46eea4ae00ac378ec44f3728d14287820ca4acc6592a1cd67b09", + "575053310101cf8a363de2f29da94143204903f9aa14076a960ffbd1c5ad04dddb330f61f78c56645ebb9462ddb09e26cff1e7e8447f6b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3fa4f7d399487ea03932ccae38cc687b9330cf12fe5f8fdd1a0b53befc90f36a39011027ffffffff2300070076312d61642d301800323032362d31302d32355431323a30323a30302e3030305a087372762d76312d300400912c17fb35083738d6eb0bf29548911111b41da0a5cb6b512ac98f17dcf91f21d3e30b815bc076686aba9bcea30afd9caa7c41a72ef35bfb9a98c6869d1e12f7c31ec984541ddb7ff258804c0fb0e9b66e94fe6528fb5ee1ba25a70144407063202266273b25c5dd444eab6c903ad7438cd19f5bcace495a941e97e34184fed9010000000000983a000010270100004355ff3ff20f1d38afb39c84644ea142d230d14972a2e8203835260cb819180c696f93e3371247ed264627e7f8d79a81b2d80804a4bd2719bf03cd29cb87cf03", + "575053310101cf8a363de2f29da94143204903f9aa14076a960ffbd1c5ad04dddb330f61f78cc5d6a92ba2c40c3e88aea4a0c80a1ad36b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3fa4f7d399487ea03932ccae38cc687b9330cf12fe5f8fdd1a0b53befc90f36a39011027ffffffff2300070076312d61642d301800323032362d31302d32355431323a30323a30302e3030305a087372762d76312d300400912c17fb35083738d6eb0bf29548911111b41da0a5cb6b512ac98f17dcf91f21d3e30b815bc076686aba9bcea30afd9caa7c41a72ef35bfb9a98c6869d1e12f7c31ec984541ddb7ff258804c0fb0e9b66e94fe6528fb5ee1ba25a70144407063202266273b25c5dd444eab6c903ad7438cd19f5bcace495a941e97e34184fed9010000000000983a000010270100003b48da026712c5345baca863290f3565ed21a30e175c9533c2e943bd783e54f2d9d44c7a00413d5afba695a8b902ee4691bdbe12dbbfbb8b379efdd650c61b03", + "575053310101cf8a363de2f29da94143204903f9aa14076a960ffbd1c5ad04dddb330f61f78c56645ebb9462ddb09e26cff1e7e8447f6b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f077b90947e7a9f3b0bc23c352ea07057b502c9b70728025505b53548a4237820011027ffffffff25000900627265616b2d3132301800323032362d31302d32355431323a30323a30302e3030305a087372762d76312d300400912c17fb35083738d6eb0bf29548911111b41da0a5cb6b512ac98f17dcf91f21d3e30b815bc076686aba9bcea30afd9caa7c41a72ef35bfb9a98c6869d1e12f7c31ec984541ddb7ff258804c0fb0e9b66e94fe6528fb5ee1ba25a70144407063202266273b25c5dd444eab6c903ad7438cd19f5bcace495a941e97e34184fed9010000000000983a0000102701000028cd60a33170fdd3e148fe9e9b3be6da7456fee7696984dc1000aa00c6c53dacb41864a96483c9c2136df707b293cad8109340246b52949c1ddd5cfc9d5c580b", + "575053310101144659b48f342d02b9298907ab32fcf0479ac9c99a0d293c7c2ebf8df313dd1256645ebb9462ddb09e26cff1e7e8447f4bbe86d73166f8e342b72f079d4ce024b629c8eb91441e1b657ce508f893c403077b90947e7a9f3b0bc23c352ea07057b502c9b70728025505b53548a4237820011027ffffffff2300070076312d61642d311800323032362d31302d32355431323a30323a31352e3030305a087372762d76312d310400bea9d70662ff2ffaa4c1f8fef5dfed6b3dfba0e28664eb9602b87a5c7f9d43dd7ad95f226ee22f864b1818023079aab7a3a38303c3aa028edd992db97b09c08fd4236e07b3ad5d7ca4aa508317c25e41968a9a7650bf7905ec288f94434adb8c01eb03276ebac720029d3e9593bd0f1f1c2e0c7e7b584062b144be66261627ba010000000000983a0000102701000090fcb3e057ac55ce5ac766ea47e5403a20e6b182bd2b03e58b41b6b7652b4b12791d80ea98d3acea6a863265c72255d82fffb46ae448f704b9894c4364396f08", + "575053310101715f927defe392611515f513064f8b71d7b1d95b262347d2bf91da4275b5bab756645ebb9462ddb09e26cff1e7e8447f6b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f077b90947e7a9f3b0bc23c352ea07057b502c9b70728025505b53548a4237820011027ffffffff2300070076312d61642d301800323032362d31302d32355431323a30323a30302e3030305a087372762d76312d300400912c17fb35083738d6eb0bf29548911111b41da0a5cb6b512ac98f17dcf91f21d3e30b815bc076686aba9bcea30afd9caa7c41a72ef35bfb9a98c6869d1e12f7c31ec984541ddb7ff258804c0fb0e9b66e94fe6528fb5ee1ba25a70144407063202266273b25c5dd444eab6c903ad7438cd19f5bcace495a941e97e34184fed9010000000000983a00001027010000922f294bcb2007798c8fafc49dfc4e35cface0e7288d4127c449e60210bb08b38c47bcd9794b6778b5ac68f48efc62e4bcbde9d76d028d74ac07c5fe713f190f", + "575053310101cf8a363de2f29da94143204903f9aa14076a960ffbd1c5ad04dddb330f61f78c56645ebb9462ddb09e26cff1e7e8447f4bbe86d73166f8e342b72f079d4ce024b629c8eb91441e1b657ce508f893c403077b90947e7a9f3b0bc23c352ea07057b502c9b70728025505b53548a4237820011027ffffffff2300070076312d61642d311800323032362d31302d32355431323a30323a31352e3030305a087372762d76312d310400bea9d70662ff2ffaa4c1f8fef5dfed6b3dfba0e28664eb9602b87a5c7f9d43dd7ad95f226ee22f864b1818023079aab7a3a38303c3aa028edd992db97b09c08fd4236e07b3ad5d7ca4aa508317c25e41968a9a7650bf7905ec288f94434adb8c01eb03276ebac720029d3e9593bd0f1f1c2e0c7e7b584062b144be66261627ba010000000000983a0000102701000055a9818d339ac3b8d6c5c89eee4357df3b73efcc0d4968bd444d0fd5d8951ab54044de7ccfc7e944e5797215f1287519a61f3d71bbf2287d5829d241545f89", + "575053310101cf8a363de2f29da94143204903f9aa14076a960ffbd1c5ad04dddb330f61f78c56645ebb9462ddb09e26cff1e7e8447f6b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f077b90947e7a9f3b0bc23c352ea07057b502c9b70728025505b53548a4237820011027ffffffff2300070076312d61642d301800323032362d31302d32355431323a30323a30302e3030305a087372762d76312d300300912c17fb35083738d6eb0bf29548911111b41da0a5cb6b512ac98f17dcf91f21c31ec984541ddb7ff258804c0fb0e9b66e94fe6528fb5ee1ba25a70144407063202266273b25c5dd444eab6c903ad7438cd19f5bcace495a941e97e34184fed9020000000000a00f0000102701401f0000581b0000102701000041075d191248363fd97549d431326208fa6098b76570a520f358b1d2055b25ef9180b4380d9c110a9265e250d219a433d7438ce3fd358932f8e87aff9cfc7a0a", + "575053310101cf8a363de2f29da94143204903f9aa14076a960ffbd1c5ad04dddb330f61f78c56645ebb9462ddb09e26cff1e7e8447f6b193afc3c2c63ca153cf14d25cea5773ebe158f8f73a7df4b55c09858495c3f077b90947e7a9f3b0bc23c352ea07057b502c9b70728025505b53548a4237820011027ffffffff2300070076312d61642d301800323032362d31302d32355431323a30323a30302e3030305a087372762d76312d300400912c17fb35083738d6eb0bf29548911111b41da0a5cb6b512ac98f17dcf91f21d3e30b815bc076686aba9bcea30afd9caa7c41a72ef35bfb9a98c6869d1e12f7c31ec984541ddb7ff258804c0fb0e9b66e94fe6528fb5ee1ba25a70144407063202266273b25c5dd444eab6c903ad7438cd19f5bcace495a941e97e34184fed9010000000000983a00001027010000458ccbf9285a44351d2353b0fb9867630c43f6575a3bbc3efbd155d612c089c5300409282fcd46eea4ae00ac378ec44f3728d14287820ca4acc6592a1cd67b09" + ], + "claim_about": [ + "viewer 1, ad-a: in view and audible from start to end", + "viewer 1, ad-b: 8 s at more than half its pixels, and 11.25 s of 15, the third quartile", + "viewer 2, ad-c: in view and audible from start to end", + "viewer 1's ad-a claim given a second time", + "a claim naming viewer 1's session, signed by viewer 3's session key", + "viewer 3 claims ad-a of viewer 1's break, decided for viewer 1's session alone", + "a claim naming the break's own date range, which no record names as an ad", + "viewer 1, ad-b, naming a manifest the verifier does not hold", + "viewer 1, ad-a, naming viewer 2's playlist, which carries no such date range", + "a claim short of its last byte", + "viewer 1, ad-a, signed: a skip from 4 s to 8 s with no resumption stated, which breaks ad-presentation §5.4 rule 6", + "viewer 1, ad-a, one bit of the signature flipped" + ], + "record_facts": [ + { + "digest": "9497cbba7b5f37ed8addefc2359c89073aeba88d689898b6b501e76043dec2ff", + "verdict": "accepted", + "response_checked": true, + "ads": [ + { + "slot": 0, + "own_claims": [ + 0 + ], + "presentations": 1, + "verdict": "presented", + "quartiles": [ + 1, + 1, + 1, + 1, + 1 + ], + "presented_spend_micros": 2500000, + "stated_quartile": null + }, + { + "slot": 1, + "own_claims": [ + 1 + ], + "presentations": 1, + "verdict": "presented", + "quartiles": [ + 1, + 1, + 1, + 1, + 0 + ], + "presented_spend_micros": 1800000, + "stated_quartile": null + } + ], + "presented_spend_micros": 4300000, + "stated_spend_micros": null + }, + { + "digest": "273231a1646371b68d8ce0e8488ff98652eb57b9422bb53867e11e68e6670a2b", + "verdict": "accepted", + "response_checked": true, + "ads": [ + { + "slot": 0, + "own_claims": [ + 2 + ], + "presentations": 1, + "verdict": "presented", + "quartiles": [ + 1, + 1, + 1, + 1, + 1 + ], + "presented_spend_micros": 3000000, + "stated_quartile": null + }, + { + "slot": 1, + "own_claims": [], + "presentations": 0, + "verdict": "claim_absent", + "quartiles": [ + 0, + 0, + 0, + 0, + 0 + ], + "presented_spend_micros": 0, + "stated_quartile": null + } + ], + "presented_spend_micros": 3000000, + "stated_spend_micros": null + }, + { + "digest": "132db2cdb098ad11d765306b01b0ebb82b438dcfc11387d329875d5b2552beec", + "verdict": "pod_signer_unknown" + }, + { + "digest": "445e0e910c9cde811093d2045c3d2210a3b7896026f9796b76fb676f2ea82fd7", + "verdict": "pod_response_mismatch" + }, + { + "digest": "0ee75031e676891cb779b599f6a099b1bd4165fa44ca9c821f01ff2693c85afa", + "verdict": "pod_duplicate" + }, + { + "digest": "a85a651e9901d00344bdf9baaa9a6155ed5af360f6e3573620178a58e8db4585", + "verdict": "pod_response_mismatch" + }, + { + "digest": null, + "verdict": "pod_input_malformed" + }, + { + "digest": "4295839b79c98a62e289fabd6a646ee34df797a3def35c51609d27166c849328", + "verdict": "bad_signature" + } + ], + "claim_facts": [ + { + "digest": "20dc125d60b8decfeade65e5c1c32e26eb336ee9df984ddc483eba1192d2b9b6", + "verdict": "presented", + "record": 0, + "slot": 0, + "quartile": 5, + "run_ms": 15000, + "viewable_ms": 15000 + }, + { + "digest": "4ea62ac07cc0180f316431cf466028738cf0c5e7d1b80c1d495cf299298e70b1", + "verdict": "presented", + "record": 0, + "slot": 1, + "quartile": 4, + "run_ms": 8000, + "viewable_ms": 8000 + }, + { + "digest": "7bbe239cd63ef376c5a37435e49684452a3c5f6832ce80831044e3d14cb5ded4", + "verdict": "presented", + "record": 1, + "slot": 0, + "quartile": 5, + "run_ms": 15000, + "viewable_ms": 15000 + }, + { + "digest": "20dc125d60b8decfeade65e5c1c32e26eb336ee9df984ddc483eba1192d2b9b6", + "verdict": "claim_duplicate", + "record": 0, + "slot": 0, + "quartile": null, + "run_ms": null, + "viewable_ms": null + }, + { + "digest": "9d59dbd435a72ceba0a9a8cfb153c35bb08c733c427aae2e85e3618203a415c3", + "verdict": "claim_key_unbound", + "record": null, + "slot": null, + "quartile": null, + "run_ms": null, + "viewable_ms": null + }, + { + "digest": "8f5a06d4560697315ad4382f7d41f5868262a4dab411bf484cff87238d6d6ae1", + "verdict": "claim_session_unbound", + "record": null, + "slot": null, + "quartile": null, + "run_ms": null, + "viewable_ms": null + }, + { + "digest": "bd1fb8b6da331383481baf039c6289fcace9c158b4c526943c3a4111c8d74ddf", + "verdict": "claim_no_decision", + "record": null, + "slot": null, + "quartile": null, + "run_ms": null, + "viewable_ms": null + }, + { + "digest": "0d5c4afadab80f49942ada75e56be264b00a150df63269d4d552eb959296a594", + "verdict": "claim_manifest_unknown", + "record": 0, + "slot": 1, + "quartile": null, + "run_ms": null, + "viewable_ms": null + }, + { + "digest": "4035ae33156c53ac5e6bffee450407da9df456fd231c04448051d77bfc60ee9e", + "verdict": "claim_marker_absent", + "record": 0, + "slot": 0, + "quartile": null, + "run_ms": null, + "viewable_ms": null + }, + { + "digest": null, + "verdict": "stitched_claim_malformed", + "record": null, + "slot": null, + "quartile": null, + "run_ms": null, + "viewable_ms": null + }, + { + "digest": null, + "verdict": "stitched_claim_malformed", + "record": null, + "slot": null, + "quartile": null, + "run_ms": null, + "viewable_ms": null + }, + { + "digest": "20dc125d60b8decfeade65e5c1c32e26eb336ee9df984ddc483eba1192d2b9b6", + "verdict": "bad_signature", + "record": null, + "slot": null, + "quartile": null, + "run_ms": null, + "viewable_ms": null + } + ], + "spend": { + "USD": 7300000 + } + }, + { + "name": "hls_interstitial", + "about": "one shared HLS playlist with an interstitial date range whose asset list each viewer is served; a pod-input record per viewer, signed by a proxy in front of the ad server, under the cross-media rule with an audible run and a device attestation; one ad priced by a winning bid", + "manifests": [ + "hls_interstitial.m3u8", + "hls_interstitial.assets.json" + ], + "responses": [ + "hls_interstitial.vast.xml" + ], + "receipts": [], + "bound": [ + "hls_interstitial.bid.json" + ], + "records": [ + "5750493101025b419e4f1f60e68ef82affaff4a5b7f609627265616b2d3330303075000001ab44d863f64ba08b1d0e18d40d6a62210aa6ce058300c977fd97377bc20b2e8b02005553440200000000000000000000000000070202000e006973737565722e6578616d706c650200091655bb285b10b65907042f3384ede7b15f74f038c5e1bb8e7872b543c21d2a5b68747470733a2f2f6164732e6578616d706c652f632f366231373936666338336166623261386233363731323039373730353834353464663133356236313430613938663536313530656630623331666164376237612e6d3375380561642d6931065549442d49311072656769737472792e6578616d706c656b1796fc83afb2a8b367120977058454df135b6140a98f56150ef0b31fad7b7a983a0000401640000000000002601a25050ae572bed11bf3c20a80b8508f0f57364de89f98fb9ae03b34122f13013b7442f6bcf18af28c6b0503cde33338f7a392c6a257aa89ac6d78b6688352bf5b68747470733a2f2f6164732e6578616d706c652f632f333064303431623461376566633337316137656565366432633466616239323030663463633231623566333231353265353039323165356438333233306239332e6d3375380561642d6932065549442d49321072656769737472792e6578616d706c6530d041b4a7efc371a7eee6d2c4fab9200f4cc21b5f32152e50921e5d83230b93983a000080841e00000000000100000000000000000000000000000000000000000000000000000000000000002a8287b15cf9da772d527432bb5468a927a65746b1018b8c295c427b5a34c93c190868449219be9c5a5fd95dbff8366bb2fbb82fdc507a6416f6376e0077fd12bc62f99ac2f2a9cbe0a6c7ea4b83a45b9f0781f2a1111c9808598e4b467ff90b", + "57504931010246c404f9214eea9b7980a5687eb9096909627265616b2d3330303075000001ab44d863f64ba08b1d0e18d40d6a62210aa6ce058300c977fd97377bc20b2e8b02005553440200000000000000000000000000070202000e006973737565722e6578616d706c650200091655bb285b10b65907042f3384ede7b15f74f038c5e1bb8e7872b543c21d2a5b68747470733a2f2f6164732e6578616d706c652f632f366231373936666338336166623261386233363731323039373730353834353464663133356236313430613938663536313530656630623331666164376237612e6d3375380561642d6931065549442d49311072656769737472792e6578616d706c656b1796fc83afb2a8b367120977058454df135b6140a98f56150ef0b31fad7b7a983a0000401640000000000002601a25050ae572bed11bf3c20a80b8508f0f57364de89f98fb9ae03b34122f13013b7442f6bcf18af28c6b0503cde33338f7a392c6a257aa89ac6d78b6688352bf5b68747470733a2f2f6164732e6578616d706c652f632f333064303431623461376566633337316137656565366432633466616239323030663463633231623566333231353265353039323165356438333233306239332e6d3375380561642d6932065549442d49321072656769737472792e6578616d706c6530d041b4a7efc371a7eee6d2c4fab9200f4cc21b5f32152e50921e5d83230b93983a000080841e00000000000100000000000000000000000000000000000000000000000000000000000000002a8287b15cf9da772d527432bb5468a927a65746b1018b8c295c427b5a34c93c462cbd2f29c09f84e353e7a6797564619c5cc7e560e2191330265dd66dd67db714256cdab1876710f93213149d3676916176e444f99e6c794b451dfacc51ae07" + ], + "record_about": [ + "viewer 11's interstitial: ad-i1 at a winning bid's price, its bid document bound by digest, and ad-i2 at the response's CPM 20.00 USD", + "viewer 12's interstitial, the same decision" + ], + "claims": [ + "5750533101022df55303fe94314d23a8fc85fa0200384b58a02d6ea71d8696d7980e462a77e95b419e4f1f60e68ef82affaff4a5b7f66b1796fc83afb2a8b367120977058454df135b6140a98f56150ef0b31fad7b7a60ef283a69a3cfc38a785886ba8c8e3cd1d76c4d1d5ee23bb51559cad82bb40c021027ffffffff27000900627265616b2d3330301800323032362d31302d32355431323a30353a30302e3030305a00005b68747470733a2f2f6164732e6578616d706c652f632f366231373936666338336166623261386233363731323039373730353834353464663133356236313430613938663536313530656630623331666164376237612e6d33753803004b4b43423a097fadccde97ce3195f9e0b103a90da450128b158128a1b84c2a275532613ee736e9a1ddbf1036b9a8afdd8b447ec4b4a0cafc5bc58cbc76e74824dc0901e3efbd5a512795275e1854f93946d138d6e4e5fdefcbe516d30bb42fa7010000000000983a00001027016201000234b79687b039ddc74e85d568f7d36c136ddec0356495c1f5f33fadc73b5cf7822dec94e1f5ebe7e98514cb48c62f94165b34451f07b524734537916afc5fd2daca572f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd2708186cd3cc86e6a3144d686ca6c23678ab768e46ab9339e28a996a9296389becd4133791568f14f045f032ba46d258d9ffd02e4fa9cbdee9556846f1d7757cb08446a68edfa220a3cd2062276343d8c5cd910f159731eb2206c87cb143202b65bb4f1daf027edad4ea7000cf84372a61b44805677c68242879b1cdf81c7d5452e6ecc8baa8ec84ef343773ba5dac9a02184af312c41e1ae7e7b02a6825cd80d547f6f34799c860c2b6e672698b24816ec1ef101e93c8b44aa7cb91486e89706ee5827b95f1c651b2d2fc1f63368de6a76db84758116a8f34540c69aae31f1655ca6714cea734acc87d322e8ff993125563bac520f7d2e213edfa9142454f381ec324e8fbab8701eb1fc7667fe48a6ee3e5b4e42ff2d63088ef55212baf90dda74243d92e5d9972f0e511c583810a99f699e70721fbac65b60c34276d34f62b6c07", + "5750533101022df55303fe94314d23a8fc85fa0200384b58a02d6ea71d8696d7980e462a77e95b419e4f1f60e68ef82affaff4a5b7f630d041b4a7efc371a7eee6d2c4fab9200f4cc21b5f32152e50921e5d83230b9360ef283a69a3cfc38a785886ba8c8e3cd1d76c4d1d5ee23bb51559cad82bb40c021027ffffffff27000900627265616b2d3330301800323032362d31302d32355431323a30353a30302e3030305a01005b68747470733a2f2f6164732e6578616d706c652f632f333064303431623461376566633337316137656565366432633466616239323030663463633231623566333231353265353039323165356438333233306239332e6d33753803002b3af11a3fa068a4d364daf2f64674b457b1735df987762344e6d7f90b6003ca211282a57549ba20a9f3df0eca2c05743ef9b138e0e595547d435c39cfe83fe7d6f72c30e1b680929bb04696ea3175d8566623e5ce65fe33e69f76272bdbbe6e010000000000983a00001027016201000241f9f8c3e20a1f25375cc771088edd2b941187d2e7a9a43a5627638142e4d9b332acb8362865a6258f9debc00bd9c373efba6af8b9c00cf63e7fc629f16d06d4ca572f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd27087aad10aa5cd9d5165f1da296b088d71c92035e1f02b7abe7a280e8a071a3b94e43b93ec66a5c2e0d7a56703f3272a6272387332281a26e5e37878d4527a1157be40ce03199901c909e6b47718841a63fefb0fe1d29276e6a7f18b138c3b92ce7d63c70a2021fc1b444219cf338c479df5d090f2ad394d43ea2f17398b480368683d63b0fd8bbf694753add56a6f217a7e9881e5f28d8d2df671082377849f9ccb91e697acebe710fe98269835acba554e0164716d48d3f6383ee228fdf0c04645e620bec6b5a484bcd319f6bb5387ad4a53b8f4a35ce13d6717cdf684ba4968151ba1e1613ff35a5b8318f3ab07aa97cdbb2c568de4a04bf32a5adb2828d1f4b18c3e54c5f678dc2641e695d21a4885f3c2c7931a164ded2c0e02e6365264f0d08e24ede41031ad2d7210ccefbba973d6d5fc93c25ad8aba0b84ded2571bfb09", + "5750533101022df55303fe94314d23a8fc85fa0200384b58a02d6ea71d8696d7980e462a77e946c404f9214eea9b7980a5687eb909696b1796fc83afb2a8b367120977058454df135b6140a98f56150ef0b31fad7b7a19c3fed060d13a80c5bea6096a336cbe22497798912abb27c7c8edc8760fc1e3021027ffffffff27000900627265616b2d3330301800323032362d31302d32355431323a30353a30302e3030305a00005b68747470733a2f2f6164732e6578616d706c652f632f366231373936666338336166623261386233363731323039373730353834353464663133356236313430613938663536313530656630623331666164376237612e6d33753803004b4b43423a097fadccde97ce3195f9e0b103a90da450128b158128a1b84c2a275532613ee736e9a1ddbf1036b9a8afdd8b447ec4b4a0cafc5bc58cbc76e74824dc0901e3efbd5a512795275e1854f93946d138d6e4e5fdefcbe516d30bb42fa7010000000000983a00001027010000360692a5c8ba3a111cebe0ababdb29ae69b6c572824804d0701229e58d6f632c4b8bf8af4fc6d0da17bb29d546257bec83539a8f4a484ed94e552d9b3ca17402", + "5750533101022df55303fe94314d23a8fc85fa0200384b58a02d6ea71d8696d7980e462a77e946c404f9214eea9b7980a5687eb9096930d041b4a7efc371a7eee6d2c4fab9200f4cc21b5f32152e50921e5d83230b9319c3fed060d13a80c5bea6096a336cbe22497798912abb27c7c8edc8760fc1e3021027ffffffff27000900627265616b2d3330301800323032362d31302d32355431323a30353a30302e3030305a01005b68747470733a2f2f6164732e6578616d706c652f632f333064303431623461376566633337316137656565366432633466616239323030663463633231623566333231353265353039323165356438333233306239332e6d33753803002b3af11a3fa068a4d364daf2f64674b457b1735df987762344e6d7f90b6003ca211282a57549ba20a9f3df0eca2c05743ef9b138e0e595547d435c39cfe83fe7d6f72c30e1b680929bb04696ea3175d8566623e5ce65fe33e69f76272bdbbe6e010000000000983a00001027016201000241f9f8c3e20a1f25375cc771088edd2b941187d2e7a9a43a5627638142e4d9b332acb8362865a6258f9debc00bd9c373efba6af8b9c00cf63e7fc629f16d06d4ca572f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd27087aad10aa5cd9d5165f1da296b088d71c92035e1f02b7abe7a280e8a071a3b94e43b93ec66a5c2e0d7a56703f3272a6272387332281a26e5e37878d4527a1157be40ce03199901c909e6b47718841a63fefb0fe1d29276e6a7f18b138c3b92ce7d63c70a2021fc1b444219cf338c479df5d090f2ad394d43ea2f17398b480368683d63b0fd8bbf694753add56a6f217a7e9881e5f28d8d2df671082377849f9ccb91e697acebe710fe98269835acba554e0164716d48d3f6383ee228fdf0c04645e620bec6b5a484bcd319f6bb5387ad4a53b8f4a35ce13d6717cdf684ba4968151ba1e1613ff35a5b8318f3ab07aa97cdbb2c568de4a04bf32a5adb2828d1f4bb1508830190b7801204a264fca279ef4003809aaa5598013c10be32dcdafc92772ae23f96a3eacee08c3ac8d30a8add80cfa0fd81d51e130c59f81495799050a" + ], + "claim_about": [ + "viewer 11, ad-i1, on a television: full screen, audible, with a device attestation bound to this presentation", + "viewer 11, ad-i2: the same", + "viewer 12, ad-i1: full screen and audible, with no attestation", + "viewer 12, ad-i2: carrying viewer 11's token, whose challenge is another presentation's" + ], + "record_facts": [ + { + "digest": "16945e7b9a577c52087832db5d2861e5ee79c7a7de280359cca3c09881e0bf1c", + "verdict": "accepted", + "response_checked": true, + "ads": [ + { + "slot": 0, + "own_claims": [ + 0 + ], + "presentations": 1, + "verdict": "presented", + "quartiles": [ + 1, + 1, + 1, + 1, + 1 + ], + "presented_spend_micros": 4200000, + "stated_quartile": null + }, + { + "slot": 1, + "own_claims": [ + 1 + ], + "presentations": 1, + "verdict": "presented", + "quartiles": [ + 1, + 1, + 1, + 1, + 1 + ], + "presented_spend_micros": 2000000, + "stated_quartile": null + } + ], + "presented_spend_micros": 6200000, + "stated_spend_micros": null + }, + { + "digest": "9d38d365f538675ef851be87eb054817597bc48729bc95f34f3a6b42b3a81449", + "verdict": "accepted", + "response_checked": true, + "ads": [ + { + "slot": 0, + "own_claims": [ + 2 + ], + "presentations": 0, + "verdict": "attestation_absent", + "quartiles": [ + 0, + 0, + 0, + 0, + 0 + ], + "presented_spend_micros": 0, + "stated_quartile": null + }, + { + "slot": 1, + "own_claims": [ + 3 + ], + "presentations": 0, + "verdict": "attestation_invalid", + "quartiles": [ + 0, + 0, + 0, + 0, + 0 + ], + "presented_spend_micros": 0, + "stated_quartile": null + } + ], + "presented_spend_micros": 0, + "stated_spend_micros": null + } + ], + "claim_facts": [ + { + "digest": "ea6db9ba04c5091fb161000aaec31a5237562b99111f6e34129eca6a819fd612", + "verdict": "presented", + "record": 0, + "slot": 0, + "quartile": 5, + "run_ms": 15000, + "viewable_ms": 15000 + }, + { + "digest": "4a46de605434b991ce2ae18b0fc391019800ae8162fe75b189e8cc5469f56c4e", + "verdict": "presented", + "record": 0, + "slot": 1, + "quartile": 5, + "run_ms": 15000, + "viewable_ms": 15000 + }, + { + "digest": "4f850eca21ad3d40e7b205716df0fef4e2d8a3e705ddde5ceddc298ddc3bacdd", + "verdict": "attestation_absent", + "record": 1, + "slot": 0, + "quartile": 5, + "run_ms": 15000, + "viewable_ms": 15000 + }, + { + "digest": "16cff41194e1d4f60173c64f7b432fddec1557d288cd0ad8a050b5228637abe1", + "verdict": "attestation_invalid", + "record": 1, + "slot": 1, + "quartile": 5, + "run_ms": 15000, + "viewable_ms": 15000 + } + ], + "spend": { + "USD": 6200000 + } + }, + { + "name": "squeezeback", + "about": "a squeeze on a linear DASH channel: the program in a 1440 × 810 window in the bottom right corner and the ad in the L around it (live-compose §5.9), marked by an EventStream event that carries its AdServingId in messageData; one shared pod-input record, so each viewer whose claim verifies is one presented impression", + "manifests": [ + "squeezeback.mpd" + ], + "responses": [ + "squeezeback.vast.xml" + ], + "receipts": [], + "bound": [], + "records": [ + "575049310103000000000000000000000000000000000a73717565657a652d3431204e000001627aeb553278531a5ee7895a4b1298a78b9d0a988408980db1bcdb171e69f9e4010b6d6573736167654461746155534402038007380404a0052a030000000101000000000100a99e9deaa3abaa40818ba78b293a7641668cca0ad8c6f5455a2a25afc2a7aa8a087372762d73712d31087372762d73712d31055549442d531072656769737472792e6578616d706c657c60724768c078c2bc13f0afea11c340a6c9c0cd606407e503405f92a75132a5204e0000804f120000000000010000000000000000000000000000000000000000000000000000000000000000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d3560d7eb6951d38538f703c1e30cb7914ef92a7ba1e19e515c8f9a19bf7d916520c641e0d14f47d615dc9a79fc6553e4dff3241c58445f7ab6f6e5dbfbc9b926208" + ], + "record_about": [ + "the channel's break, every viewer shown it: ad-s at CPM 12.00 USD in a squeeze" + ], + "claims": [ + "575053310103737b592212d2137bff5ec737ad8445dbae07542df5b9614008bef208c13900526c488d71e7c725047143922b7ff0cab57c60724768c078c2bc13f0afea11c340a6c9c0cd606407e503405f92a75132a5b38061e5837e743faff21e534f226cdf34f186675bc6b0bac1f6483dbbf2eeee011711ffffffff250002007030180075726e3a6578616d706c653a73717565657a653a3230323601003102003431087372762d73712d310400d79c9b64605b5f6c256493d4a0698a1210d95df1ca557ac73fcaec4eeb76d7fd91daf7a0ea77e64ac03ad3ee0f27a914473a931e2bb39f08f97e81a8b237619c2861506d722cae31e49b4432990477decb59324c719b70112121c43a52dfa633b7404e121039cdf1501cd7970b9881a66bf65538be5df8656ba560bc08ac46c5010000000000204e000010270100008c7ba3e5a1bb1dafa25075b82485dde36b613e0278a26ca57b26c4df99c15c87aafa6f7a237491efb7ff9ac4884d10f39cfc49d3e87258cbbf747a5f67a54701", + "575053310103737b592212d2137bff5ec737ad8445dbae07542df5b9614008bef208c1390052f7661741a84240b33e013391fd003c767c60724768c078c2bc13f0afea11c340a6c9c0cd606407e503405f92a75132a591d542e13ec9188131b12de8c5f6c9e39ea865e6de855503ff3d5ab9c2086f3f011711ffffffff250002007030180075726e3a6578616d706c653a73717565657a653a3230323601003102003431087372762d73712d310400d79c9b64605b5f6c256493d4a0698a1210d95df1ca557ac73fcaec4eeb76d7fd91daf7a0ea77e64ac03ad3ee0f27a914473a931e2bb39f08f97e81a8b237619c2861506d722cae31e49b4432990477decb59324c719b70112121c43a52dfa633b7404e121039cdf1501cd7970b9881a66bf65538be5df8656ba560bc08ac46c5010000000000204e0000581b010000e340bd45a69b0e71b926733621e0335a560baf993c1427e598921993d81993ffea309f2c415157bfe744da9ac89daeea48355b0c5185a879369acd2a799cd80d", + "575053310103737b592212d2137bff5ec737ad8445dbae07542df5b9614008bef208c1390052c6d708b3aab50f3627ef106ffa8b268a7c60724768c078c2bc13f0afea11c340a6c9c0cd606407e503405f92a75132a5634d08ef5b8e7e524bf1910e9869c1023c1e7dfc79a1d0422c7ab02c471a7bf1011711ffffffff250002007030180075726e3a6578616d706c653a73717565657a653a3230323601003102003431087372762d73712d310200d79c9b64605b5f6c256493d4a0698a1210d95df1ca557ac73fcaec4eeb76d7fd91daf7a0ea77e64ac03ad3ee0f27a914473a931e2bb39f08f97e81a8b237619c0100000000001027000010270100007450d6522ac2822e09cd40cf7086aafd513994d9bfc4f489e327e688b9872495b771086de87477106754977936df18cb5a4ed8b9dbae786b85a8021b75104501", + "575053310103737b592212d2137bff5ec737ad8445dbae07542df5b9614008bef208c1390052c1a9a7bf07b591f93449e9304b4639cd7c60724768c078c2bc13f0afea11c340a6c9c0cd606407e503405f92a75132a522837459cdac2d2b15fd13e52b0a36c0f9895a6738f46b19a2954f29b41d4d19011027ffffffff250002007030180075726e3a6578616d706c653a73717565657a653a3230323601003102003431087372762d73712d310400d79c9b64605b5f6c256493d4a0698a1210d95df1ca557ac73fcaec4eeb76d7fd91daf7a0ea77e64ac03ad3ee0f27a914473a931e2bb39f08f97e81a8b237619c2861506d722cae31e49b4432990477decb59324c719b70112121c43a52dfa633b7404e121039cdf1501cd7970b9881a66bf65538be5df8656ba560bc08ac46c5010000000000204e0000102701000030883f4ff739f693111bb2ecf06dd53d444496ab8fd6a14c2c7c1f53938bbc915b89c168b4e073fba1d00a6e9f9bcdfec465264fa4e71665de56bedc2e2bd500", + "575053310103737b592212d2137bff5ec737ad8445dbae07542df5b9614008bef208c1390052d2e41e23411b16f4d8d9ab16d2aef05f7c60724768c078c2bc13f0afea11c340a6c9c0cd606407e503405f92a75132a5a826da85ecd14b7396ae4bb74162dc510d82067623dcbbf5f4614d9652c838f2011711ffffffff250002007030180075726e3a6578616d706c653a73717565657a653a3230323601003102003431087372762d73712d310400d79c9b64605b5f6c256493d4a0698a1210d95df1ca557ac73fcaec4eeb76d7fd91daf7a0ea77e64ac03ad3ee0f27a914473a931e2bb39f08f97e81a8b237619c2861506d722cae31e49b4432990477decb59324c719b70112121c43a52dfa633b7404e121039cdf1501cd7970b9881a66bf65538be5df8656ba560bc08ac46c5010000000000204e0000a00f010000d663435d30e881adbf9ed55905b65be2dcb3dee42a8289742e2e84f69d5cc05ab882763b4e99a697bba944ce916b2c2084a74b6a87234ce02987f88ce4425b0d", + "575053310103737b592212d2137bff5ec737ad8445dbae07542df5b9614008bef208c13900526c488d71e7c725047143922b7ff0cab57c60724768c078c2bc13f0afea11c340a6c9c0cd606407e503405f92a75132a5b38061e5837e743faff21e534f226cdf34f186675bc6b0bac1f6483dbbf2eeee011711ffffffff250002007030180075726e3a6578616d706c653a73717565657a653a3230323601003102003431087372762d73712d310400d79c9b64605b5f6c256493d4a0698a1210d95df1ca557ac73fcaec4eeb76d7fd91daf7a0ea77e64ac03ad3ee0f27a914473a931e2bb39f08f97e81a8b237619c2861506d722cae31e49b4432990477decb59324c719b70112121c43a52dfa633b7404e121039cdf1501cd7970b9881a66bf65538be5df8656ba560bc08ac46c5010000000000204e000010270100008c7ba3e5a1bb1dafa25075b82485dde36b613e0278a26ca57b26c4df99c15c87aafa6f7a237491efb7ff9ac4884d10f39cfc49d3e87258cbbf747a5f67a54701", + "575053310103737b592212d2137bff5ec737ad8445dbae07542df5b9614008bef208c13900520ddb9473ec63be785a169f84ca2451bb7c60724768c078c2bc13f0afea11c340a6c9c0cd606407e503405f92a75132a5b971161e6f8753f8146f7e75e913943c4b7190b8c2f2b36c794bcd31e619e5a4011711ffffffff250002007030180075726e3a6578616d706c653a73717565657a653a3230323601003102003431087372762d73712d310400d79c9b64605b5f6c256493d4a0698a1210d95df1ca557ac73fcaec4eeb76d7fd91daf7a0ea77e64ac03ad3ee0f27a914473a931e2bb39f08f97e81a8b237619cf63683db8abbe7d50b9964afeb259e78460d84431b35d51c2b665e07dbb5007db7404e121039cdf1501cd7970b9881a66bf65538be5df8656ba560bc08ac46c5010000000000204e00001027010000dd8551dc30a6bee013c7b36f30d7283a7bc68b85ccf26d904ea92dece91bdfd3fe308b350008c46b92c27c1662748c267f3682e6b43b15ccd38edac3fbf50608" + ], + "claim_about": [ + "viewer 21: the ad's region in view throughout", + "viewer 22: 70% of the ad's region in view throughout", + "viewer 23: left at 10 s of 20, the midpoint", + "viewer 24 states the whole picture as the ad's share, where the frame gives 4375 basis points", + "viewer 25: 40% of the ad's region in view, below the video rule's half", + "viewer 21's claim given a second time", + "viewer 26: a presented object's hash is not the creative's third object" + ], + "record_facts": [ + { + "digest": "6f3a8cd6d7e660219a0511f6d1ca18ae8b57e2446aec47e29284fc83db5f392b", + "verdict": "accepted", + "response_checked": true, + "ads": [ + { + "slot": 0, + "own_claims": [ + 0, + 1, + 2, + 3, + 4, + 6 + ], + "presentations": 3, + "verdict": "presented", + "quartiles": [ + 3, + 3, + 3, + 2, + 2 + ], + "presented_spend_micros": 3600000, + "stated_quartile": null + } + ], + "presented_spend_micros": 3600000, + "stated_spend_micros": null + } + ], + "claim_facts": [ + { + "digest": "21d55ff75cb90c3329e74ce183193cee67528235c848758b42c02878877b48cb", + "verdict": "presented", + "record": 0, + "slot": 0, + "quartile": 5, + "run_ms": 20000, + "viewable_ms": 20000 + }, + { + "digest": "680dd75de1d5a48e7b89b1fe0668b892db9a669a252f1c2b9f9db84723c93e4c", + "verdict": "presented", + "record": 0, + "slot": 0, + "quartile": 5, + "run_ms": 20000, + "viewable_ms": 20000 + }, + { + "digest": "ad59a57ddc5b78d9578ae67bb0b46fd9d2ce38160280ed25d284743bda66ec66", + "verdict": "presented", + "record": 0, + "slot": 0, + "quartile": 3, + "run_ms": 10000, + "viewable_ms": 10000 + }, + { + "digest": "d8abb9540b8212347769311b1edf80cbc7d457f35c92697557b62413c13a7f07", + "verdict": "claim_frame_mismatch", + "record": 0, + "slot": 0, + "quartile": 5, + "run_ms": 20000, + "viewable_ms": 20000 + }, + { + "digest": "e2e8a6622678121bf4c7ab95d4220403aa93993b9451791c3ef41b0962034e3c", + "verdict": "claim_not_viewable", + "record": 0, + "slot": 0, + "quartile": 5, + "run_ms": 0, + "viewable_ms": 0 + }, + { + "digest": "21d55ff75cb90c3329e74ce183193cee67528235c848758b42c02878877b48cb", + "verdict": "claim_duplicate", + "record": 0, + "slot": 0, + "quartile": null, + "run_ms": null, + "viewable_ms": null + }, + { + "digest": "9dbf582b4216d562c18f44cf4ee718acfdd4555f41dea73f787ea52193750d36", + "verdict": "claim_asset_mismatch", + "record": 0, + "slot": 0, + "quartile": 5, + "run_ms": 20000, + "viewable_ms": 20000 + } + ], + "spend": { + "USD": 3600000 + } + }, + { + "name": "double_box", + "about": "a double box on a linear HLS channel: the program and the ad side by side in 16:9 boxes 16 pixels apart (live-compose §5.9); one shared pod-input record in EUR at a price the signer states, its VMAP response bound by digest and not read", + "manifests": [ + "double_box.m3u8" + ], + "responses": [], + "receipts": [], + "bound": [ + "double_box.vmap.xml" + ], + "records": [ + "575049310101000000000000000000000000000000000864622d627265616b10270000022d1d848146d66dec504f4766812d5f606c39fcab7973fe978bf053ed5cce810e0107582d41442d494445555202048007380401000000001000010101000000000100dd28a65e8a5cf103133647fbd0f7020c0c3391a159abd509111d8a6a36f50b6c087372762d64622d31087372762d64622d3100005d27b5699b25f123fb2c068ee98f43fcd3dd80898fdf8a68222eea631b89b33210270000a0bb0d0000000000030000000000000000000000000000000000000000000000000000000000000000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d356997fb56133a9cb5bfb21f7067ab61c11272b56711e9199f42bd93b231ea419e473afc0219c4cb5b65b51e2d857e963e9ee5c0aa46191945367f9c11a99b54604" + ], + "record_about": [ + "the channel's break, every viewer shown it: one ad in a double box" + ], + "claims": [ + "575053310101c86fbdf019c0ab218957f24c269bbe1ab802243b25eb5cecc4edc80000293ca7e00e87ce739520a6ce91bec571dd51a05d27b5699b25f123fb2c068ee98f43fcd3dd80898fdf8a68222eea631b89b3326e6f0d61db1bed7bfaf01088224317706db3f45f9e3819302d89829adc8f9b48019809ffffffff2000040064622d311800323032362d31302d32355431323a31353a30302e3030305a087372762d64622d310200870777575450fa85b8d8e996f2cc78676b269b306921ebc9feed2a3e74d4b4a8abf6e1676be9383b7b2192ca27e431e2d8f3a2107c19d93ada212f90ee497c13010000000000102700001027010000d08ab49c675d1f444d5de4816640e9aa6d0c8f6f5fb9b89cb060a00aa6db7ca5ab3ba3720255120e3a0649407081d59c8098c36a2ee217f9b50afdd6b7100305", + "575053310101c86fbdf019c0ab218957f24c269bbe1ab802243b25eb5cecc4edc80000293ca795f3186502eab71753f6ae382c406f175d27b5699b25f123fb2c068ee98f43fcd3dd80898fdf8a68222eea631b89b3325b40e268c3ab78910157a9f7af1cd5f150a5cd75eb09cb1a641680d935fe4bf3039809ffffffff2000040064622d311800323032362d31302d32355431323a31353a30302e3030305a087372762d64622d310200870777575450fa85b8d8e996f2cc78676b269b306921ebc9feed2a3e74d4b4a8abf6e1676be9383b7b2192ca27e431e2d8f3a2107c19d93ada212f90ee497c1301000000000010270000401f010000042f1fa97f5bba16bace9e83fecea751fbdf5ee69eaa1bcd49007424cf7bf763067f4fd64bdaaccd5496404994439fe1557a29baf67c38c901fc35133866ac0e" + ], + "claim_about": [ + "viewer 31: the ad's box in view throughout", + "viewer 32: 80% of the player's area in view, measured over the player and stated so" + ], + "record_facts": [ + { + "digest": "58dc2611d72505a66a52568729d9042f935b3ba30cabbfd4c18d6980d0268fef", + "verdict": "accepted", + "response_checked": false, + "ads": [ + { + "slot": 0, + "own_claims": [ + 0, + 1 + ], + "presentations": 2, + "verdict": "presented", + "quartiles": [ + 2, + 2, + 2, + 2, + 2 + ], + "presented_spend_micros": 1800000, + "stated_quartile": null + } + ], + "presented_spend_micros": 1800000, + "stated_spend_micros": null + } + ], + "claim_facts": [ + { + "digest": "8b04c6e586f0c92416d1a1a552b7db828bd7266909381a725f84a623eb2054ce", + "verdict": "presented", + "record": 0, + "slot": 0, + "quartile": 5, + "run_ms": 10000, + "viewable_ms": 10000 + }, + { + "digest": "4f4bd88dc55bbf2d4485a0b9b2d310c934899cb4ae9ff5dcb93126bc79051bbb", + "verdict": "presented", + "record": 0, + "slot": 0, + "quartile": 5, + "run_ms": 10000, + "viewable_ms": 10000 + } + ], + "spend": { + "EUR": 1800000 + } + }, + { + "name": "form1_prefetch", + "about": "a stitcher's own decision, sealed in a form-1 wai.video.ssai receipt whose beacons counted ad-f2 as started when its first segment was fetched ahead of the break; the viewer left after ad-f1, so ad-f2 has no presentation and the spend that counts is ad-f1's alone, beside the receipt's stated spend", + "manifests": [ + "form1_prefetch.m3u8" + ], + "responses": [], + "receipts": [ + "form1_prefetch.receipt.json" + ], + "bound": [], + "records": [ + "5750493101015384f0059ac861978eb420748e91a8b70866312d627265616b3075000003dbf8e4340d993f39fd150cd4f28e1b97c3eb528e579602bfc290de89e3312b6f0307582d41442d494455534402000000000000000000000000000101000000000200e213b59c682f033ee15842b943d526ae470977e48eac76c748744da9c37ff968087372762d66312d300561642d663100009300e637c151e8585af0813f477696c97bebc09d94472b61109841d8fdb22135983a0000c0c62d000000000003000000000000000000000000000000000000000000000000000000000000000001d994850b87273d0d33387ddd57020c5801194a59347ffd499dd3d7f8316e5378087372762d66312d310561642d663200006556c8bc5c8c5e27f028eef55945ef4dd80e88422c8f761aed913fb815818d7e983a000080841e0000000000030000000000000000000000000000000000000000000000000000000000000000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d356c5d9ef844d9b3dd94556aebbd9556aa6deec288a67ea61e4b104e0ce84cf8e2e355929779f4bfbba18d2221780c298925eac6118583cb103b944c2426bfe7b01", + "57504931010112365eba028267aaafe51682c9e91cda0866312d627265616b3075000003dbf8e4340d993f39fd150cd4f28e1b97c3eb528e579602bfc290de89e3312b6f0307582d41442d4944555344020000000000000000000000000001010000000002006213b59c682f033ee15842b943d526ae470977e48eac76c748744da9c37ff968087372762d66312d300561642d663100009300e637c151e8585af0813f477696c97bebc09d94472b61109841d8fdb22135983a0000c0c62d0000000000030000000000000000000000000000000000000000000000000000000000000000015994850b87273d0d33387ddd57020c5801194a59347ffd499dd3d7f8316e5378087372762d66312d310561642d663200006556c8bc5c8c5e27f028eef55945ef4dd80e88422c8f761aed913fb815818d7e983a0000200b200000000000030000000000000000000000000000000000000000000000000000000000000000d51eb97a22c66a85da121af9af5c97cf47f1cf4839b9dc60ec3f17e2c589d3565efd45eb08a6ec157addcee3cedd4aa4d1f7104235aa1d293462d4c9c1d28816a5e8f44b2cab0a3394bc9c978d9ef78fd016b6e00e1980970aada37cd66cc903" + ], + "record_about": [ + "viewer 41's break, decided by the stitcher's own deterministic decision and sealed in a form-1 receipt that states ad-f2 started", + "a record naming the same receipt with ad-f2 at another price" + ], + "claims": [ + "57505331010120ec58d17e783b6e13fe7f2f6fd568770ed643999c26342039a846e9b6ce1b425384f0059ac861978eb420748e91a8b79300e637c151e8585af0813f477696c97bebc09d94472b61109841d8fdb221355e5ed077b4b876f9853c7e460a83b2156e5b82420af023ccb91afe103a0753fa011027ffffffff2300070066312d61642d301800323032362d31302d32355431323a32303a30302e3030305a087372762d66312d30030077c807ae2513651a5aa4328029ecf1f7c14824ac70155ed739e90ad648905c46921eb679dcc0008c174c42686ec8c1fd40e93c978223d12e90e2a9e86474925919010782cd315059b9f603e1a81f2d3c619eb7cb32693ea4f8a043a635d70fa9010000000000983a00001027010000f6facf1d34213c8ec7d34de223c5b46bc8a03ffaeeb0f7f8257440ed85248093bf6688b656538e0ba3c0f15dedbb0fb4583cf49d59bf9693a52c64e00ace3006" + ], + "claim_about": [ + "viewer 41, ad-f1: in view and audible from start to end; viewer 41 left before ad-f2" + ], + "record_facts": [ + { + "digest": "6a99dbd589a99db0cad0abd70e2ecb55db559f84709762444f434bd07b58b13f", + "verdict": "accepted", + "response_checked": true, + "ads": [ + { + "slot": 0, + "own_claims": [ + 0 + ], + "presentations": 1, + "verdict": "presented", + "quartiles": [ + 1, + 1, + 1, + 1, + 1 + ], + "presented_spend_micros": 3000000, + "stated_quartile": 5 + }, + { + "slot": 1, + "own_claims": [], + "presentations": 0, + "verdict": "claim_absent", + "quartiles": [ + 0, + 0, + 0, + 0, + 0 + ], + "presented_spend_micros": 0, + "stated_quartile": 1 + } + ], + "presented_spend_micros": 3000000, + "stated_spend_micros": 5000000 + }, + { + "digest": "fdb20923e702d8888b5576a20de22e99dad3e87f80e131b73c698ac41e5aa9d6", + "verdict": "pod_response_mismatch" + } + ], + "claim_facts": [ + { + "digest": "bb11d8368fea1f94e0121fe73955fc61edc02fc23929ab7ca282948b358ffd69", + "verdict": "presented", + "record": 0, + "slot": 0, + "quartile": 5, + "run_ms": 15000, + "viewable_ms": 15000 + } + ], + "spend": { + "USD": 3000000 + } + } + ] +} diff --git a/wai/live-conformance/stitched_presentation_verify.py b/wai/live-conformance/stitched_presentation_verify.py new file mode 100644 index 000000000..bb52d6d52 --- /dev/null +++ b/wai/live-conformance/stitched_presentation_verify.py @@ -0,0 +1,1046 @@ +#!/usr/bin/env python3 +"""Independent verifier of the stitched-presentation corpus (extensions/stitched-presentation.md). + +Written from the extension's text. It shares no code with wai-rs, which generates the corpus, +and never runs it. Ed25519 signatures are checked with ../claim-conformance/_ed25519.py, WAI's +strict rule (SPEC §7 *Signatures*), and BLAKE3 with ../claim-conformance/_blake3.py. From the +corpora's other second implementations it takes, by reference as the extension does: the spans' +quartile and run and RSASSA-PSS from ad_presentation_verify.py (ad-presentation §6, §7); a +form-1 receipt's leaves, Merkle root and class encoding from live_ads_verify.py (live-ads §8.3, +energy-measurement §2.7); and an ad frame from live_compose_verify.py (live-compose §5.9). +Everything else is here, from the standard library: + +- markers, their bodies and digests (§3.1), and reading them from a Media Playlist's date range + tags, an asset list and an MPD over the XML subset of §3.5, with a reader of its own; +- the pod-input record, its rules and signature (§4), the VAST check and a form-1 receipt's + JSON carriage, payload, identity and rules (§4.5, §7.2); +- the claim, its session, its rules and signature, and its attestation challenge (§5); +- each record's and claim's verdict, each ad's presentations and quartiles, and the spend by + currency (§7). + +For every vector it requires every fact the corpus states; of the corpus, that every reason of +§9 appears in it and that no other file is there. Then it mutates copies of the corpus, one +defect each, and requires every copy to fail; and it drops each of its own checks in turn and +requires the corpus to catch the weakened verifier. + + python3 -I stitched_presentation_verify.py + +Exit status 0 only if every vector verifies, every mutated copy fails and every weakened +verifier is caught. Python 3.8 or later; standard library only. +""" + +import copy +import hashlib +import json +import os +import struct +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HERE) +sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance")) +import _blake3 as B3 # noqa: E402 +import _ed25519 as ED # noqa: E402 +import ad_presentation_verify as APV # noqa: E402 +import live_ads_verify as LAV # noqa: E402 +import live_compose_verify as LCV # noqa: E402 + +EXTENSION = os.path.join(HERE, "..", "extensions", "stitched-presentation.md") + +POD_MAGIC, CLAIM_MAGIC = b"WPI1", b"WPS1" +POD_DOMAIN = b"wai:stitched-pod-input\x01" # §4.4 +CLAIM_DOMAIN = b"wai:stitched-presentation-claim\x01" # §5.3 +SESSION_DOMAIN = b"wai:stitched-presentation-session\x01" # §5.2 +ATTEST_DOMAIN = b"wai:stitched-presentation-attest\x01" # §5.5 +MARKER_DOMAIN = b"wai:stitched-marker\x01" # §3.1 +FORM1_DOMAIN = b"wai:video-ssai-receipt\x01" # §4.5 +FORM1_ID_DOMAIN = b"wai:video-ssai-receipt-id\x01" +INTERSTITIAL = "com.apple.hls.interstitial" # allow-name: the interstitial date-range class, a protocol identifier +DATERANGE, DATERANGE_I, EVENT = 1, 2, 3 # §3.1 +VAST, VMAP, FORM1 = 1, 2, 3 # §4.1 +SERVING_ID, AD_ID, FORM1_ID = 1, 2, 3 +PRICING, WINNING_BID, STATED = 1, 2, 3 # §4.2 +CLAIM, ATTEST = 1, 2 # ad-presentation §8 +MAX_PRICE = 10 ** 12 +U64 = 2 ** 64 - 1 + +# The checks a weakened verifier may drop (main(), the last part). +SKIP = set() + + +def sha(*parts): + h = hashlib.sha256() + for p in parts: + h.update(p) + return h.digest() + + +def reasons(): + """§9's table of the codes this extension adds, from the text.""" + with open(EXTENSION, encoding="utf-8") as f: + text = f.read() + at = text.index("\n## 9. Reasons") + end = text.index("\nA verification also", at) + return [line.split("`")[1] for line in text[at:end].splitlines() if line.startswith("| `")] + + +REASONS = reasons() +OTHER = ["bad_signature", "claim_key_unbound", "claim_no_decision", "claim_duplicate", "claim_asset_mismatch", + "claim_not_viewable", "attestation_absent", "attestation_invalid", "claim_absent"] + + +class Bad(Exception): + """A verdict other than accepted or presented.""" + + +# ---------------------------------------------------------------- §3.1 markers + +def lng(b): + return struct.pack(" len(b): + raise ValueError + n = struct.unpack(" len(b): + raise ValueError + p += 2 + n + return b[p - n:p] + try: + if carriage == DATERANGE: + m = {"carriage": 1, "id": field(), "start": field()} + elif carriage == DATERANGE_I: + m = {"carriage": 2, "id": field(), "start": field()} + if p + 2 > len(b): + return None + m["index"] = struct.unpack("", "amp": "&", "quot": '"', "apos": "'"} + if r in named: + out.append(named[r]) + elif r.startswith("#x") and len(r) > 2: + out.append(chr(int(r[2:], 16))) + elif r.startswith("#") and len(r) > 1 and r[1:].isdigit(): + out.append(chr(int(r[1:]))) + else: + raise ValueError("reference") + i = k + 1 + + +def local(n): + return n.rsplit(":", 1)[-1] + + +class XmlReader: + def __init__(self, s): + self.s, self.p = s, 0 + + def rest(self): + return self.s[self.p:] + + def ws(self): + while self.p < len(self.s) and self.s[self.p] in WS: + self.p += 1 + + def eat(self, t): + if self.s.startswith(t, self.p): + self.p += len(t) + return True + return False + + def until(self, t): + j = self.s.find(t, self.p) + if j < 0: + raise ValueError("unterminated") + out = self.s[self.p:j] + self.p = j + len(t) + return out + + def misc(self): + while True: + self.ws() + if self.eat("") + elif self.s.startswith("") + else: + return + + def name(self): + j = self.p + while j < len(self.s) and self.s[j] not in WS + "/>=": + j += 1 + n = self.s[self.p:j] + if not n or not (n[0].isalpha() or n[0] in "_:") or any(not (c.isalnum() or c in "_-.:") for c in n): + raise ValueError("name") + self.p = j + return n + + def element(self, depth): + if depth > 64 or not self.eat("<"): + raise ValueError("element") + q = self.name() + attrs = {} + while True: + before = self.p + self.ws() + if self.eat("/>"): + return {"name": local(q), "attrs": attrs, "children": []} + if self.eat(">"): + break + if self.p == before: + raise ValueError("attribute") + an = self.name() + self.ws() + if not self.eat("="): + raise ValueError("attribute") + self.ws() + if self.eat('"'): + raw = self.until('"') + elif self.eat("'"): + raw = self.until("'") + else: + raise ValueError("attribute") + if "<" in raw or local(an) in attrs: + raise ValueError("attribute") + attrs[local(an)] = unescape(raw) + kids = [] + while True: + if self.eat(""): + raise ValueError("end tag") + return {"name": local(q), "attrs": attrs, "children": kids} + if self.eat("")) + elif self.eat("") + elif self.s.startswith("") + elif self.s.startswith("<", self.p): + kids.append(self.element(depth + 1)) + else: + j = self.s.find("<", self.p) + if j < 0: + raise ValueError("unterminated") + kids.append(unescape(self.s[self.p:j])) + self.p = j + + +def read_xml(b): + """§3.5: the root element, or None.""" + try: + s = b.decode("utf-8") + if s.startswith(""): + return None + x = XmlReader(s) + x.misc() + root = x.element(1) + x.misc() + return root if x.p == len(s) else None + except (UnicodeDecodeError, ValueError, OverflowError): + return None + + +def kids(e, name): + return [c for c in e["children"] if isinstance(c, dict) and c["name"] == name] + + +def text(e): + return "".join(c for c in e["children"] if isinstance(c, str)).strip(WS) + + +def mpd_ads(b, attr): + root = read_xml(b) + if root is None or root["name"] != "MPD": + return None + out = [] + for period in kids(root, "Period"): + for es in kids(period, "EventStream"): + if "schemeIdUri" not in es["attrs"]: + continue + for ev in kids(es, "Event"): + if "id" in ev["attrs"] and attr in ev["attrs"]: + m = {"carriage": 3, "period": period["attrs"].get("id", "").encode(), + "scheme": es["attrs"]["schemeIdUri"].encode(), "value": es["attrs"].get("value", "").encode(), + "id": ev["attrs"]["id"].encode()} + out.append((m, ev["attrs"][attr].encode())) + return out + + +# ---------------------------------------------------------------- §4 the record + +def is_text(b): + if not 1 <= len(b) <= 255: + return False + try: + b.decode("utf-8") + return True + except UnicodeDecodeError: + return False + + +class Cursor: + def __init__(self, b, why): + self.b, self.p, self.why = b, 0, why + + def take(self, n): + if self.p + n > len(self.b): + raise Bad(self.why) + self.p += n + return self.b[self.p - n:self.p] + + def u(self, fmt): + return struct.unpack("<" + fmt, self.take(struct.calcsize("<" + fmt)))[0] + + def short(self): + return self.take(self.u("B")) + + def long(self): + return self.take(self.u("H")) + + +def frame_ok(f): + """§4.3.""" + if f["kind"] == 0: + return all(f[k] == 0 for k in ("width", "height", "corner", "window_w", "window_h", "gap", "aspect")) + if f["kind"] not in (3, 4): + return False + w, h = f["width"], f["height"] + if not (16 <= w <= 1920 and 16 <= h <= 1920 and w % 2 == 0 and h % 2 == 0 and w * h <= 2073600): + return False + if f["kind"] == 3 and (f["gap"] or f["aspect"]): + return False + if f["kind"] == 4 and f["aspect"] > 3: + return False + return LCV.frame_fields_ok(w, h, f["kind"], f["corner"], f["window_w"], f["window_h"], f["gap"]) + + +def share_of(f): + """§6: the region's share of the picture.""" + if f["kind"] == 0: + return 10000 + _, _, region = LCV.ad_frame(f["width"], f["height"], f["kind"], f["corner"], f["window_w"], f["window_h"], f["gap"], + f["aspect"]) + return LCV.share_bp(region, f["width"], f["height"]) + + +def terms_ok(q): + """ad-presentation §8, and bit 0 set (§4.4 rule 7).""" + r, rule, tt, iss = q["require"], q["rule"], q["token_type"], q["issuer"] + claim = bool(r & CLAIM) + if r > 7 or (not claim and r) or rule > 2 or claim != (rule != 0): + return False + if r & ATTEST: + if tt not in (1, 2) or not iss: + return False + elif tt != 0 or iss: + return False + return claim + + +def xml_name(b): + return 1 <= len(b) <= 255 and (chr(b[0]).isalpha() and b[0] < 128 or b[0] == 0x5F) and \ + all(chr(c).isalnum() and c < 128 or c in b"_-." for c in b) + + +def read_record(b): + """§4.4: the record, or Bad(pod_input_malformed).""" + why = "pod_input_malformed" + c = Cursor(b, why) + if c.take(4) != POD_MAGIC or c.take(1) != b"\x01": + raise Bad(why) + r = {"carriage": c.u("B"), "session": c.take(16), "avail_id": c.short(), "avail_ms": c.u("I"), + "format": c.u("B"), "response": c.take(32), "id_kind": c.u("B"), "id_attr": c.short(), + "currency": c.take(3), "exponent": c.u("B")} + r["frame"] = {"kind": c.u("B"), "width": c.u("H"), "height": c.u("H"), "corner": c.u("B"), "window_w": c.u("H"), + "window_h": c.u("H"), "gap": c.u("H"), "aspect": c.u("B")} + r["terms"] = {"require": c.u("B"), "rule": c.u("B"), "token_type": c.u("H"), "issuer": c.long()} + r["ads"] = [] + for _ in range(c.u("B")): + r["ads"].append({"slot": c.u("B"), "marker": c.take(32), "ad_id": c.short(), "serving_id": c.short(), + "creative_id": c.short(), "registry": c.short(), "creative_hash": c.take(32), + "duration_ms": c.u("I"), "price_micros": c.u("Q"), "price_source": c.u("B"), + "evidence": c.take(32)}) + r["signer"] = c.take(32) + r["body"] = b[:c.p] + r["sig"] = c.take(64) + if c.p != len(b): + raise Bad(why) + if "record_rules" in SKIP: + return r + ok = r["carriage"] in (1, 2, 3) and is_text(r["avail_id"]) and r["avail_ms"] >= 1 + ok = ok and ((r["format"] in (VAST, VMAP) and r["id_kind"] in (SERVING_ID, AD_ID)) + or (r["format"] == FORM1 and r["id_kind"] == FORM1_ID)) + a = r["id_attr"] + if r["carriage"] == DATERANGE: + ok = ok and 1 <= len(a) <= 255 and all(ch in b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-" for ch in a) + elif r["carriage"] == EVENT: + ok = ok and xml_name(a) + else: + ok = ok and a == b"" + ok = ok and all(65 <= ch <= 90 for ch in r["currency"]) and r["exponent"] <= 4 + ok = ok and frame_ok(r["frame"]) and terms_ok(r["terms"]) and 1 <= len(r["ads"]) <= 16 + for k, ad in enumerate(r["ads"]): + src = ad["price_source"] + ok = ok and ad["slot"] == k and is_text(ad["ad_id"]) and is_text(ad["serving_id"]) + ok = ok and (ad["creative_id"] == b"") == (ad["registry"] == b"") + ok = ok and (ad["creative_id"] == b"" or (is_text(ad["creative_id"]) and is_text(ad["registry"]))) + ok = ok and ad["duration_ms"] >= 1 and ad["price_micros"] <= MAX_PRICE and src in (1, 2, 3) + ok = ok and not (src == PRICING and r["format"] != VAST) and not (src == WINNING_BID and r["format"] == FORM1) + ok = ok and not (r["format"] == FORM1 and src != STATED) + ok = ok and (ad["evidence"] != bytes(32)) == (src == WINNING_BID) + ok = ok and all(x["marker"] != ad["marker"] for x in r["ads"][:k]) + if not ok: + raise Bad(why) + return r + + +# ---------------------------------------------------------------- §4.5 a form-1 receipt + +FORM1_MEMBERS = {"form", "avail_id", "manifest_hash", "pod_hash", "impressions", "merkle_root", "budget_spent_micros", + "work_decide_ops", "joules_micro", "energy_provenance", "grant", "parent_receipt_hash", + "signer_pubkey", "signer_id", "sig"} + + +def read_form1(o): + """§4.5's JSON carriage, or None. Its integers, hexadecimal and class read as live-ads §8.2's.""" + try: + if not isinstance(o, dict): + return None + label = o.get("energy_provenance") + if label is not None and label not in LAV.EVIDENCE: + return None + if set(o) != FORM1_MEMBERS | set(LAV.EVIDENCE.get(label, [])) or LAV.u(o["form"]) != 1: + return None + # A form-2 shaped copy holds the shared members, so live-ads' reader reads its class, + # grant and impressions; the form-1 members are read here. + shaped = {k: o[k] for k in o if k not in ("form", "avail_id", "manifest_hash", "parent_receipt_hash")} + shaped.update({"form": 2, "room": "00" * 32, "cue": "00" * 32, "session": "00" * 16, "start_offset_ms": 0, + "format": 1, "asset_list_sha256": "00" * 32, "avail_ms": 0, "slots": 0, + "funds_remaining_micros": 0}) + r = LAV.read_receipt(shaped) + r["avail_id"] = LAV.text(o["avail_id"]) + r["manifest_hash"] = LAV.hexn(o["manifest_hash"], 32) + p = o["parent_receipt_hash"] + r["parent"] = None if p is None else LAV.hexn(p, 32) + return r + except LAV.Refuse: + return None + + +def form1_payload(r): + g = r["grant"] + a, cap, sid = r["avail_id"].encode(), g["capability"].encode(), r["signer_id"].encode() + out = [FORM1_DOMAIN, struct.pack(">I", len(a)), a, r["manifest_hash"], r["pod_hash"], r["merkle_root"], + LAV.be64(r["budget_spent_micros"]), LAV.be64(r["work_decide_ops"]), LAV.be64(r["joules_micro"]), + LAV.class_bytes(r["class"]), g["grant_hash"], struct.pack(">I", len(cap)), cap, + LAV.be64(g["joule_ceiling_micro"])] + out.append(b"\x00" if g["funds_ceiling"] is None else b"\x01" + LAV.be64(g["funds_ceiling"])) + out.append(b"\x00" if r["parent"] is None else b"\x01" + r["parent"]) + out.append(LAV.be64(len(r["impressions"]))) + out += [LAV.leaf(i) for i in r["impressions"]] + out += [r["signer_pubkey"], struct.pack(">I", len(sid)), sid] + return b"".join(out) + + +def form1_identity(r): + return B3.blake3(FORM1_ID_DOMAIN + form1_payload(r) + r["sig"]) + + +def form1_verifies(r): + """§4.5: a form-1 receipt verifies.""" + if ED.verify(r["signer_pubkey"], form1_payload(r), r["sig"]): + return False + imps = r["impressions"] + spend = sum(i["price_micros"] for i in imps if i["quartiles_reached"] >= 1) + if LAV.merkle([LAV.leaf(i) for i in imps]) != r["merkle_root"] or spend != r["budget_spent_micros"]: + return False + f = r["grant"]["funds_ceiling"] + if f is not None and spend > f: + return False + if r["work_decide_ops"] < len(imps) or any(i["slot"] != k or i["quartiles_reached"] > 5 for k, i in enumerate(imps)): + return False + c, j = r["class"], r["joules_micro"] + if c is None: + return j == 0 + if j == 0: + return False + if c["label"] in ("OnChipCounter", "CalibratedInstrument") and (c["ppm"] == 0 or c["window"] == 0 or j * c["ppm"] < LAV.FLOOR): + return False + return not (c["label"] == "CalibratedInstrument" and all(ch in LAV.WHITE_SPACE for ch in c["ref"])) + + +# ---------------------------------------------------------------- §7.2 the VAST check + +def decimal_scaled(s, scale): + if "." in s: + i, f = s.split(".", 1) + if not f: + return None + else: + i, f = s, "" + if not i or not (i.isascii() and i.isdigit()) or (f and not (f.isascii() and f.isdigit())): + return None + if len(f) > scale: + if f[scale:].strip("0"): + return None + f = f[:scale] + v = int(i + f + "0" * (scale - len(f))) + return v if v <= U64 else None + + +def vast_matches(rec, b): + root = read_xml(b) + if root is None or root["name"] != "VAST": + return False + for ad in rec["ads"]: + want = ad["serving_id"].decode() + + def ident(a): + if rec["id_kind"] == SERVING_ID: + il = kids(a, "InLine") + sv = kids(il[0], "AdServingId") if il else [] + return text(sv[0]) if sv else None + return a["attrs"].get("id") + hits = [a for a in kids(root, "Ad") if ident(a) == want] + if len(hits) != 1 or not kids(hits[0], "InLine"): + return False + inline = kids(hits[0], "InLine")[0] + if ad["creative_id"] and "vast_creative" not in SKIP: + cid, reg = ad["creative_id"].decode(), ad["registry"].decode() + if not any(text(u) == cid and u["attrs"].get("idRegistry") == reg + for cs in kids(inline, "Creatives") for c in kids(cs, "Creative") for u in kids(c, "UniversalAdId")): + return False + if ad["price_source"] == PRICING and "vast_price" not in SKIP: + ps = kids(inline, "Pricing") + if not ps: + return False + p = ps[0] + if p["attrs"].get("model") != "CPM" or p["attrs"].get("currency") != rec["currency"].decode(): + return False + if decimal_scaled(text(p), rec["exponent"] + 3) != ad["price_micros"]: + return False + return True + + +def form1_matches(rec, r): + if not form1_verifies(r) or len(r["impressions"]) != len(rec["ads"]): + return False + return all(i["slot"] == a["slot"] and i["ad_id"].encode() == a["serving_id"] and i["creative_hash"] == a["creative_hash"] + and i["duration_ms"] == a["duration_ms"] and i["price_micros"] == a["price_micros"] + for i, a in zip(r["impressions"], rec["ads"])) + + +# ---------------------------------------------------------------- §5 the claim + +def session_of(key): + return sha(SESSION_DOMAIN, key)[:16] + + +def read_claim(b): + """§5.4: the claim, or Bad(stitched_claim_malformed).""" + why = "stitched_claim_malformed" + c = Cursor(b, why) + if c.take(4) != CLAIM_MAGIC or c.take(1) != b"\x01": + raise Bad(why) + x = {"carriage": c.u("B"), "manifest": c.take(32), "session": c.take(16), "creative_hash": c.take(32), + "player": c.take(32), "geometry": c.u("B"), "share_bp": c.u("H"), "frames": c.u("I"), "marker": c.long(), + "ad_id": c.short()} + n = c.u("H") + x["objects"] = [c.take(32) for _ in range(n)] + m = c.u("H") + x["spans"] = [] + for _ in range(m): + start, length, vis, flags = struct.unpack(" 10000 or s["flags"] > 3: + raise Bad(why) + if prev is None and s["flags"] & APV.RESUMED: + raise Bad(why) + if prev is not None and (s["start"] < prev or (s["start"] > prev and not s["flags"] & APV.RESUMED)): + raise Bad(why) + prev = s["start"] + s["length"] + return x + + +def redemption_context(c): + return sha(ATTEST_DOMAIN, bytes([c["carriage"]]), marker_digest(c["carriage"], c["marker"]), c["session"], + c["creative_hash"], c["player"], bytes([len(c["ad_id"])]), c["ad_id"]) + + +def attestation(c, q, issuers): + if not c["attestation"]: + return "attestation_absent" + t = APV.read_token(c["attestation"]) + if t is None or t["type"] != q["token_type"]: + return "attestation_invalid" + if t["digest"] != sha(APV.token_challenge(q["token_type"], q["issuer"], redemption_context(c))): + return "attestation_invalid" + for i in issuers: + if i["name"] == q["issuer"] and i["token_type"] == t["type"]: + for k in i["keys"]: + if sha(k) == t["key_id"] and t["type"] == 2 and APV.pss_verify(k, t["input"], t["auth"]): + return None + return "attestation_invalid" + + +def marker_in_manifest(c, rec, man, manifests): + """§7.3 step 6.""" + m, attr = c["m"], rec["id_attr"].decode() + if m is None: + return False + if m["carriage"] == DATERANGE: + return any(f == m and a == c["ad_id"] for f, a in (hls_ads(man, attr) or [])) + if m["carriage"] == EVENT: + return any(f == m and a == c["ad_id"] for f, a in (mpd_ads(man, attr) or [])) + uris = asset_uris(man) + if uris is None or m["index"] >= len(uris) or uris[m["index"]].encode() != c["ad_id"]: + return False + return any((m["id"], m["start"]) in (interstitials(p) or []) for p in manifests) + + +# ---------------------------------------------------------------- §7 verification + +def creative_digest(objects): + return APV.creative_digest(objects) + + +def verify(inp): + """§7: what a verification states, as the corpus writes it.""" + accepted, rfacts, stated = [], [], [] + for b in inp["records"]: + f = {"digest": None, "verdict": "accepted"} + q_stated = None + try: + r = read_record(b) + f["digest"] = sha(r["body"]).hex() + bad = ED.verify(r["signer"], POD_DOMAIN + r["body"], r["sig"]) + if bad: + raise Bad(bad) + if r["signer"] not in inp["signers"] and "signer" not in SKIP: + raise Bad("pod_signer_unknown") + if "pod_duplicate" not in SKIP and any(p is not None and p["session"] == r["session"] and + any(a["marker"] == x["marker"] for a in p["ads"] for x in r["ads"]) + for p in accepted): + raise Bad("pod_duplicate") + checked, stated_spend = False, None + if r["format"] == VAST: + v = next((x for x in inp["responses"] if sha(x) == r["response"]), None) + if v is not None: + if not vast_matches(r, v) and "response" not in SKIP: + raise Bad("pod_response_mismatch") + checked = True + elif r["format"] == FORM1: + held = [x for x in (read_form1(o) for o in inp["receipts"]) if x is not None] + x = next((h for h in held if form1_identity(h) == r["response"]), None) + if x is not None: + if not form1_matches(r, x) and "response" not in SKIP: + raise Bad("pod_response_mismatch") + checked = True + q_stated = [i["quartiles_reached"] for i in x["impressions"]] + stated_spend = x["budget_spent_micros"] + f.update(response_checked=checked, stated_spend_micros=stated_spend) + accepted.append(r) + except Bad as e: + f = {"digest": f["digest"], "verdict": str(e)} + q_stated = None + accepted.append(None) + rfacts.append(f) + stated.append(q_stated) + + cfacts, own, passed = [], {}, set() + by_hash = {sha(m): m for m in inp["manifests"]} + for j, b in enumerate(inp["claims"]): + f = {"digest": None, "verdict": "presented", "record": None, "slot": None, "quartile": None, "run_ms": None, + "viewable_ms": None} + try: + c = read_claim(b) + f["digest"] = sha(c["body"]).hex() + bad = ED.verify(c["player"], CLAIM_DOMAIN + c["body"], c["sig"]) + if bad: + raise Bad(bad) + if session_of(c["player"]) != c["session"] and "key_bound" not in SKIP: + raise Bad("claim_key_unbound") + md = marker_digest(c["carriage"], c["marker"]) + + def names(p): + return p["carriage"] == c["carriage"] and any(a["marker"] == md and a["ad_id"] == c["ad_id"] for a in p["ads"]) + i = next((k for k, p in enumerate(accepted) if p is not None and names(p) and + (p["session"] == bytes(16) or p["session"] == c["session"] or "session" in SKIP)), None) + if i is None: + raise Bad("claim_session_unbound" if any(p is not None and names(p) for p in accepted) else "claim_no_decision") + rec = accepted[i] + ad = next(a for a in rec["ads"] if a["marker"] == md and a["ad_id"] == c["ad_id"]) + f["record"], f["slot"] = i, ad["slot"] + man = by_hash.get(c["manifest"]) + if man is None and "manifest" not in SKIP: + raise Bad("claim_manifest_unknown") + if man is not None and "marker" not in SKIP and not marker_in_manifest(c, rec, man, inp["manifests"]): + raise Bad("claim_marker_absent") + key = (i, ad["slot"], c["session"]) + if key in passed and "claim_duplicate" not in SKIP: + raise Bad("claim_duplicate") + passed.add(key) + own.setdefault((i, ad["slot"]), []).append(j) + q = APV.quartile(c["spans"], ad["duration_ms"]) + run, viewable = APV.runs(c, rec["terms"]) + f.update(quartile=q, run_ms=run, viewable_ms=viewable) + cr = next((x for x in inp["creatives"] if creative_digest(x) == ad["creative_hash"]), None) + asset_ok = c["creative_hash"] == ad["creative_hash"] and cr is not None and \ + sum(o["duration_ms"] for o in cr) == ad["duration_ms"] and \ + all(s["start"] + s["length"] <= ad["duration_ms"] for s in c["spans"]) and \ + APV.touched(cr, c["spans"]) == c["objects"] + if not asset_ok and "asset" not in SKIP: + raise Bad("claim_asset_mismatch") + if c["share_bp"] != share_of(rec["frame"]) and "frame" not in SKIP: + raise Bad("claim_frame_mismatch") + if run < 2000 and "viewable" not in SKIP: + raise Bad("claim_not_viewable") + if rec["terms"]["require"] & ATTEST and "attest" not in SKIP: + why = attestation(c, rec["terms"], inp["issuers"]) + if why: + raise Bad(why) + except Bad as e: + f["verdict"] = str(e) + cfacts.append(f) + + spend = {} + for i, rec in enumerate(accepted): + if rec is None: + continue + ads, total = [], 0 + for a in rec["ads"]: + mine = own.get((i, a["slot"]), []) + pres = [j for j in mine if cfacts[j]["verdict"] == "presented"] + qs = [cfacts[j]["quartile"] for j in pres] + if pres: + verdict = "presented" + else: + verdict = cfacts[mine[0]]["verdict"] if mine else "claim_absent" + s = a["price_micros"] * len(pres) + total += s + ads.append({"slot": a["slot"], "own_claims": mine, "presentations": len(pres), "verdict": verdict, + "quartiles": [sum(1 for x in qs if x >= k) for k in range(1, 6)], "presented_spend_micros": s, + "stated_quartile": None if stated[i] is None else stated[i][a["slot"]]}) + rfacts[i].update(ads=ads, presented_spend_micros=total) + cur = rec["currency"].decode() + spend[cur] = spend.get(cur, 0) + total + return {"record_facts": rfacts, "claim_facts": cfacts, "spend": spend} + + +# ---------------------------------------------------------------- the corpus + +def held(doc): + creatives = [[{"content_hash": bytes.fromhex(o["content_hash"]), "duration_ms": o["duration_ms"]} for o in c["objects"]] + for c in doc["creatives"]] + issuers = [{"name": i["name"].encode(), "token_type": i["token_type"], "keys": [bytes.fromhex(k) for k in i["keys"]]} + for i in doc["issuers"]] + return creatives, issuers, [bytes.fromhex(k) for k in doc["signers"]] + + +def inputs(doc, v, files): + cr, iss, sg = held(doc) + get = lambda k: [files[n] for n in v[k]] # noqa: E731 + return {"records": [bytes.fromhex(x) for x in v["records"]], "claims": [bytes.fromhex(x) for x in v["claims"]], + "manifests": get("manifests"), "responses": get("responses"), + "receipts": [json.loads(x.decode("utf-8")) for x in get("receipts")], + "creatives": cr, "issuers": iss, "signers": sg} + + +def check(doc, files, quiet=False): + """Every vector's facts; the reasons; the files. A list of failures.""" + fails = [] + names = {"vectors.json"} + seen = set() + for v in doc["vectors"]: + for k in ("manifests", "responses", "receipts", "bound"): + names.update(v[k]) + try: + got = verify(inputs(doc, v, files)) + except (KeyError, ValueError, TypeError, IndexError) as e: + fails.append("%s: its inputs do not read (%s)" % (v["name"], e)) + continue + for k, want in got.items(): + if v.get(k) != want: + fails.append("%s: %s differs" % (v["name"], k)) + for r in got["record_facts"]: + seen.add(r["verdict"]) + seen.update(a["verdict"] for a in r.get("ads", [])) + seen.update(c["verdict"] for c in got["claim_facts"]) + for r in REASONS + OTHER: + if r not in seen: + fails.append("no record, claim or ad gives %s" % r) + if set(files) - {"README.md"} != names: + fails.append("the files are not exactly the corpus's: %s" % sorted(set(files) ^ names - {"README.md"})) + if not quiet: + for f in fails: + print("FAIL", f) + return fails + + +def load(d): + files = {} + for n in sorted(os.listdir(d)): + with open(os.path.join(d, n), "rb") as f: + files[n] = f.read() + return json.loads(files["vectors.json"].decode("utf-8")), files + + +def flip(h, at): + b = bytearray(bytes.fromhex(h)) + b[at % len(b)] ^= 0x01 + return b.hex() + + +def vec(d, name): + return next(v for v in d["vectors"] if v["name"] == name) + + +def mutations(doc, files): + """Copies of the corpus, one defect each: (what, doc, files).""" + out = [] + + def mut(what, f=None, g=None): + d, fs = copy.deepcopy(doc), dict(files) + if f: + f(d) + if g: + g(fs) + out.append((what, d, fs)) + + def edit(name, old, new): + def g(fs): + fs[name] = fs[name].replace(old, new, 1) + return g + + def set_claim(name, k, h): + return lambda d: vec(d, name)["claims"].__setitem__(k, h(vec(d, name)["claims"][k])) + + def set_record(name, k, h): + return lambda d: vec(d, name)["records"].__setitem__(k, h(vec(d, name)["records"][k])) + + mut("hls_stitched: a record's signature flipped", set_record("hls_stitched", 0, lambda h: flip(h, len(h) // 2 - 3))) + mut("hls_stitched: a claim's span visible fraction changed", set_claim("hls_stitched", 1, lambda h: flip(h, 300))) + mut("hls_stitched: viewer 1's playlist carries another ad identity", + g=edit("hls_stitched.viewer1.m3u8", b'X-AD-ID="srv-v1-0"', b'X-AD-ID="srv-v1-9"')) + mut("hls_stitched: viewer 1's VAST prices ad-a at 25.01", + g=edit("hls_stitched.viewer1.vast.xml", b">25.00<", b">25.01<")) + mut("hls_stitched: viewer 2's VAST names another universal identifier", + g=edit("hls_stitched.viewer2.vast.xml", b">UID-C<", b">UID-X<")) + mut("hls_stitched: the stated spend", lambda d: vec(d, "hls_stitched")["spend"].__setitem__("USD", 7400000)) + mut("hls_stitched: the prefetched ad stated presented", + lambda d: vec(d, "hls_stitched")["record_facts"][1]["ads"][1].update(verdict="presented")) + mut("hls_stitched: viewer 1's playlist missing", lambda d: vec(d, "hls_stitched")["manifests"].pop(0)) + mut("hls_stitched: an unknown signer trusted", lambda d: d["signers"].append(vec(d, "hls_stitched")["records"][2][-192:-128])) + mut("hls_interstitial: an asset's URI changed", g=edit("hls_interstitial.assets.json", b'"URI":"https://ads.example/c/6', b'"URI":"https://ads.example/c/7')) + mut("hls_interstitial: the interstitial class changed", g=edit("hls_interstitial.m3u8", INTERSTITIAL.encode(), b"urn:example:other")) + mut("hls_interstitial: a token's byte flipped", set_claim("hls_interstitial", 0, lambda h: flip(h, len(h) // 2 - 70))) + mut("hls_interstitial: the issuer's key changed", lambda d: d["issuers"][0]["keys"].__setitem__(0, flip(d["issuers"][0]["keys"][0], 100))) + mut("squeezeback: the MPD's event identity changed", g=edit("squeezeback.mpd", b'messageData="srv-sq-1"', b'messageData="srv-sq-2"')) + mut("squeezeback: the quartile counts", lambda d: vec(d, "squeezeback")["record_facts"][0]["ads"][0].update(quartiles=[3, 3, 3, 3, 2])) + mut("squeezeback: a creative's duration", lambda d: d["creatives"][5]["objects"][0].update(duration_ms=5001)) + mut("double_box: the stated currency's spend", lambda d: vec(d, "double_box")["spend"].update({"EUR": 1800001})) + mut("form1_prefetch: the receipt's stated quartile", g=edit("form1_prefetch.receipt.json", b'"quartiles_reached": 1', b'"quartiles_reached": 2')) + mut("form1_prefetch: the stated spend beside the presented", lambda d: vec(d, "form1_prefetch")["record_facts"][0].update(stated_spend_micros=3000000)) + mut("a file the corpus does not name", g=lambda fs: fs.__setitem__("extra.m3u8", b"#EXTM3U\n")) + return out + + +CHECKS = ["record_rules", "signer", "pod_duplicate", "response", "vast_price", "vast_creative", "claim_rules", + "key_bound", "session", "manifest", "marker", "claim_duplicate", "asset", "frame", "viewable", "attest"] + + +def main(corpus_dir): + doc, files = load(corpus_dir) + ok = True + fails = check(doc, files) + print("%s every vector gets the facts the corpus states, every reason of §9 is given, and no other file is there" + % ("FAIL" if fails else "PASS")) + ok &= not fails + caught = 0 + for what, d, fs in mutations(doc, files): + try: + bad = check(d, fs, quiet=True) + except (KeyError, ValueError, TypeError, IndexError, StopIteration): + bad = ["does not read"] + if bad: + caught += 1 + else: + print("FAIL the mutation was not caught:", what) + ok = False + print("%s every mutated copy of the corpus fails (%d)" % ("PASS" if ok else "FAIL", caught)) + weak_ok = True + for k in CHECKS: + SKIP.clear() + SKIP.add(k) + try: + bad = check(doc, files, quiet=True) + except (KeyError, ValueError, TypeError, IndexError): + bad = ["does not run"] + if not bad: + print("FAIL the corpus does not catch a verifier without its check", k) + weak_ok = False + SKIP.clear() + print("%s the corpus catches the verifier without each of its checks" % ("PASS" if weak_ok else "FAIL")) + return 0 if ok and weak_ok else 1 + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("usage: stitched_presentation_verify.py ", file=sys.stderr) + sys.exit(2) + sys.exit(main(sys.argv[1])) diff --git a/wai/wai-rs/Cargo.toml b/wai/wai-rs/Cargo.toml index 4416f8c46..786331198 100644 --- a/wai/wai-rs/Cargo.toml +++ b/wai/wai-rs/Cargo.toml @@ -287,6 +287,15 @@ live_watermark = ["live_room"] # the impressions whose claim verifies. SHA-256, SHA-384, BLAKE3, Ed25519 and the key log, and # num-bigint (already a dependency of `oer`) for the RSA arithmetic. live_ads = ["live_room", "video_ssai", "dep:num-bigint"] +# Stitched ad presentation (extensions/stitched-presentation): `wai.video.stitched_presentation` — +# a player's signed claim (WPS1) of an ad another party stitched into an HLS or DASH stream, named +# by the marker the stream carries for it (an EXT-X-DATERANGE tag, an interstitial's asset, an +# EventStream event) in the manifest it read; the signed pod-input record (WPI1): the ad server's +# response digest, the chosen creatives, their prices and the avail; the composited frames of +# live-compose §5.9; and the presentations, quartiles and spend recomputed from the claims that +# verify. ad-presentation's spans, viewability, quartile and attestation; live-compose's layout; +# a reader of HLS attribute lists, asset lists and an XML subset of its own. No new dependency. +stitched_presentation = ["live_ads", "live_compose"] # Live provenance (extensions/live-provenance): `wai.live.provenance` — the live video provenance # of C2PA 2.4 §19 for a room's CMAF tracks and a device's CMAF take, written and verified with no # C2PA library: ISO BMFF segments and the BMFF hash over whole-box exclusions, the JUMBF manifest @@ -962,6 +971,13 @@ name = "wai_ad_presentation_vectors" path = "src/bin/wai_ad_presentation_vectors.rs" required-features = ["live_ads"] +# Stitched presentation: the corpus (live-conformance/stitched/), and the verifier and the +# pod-input signer as a tool and a service (extensions/stitched-presentation.md §8). +[[bin]] +name = "wai_stitched_presentation" +path = "src/bin/wai_stitched_presentation.rs" +required-features = ["stitched_presentation"] + [[bin]] name = "wai_live_provenance_vectors" path = "src/bin/wai_live_provenance_vectors.rs" @@ -1757,6 +1773,11 @@ name = "ad_presentation" path = "tests/ad_presentation.rs" required-features = ["live_ads"] +[[test]] +name = "stitched_presentation" +path = "tests/stitched_presentation.rs" +required-features = ["stitched_presentation"] + [[test]] name = "live_provenance" path = "tests/live_provenance.rs" diff --git a/wai/wai-rs/src/bin/wai_stitched_presentation.rs b/wai/wai-rs/src/bin/wai_stitched_presentation.rs new file mode 100644 index 000000000..cbcb1a2a4 --- /dev/null +++ b/wai/wai-rs/src/bin/wai_stitched_presentation.rs @@ -0,0 +1,441 @@ +//! Stitched ad presentation (extensions/stitched-presentation.md): the corpus, the verifier +//! and the pod-input signer, as a tool and as a service (§8). +//! +//! wai_stitched_presentation vectors gen +//! wai_stitched_presentation vectors verify +//! write live-conformance/stitched/ from fixed seeds; or check that every file +//! regenerates byte for byte, that no other file but README.md is there, and that +//! every vector gets the facts it states +//! wai_stitched_presentation verify +//! verify a bundle (records, claims, manifests, responses, receipts, creatives, +//! issuers, signers; README of live-conformance/stitched/) and print what §7.5 states +//! wai_stitched_presentation sign-pod --key +//! write and sign a pod-input record (§4) from a draft and print it in hexadecimal; +//! the seed file holds the signer's 32-byte Ed25519 seed in hexadecimal +//! wai_stitched_presentation serve [--listen 127.0.0.1:9690] [--max-body BYTES] +//! [--max-connections N] [--read-timeout-ms MS] +//! answer `POST /v1/verify` (a bundle in, the statement out) and `GET /v1/health` +//! over HTTP/1.1 on a loopback address, one request a connection +//! +//! The corpus's second implementation is `live-conformance/stitched_presentation_verify.py`, +//! written from the extension's text and sharing no code with this binary. + +use std::collections::BTreeMap; +use std::io::{Read, Write}; +use std::net::{SocketAddr, TcpListener, TcpStream}; +use std::path::Path; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use ed25519_dalek::SigningKey; +use serde_json::{json, Value}; +use sha2::{Digest, Sha256}; +use wai::live_ads::presentation::{Creative, CreativeObject, Issuer, Requirement}; +use wai::stitched_presentation::{self as sp, corpus, Frame, Marker, PodAd, PodInput}; + +fn main() { + let a: Vec = std::env::args().collect(); + let code = match a.get(1).map(String::as_str) { + Some("vectors") => match (a.get(2).map(String::as_str), a.get(3)) { + (Some("gen"), Some(d)) => vectors_gen(Path::new(d)), + (Some("verify"), Some(d)) => vectors_verify(Path::new(d)), + _ => usage(), + }, + Some("verify") => match a.get(2) { + Some(p) => match std::fs::read(p).map_err(|e| e.to_string()).and_then(|b| statement(&b)) { + Ok(v) => { + println!("{}", serde_json::to_string_pretty(&v).expect("JSON")); + 0 + } + Err(e) => { + eprintln!("verify: {e}"); + 1 + } + }, + None => usage(), + }, + Some("sign-pod") => match (a.get(2), opt(&a, "--key")) { + (Some(draft), Some(key)) => match sign_pod(Path::new(draft), Path::new(key)) { + Ok(hex_rec) => { + println!("{hex_rec}"); + 0 + } + Err(e) => { + eprintln!("sign-pod: {e}"); + 1 + } + }, + _ => usage(), + }, + Some("serve") => serve(&a[2..]), + _ => usage(), + }; + std::process::exit(code); +} + +fn usage() -> i32 { + eprintln!("usage: wai_stitched_presentation vectors gen|verify | verify | sign-pod --key | serve [--listen ADDR] [--max-body N] [--max-connections N] [--read-timeout-ms MS]"); + 2 +} + +fn opt<'a>(a: &'a [String], flag: &str) -> Option<&'a str> { + a.iter().position(|x| x == flag).and_then(|i| a.get(i + 1)).map(String::as_str) +} + +// ---- the corpus ---- + +fn vectors_gen(d: &Path) -> i32 { + if let Err(e) = std::fs::create_dir_all(d) { + eprintln!("create {}: {e}", d.display()); + return 1; + } + for (name, body) in corpus::generate() { + if let Err(e) = std::fs::write(d.join(&name), body) { + eprintln!("write {name}: {e}"); + return 1; + } + } + println!("wrote {}", d.display()); + 0 +} + +fn vectors_verify(d: &Path) -> i32 { + let mut files = BTreeMap::new(); + let Ok(entries) = std::fs::read_dir(d) else { + eprintln!("read {}", d.display()); + return 1; + }; + for e in entries.flatten() { + let name = e.file_name().to_string_lossy().into_owned(); + if name == "README.md" { + continue; + } + match std::fs::read_to_string(e.path()) { + Ok(s) => { + files.insert(name, s); + } + Err(err) => { + eprintln!("read {name}: {err}"); + return 1; + } + } + } + let generated: BTreeMap = corpus::generate().into_iter().collect(); + let same = files == generated; + println!("{} every file regenerates byte for byte from its seeds, and no other file is there", if same { "PASS" } else { "FAIL" }); + let fails = corpus::verify(&files); + for f in &fails { + println!("FAIL {f}"); + } + println!("{} every vector gets the facts the corpus states", if fails.is_empty() { "PASS" } else { "FAIL" }); + (!same || !fails.is_empty()) as i32 +} + +// ---- a bundle ---- + +fn unhex(s: &str) -> Result, String> { + if !s.len().is_multiple_of(2) || !s.bytes().all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)) { + return Err(format!("not lowercase hexadecimal: {s:.40}")); + } + Ok((0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).expect("hex")).collect()) +} + +fn hex(b: &[u8]) -> String { + b.iter().map(|x| format!("{x:02x}")).collect() +} + +fn hex_list(v: &Value, k: &str) -> Result>, String> { + v.get(k).and_then(Value::as_array).ok_or(format!("{k}: an array"))?.iter().map(|x| unhex(x.as_str().ok_or(format!("{k}: hexadecimal strings"))?)).collect() +} + +fn text_list(v: &Value, k: &str) -> Result>, String> { + match v.get(k) { + None => Ok(Vec::new()), + Some(a) => a.as_array().ok_or(format!("{k}: an array"))?.iter().map(|x| x.as_str().map(|s| s.as_bytes().to_vec()).ok_or(format!("{k}: strings"))).collect(), + } +} + +/// Verify a bundle and state the result (§7.5). +fn statement(b: &[u8]) -> Result { + let v: Value = serde_json::from_slice(b).map_err(|e| format!("a JSON object: {e}"))?; + let records = hex_list(&v, "records")?; + let claims = hex_list(&v, "claims")?; + if claims.len() > sp::MAX_CLAIMS { + return Err(format!("at most {} claims a verification", sp::MAX_CLAIMS)); + } + let manifests = text_list(&v, "manifests")?; + let responses = text_list(&v, "responses")?; + let receipts: Vec = v.get("receipts").and_then(Value::as_array).cloned().unwrap_or_default(); + let creatives: Vec = v + .get("creatives") + .and_then(Value::as_array) + .ok_or("creatives: an array")? + .iter() + .map(|c| { + let objs = c.get("objects").and_then(Value::as_array).ok_or("a creative's objects")?; + let objects = objs + .iter() + .map(|o| { + let h: [u8; 32] = unhex(o.get("content_hash").and_then(Value::as_str).ok_or("content_hash")?)?.try_into().map_err(|_| "a 32-byte content hash")?; + let d = u32::try_from(o.get("duration_ms").and_then(Value::as_u64).ok_or("duration_ms")?).map_err(|_| "duration_ms")?; + Ok(CreativeObject { content_hash: h, duration_ms: d }) + }) + .collect::, String>>()?; + Ok(Creative { objects }) + }) + .collect::>()?; + let issuers: Vec = match v.get("issuers").and_then(Value::as_array) { + None => Vec::new(), + Some(a) => a + .iter() + .map(|i| { + Ok(Issuer { + name: i.get("name").and_then(Value::as_str).ok_or("an issuer's name")?.as_bytes().to_vec(), + token_type: u16::try_from(i.get("token_type").and_then(Value::as_u64).ok_or("token_type")?).map_err(|_| "token_type")?, + keys: hex_list(i, "keys")?, + }) + }) + .collect::>()?, + }; + let signers: Vec<[u8; 32]> = hex_list(&v, "signers")?.into_iter().map(|k| k.try_into().map_err(|_| "a 32-byte signer key".to_string())).collect::>()?; + let got = sp::verify(&sp::Input { records: &records, claims: &claims, manifests: &manifests, responses: &responses, receipts: &receipts, creatives: &creatives, issuers: &issuers, signers: &signers }); + let mut o = corpus::facts_json(&got); + o.insert("capability".into(), json!(sp::CAPABILITY)); + Ok(Value::Object(o)) +} + +// ---- the pod-input signer ---- + +fn field<'a>(v: &'a Value, k: &str) -> Result<&'a Value, String> { + v.get(k).ok_or(format!("{k}: missing")) +} + +fn num(v: &Value, k: &str) -> Result { + field(v, k)?.as_u64().ok_or(format!("{k}: an integer")) +} + +fn text(v: &Value, k: &str) -> Result, String> { + Ok(field(v, k)?.as_str().ok_or(format!("{k}: a string"))?.as_bytes().to_vec()) +} + +fn marker_of(carriage: u8, m: &Value) -> Result { + let s = |k: &str| -> Result, String> { Ok(m.get(k).and_then(Value::as_str).unwrap_or("").as_bytes().to_vec()) }; + Ok(match carriage { + sp::carriage::HLS_DATERANGE => Marker::DateRange { id: text(m, "id")?, start: text(m, "start")? }, + sp::carriage::HLS_INTERSTITIAL => Marker::Interstitial { id: text(m, "id")?, start: text(m, "start")?, index: u16::try_from(num(m, "index")?).map_err(|_| "index")? }, + sp::carriage::DASH_EVENT => Marker::Event { period: s("period")?, scheme: text(m, "scheme")?, value: s("value")?, id: text(m, "id")? }, + _ => return Err("carriage: 1, 2 or 3".into()), + }) +} + +/// A draft (README of live-conformance/stitched/): the record's fields, a marker's fields in +/// place of its digest, and the response by its file or its digest. +fn sign_pod(draft: &Path, key: &Path) -> Result { + let d: Value = serde_json::from_slice(&std::fs::read(draft).map_err(|e| e.to_string())?).map_err(|e| e.to_string())?; + let seed = String::from_utf8(std::fs::read(key).map_err(|e| format!("the key file: {e}"))?).map_err(|_| "the key file: hexadecimal")?; + let seed: [u8; 32] = unhex(seed.trim())?.try_into().map_err(|_| "the key file: a 32-byte seed")?; + let carriage = u8::try_from(num(&d, "carriage")?).map_err(|_| "carriage")?; + let response: [u8; 32] = match (d.get("response_file").and_then(Value::as_str), d.get("response").and_then(Value::as_str)) { + (Some(f), _) => { + let p = draft.parent().unwrap_or(Path::new(".")).join(f); + Sha256::digest(std::fs::read(&p).map_err(|e| format!("{}: {e}", p.display()))?).into() + } + (None, Some(h)) => unhex(h)?.try_into().map_err(|_| "response: 32 bytes")?, + _ => return Err("response_file or response".into()), + }; + let session: [u8; 16] = match d.get("session").and_then(Value::as_str) { + None => [0; 16], + Some(h) => unhex(h)?.try_into().map_err(|_| "session: 16 bytes")?, + }; + let f = d.get("frame").cloned().unwrap_or(Value::Null); + let fnum = |k: &str| f.get(k).and_then(Value::as_u64).unwrap_or(0); + let frame = Frame { + kind: fnum("kind") as u8, + width: fnum("width") as u16, + height: fnum("height") as u16, + corner: fnum("corner") as u8, + window_w: fnum("window_w") as u16, + window_h: fnum("window_h") as u16, + gap: fnum("gap") as u16, + aspect: fnum("aspect") as u8, + }; + let r = field(&d, "requirement")?; + let requirement = Requirement { + require: num(r, "require")? as u8, + rule: num(r, "rule")? as u8, + token_type: num(r, "token_type")? as u16, + issuer: r.get("issuer").and_then(Value::as_str).unwrap_or("").as_bytes().to_vec(), + }; + let currency: [u8; 3] = text(&d, "currency")?.try_into().map_err(|_| "currency: three letters")?; + let ads = field(&d, "ads")? + .as_array() + .ok_or("ads: an array")? + .iter() + .enumerate() + .map(|(k, a)| { + let evidence: [u8; 32] = match a.get("evidence").and_then(Value::as_str) { + None => [0; 32], + Some(h) => unhex(h)?.try_into().map_err(|_| "evidence: 32 bytes")?, + }; + let creative_hash: [u8; 32] = unhex(a.get("creative_hash").and_then(Value::as_str).ok_or("creative_hash")?)?.try_into().map_err(|_| "creative_hash: 32 bytes")?; + Ok(PodAd { + slot: k as u8, + marker: marker_of(carriage, field(a, "marker")?)?.digest(), + ad_id: text(a, "ad_id")?, + serving_id: text(a, "serving_id")?, + creative_id: a.get("creative_id").and_then(Value::as_str).unwrap_or("").as_bytes().to_vec(), + registry: a.get("registry").and_then(Value::as_str).unwrap_or("").as_bytes().to_vec(), + creative_hash, + duration_ms: u32::try_from(num(a, "duration_ms")?).map_err(|_| "duration_ms")?, + price_micros: num(a, "price_micros")?, + price_source: num(a, "price_source")? as u8, + evidence, + }) + }) + .collect::, String>>()?; + let rec = PodInput { + carriage, + session, + avail_id: text(&d, "avail_id")?, + avail_ms: u32::try_from(num(&d, "avail_ms")?).map_err(|_| "avail_ms")?, + response_format: num(&d, "response_format")? as u8, + response, + id_kind: num(&d, "id_kind")? as u8, + id_attr: d.get("id_attr").and_then(Value::as_str).unwrap_or("").as_bytes().to_vec(), + currency, + exponent: num(&d, "exponent")? as u8, + frame, + requirement, + ads, + signer: [0; 32], + sig: [0; 64], + }; + if !rec.rules_hold() { + return Err("the draft breaks a rule of §4.4 (pod_input_malformed)".into()); + } + Ok(hex(&rec.sign(&SigningKey::from_bytes(&seed)).to_bytes())) +} + +// ---- the service ---- + +struct Config { + max_body: usize, + read_timeout: Duration, +} + +const MAX_HEAD: usize = 16 * 1024; + +fn respond(mut s: &TcpStream, status: u16, body: &Value) { + let reason = match status { + 200 => "OK", + 400 => "Bad Request", + 404 => "Not Found", + 405 => "Method Not Allowed", + 408 => "Request Timeout", + 413 => "Content Too Large", + 431 => "Request Header Fields Too Large", + 503 => "Service Unavailable", + _ => "Error", + }; + let b = serde_json::to_vec(body).expect("JSON"); + let head = format!("HTTP/1.1 {status} {reason}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nCache-Control: no-store\r\nConnection: close\r\n\r\n", b.len()); + let _ = s.write_all(head.as_bytes()).and_then(|_| s.write_all(&b)); +} + +fn handle(cfg: &Config, s: TcpStream) { + let deadline = Instant::now() + cfg.read_timeout; + let mut buf = Vec::new(); + let mut chunk = [0u8; 8192]; + let head_end = loop { + if let Some(i) = buf.windows(4).position(|w| w == b"\r\n\r\n") { + break i + 4; + } + if buf.len() > MAX_HEAD { + return respond(&s, 431, &json!({"error": "request head too large"})); + } + let left = deadline.saturating_duration_since(Instant::now()); + if left.is_zero() || s.set_read_timeout(Some(left)).is_err() { + return respond(&s, 408, &json!({"error": "request head not received in time"})); + } + match (&s).read(&mut chunk) { + Ok(0) | Err(_) => return respond(&s, 408, &json!({"error": "request head not received in time"})), + Ok(n) => buf.extend_from_slice(&chunk[..n]), + } + }; + let head = String::from_utf8_lossy(&buf[..head_end]).into_owned(); + let mut lines = head.split("\r\n"); + let mut first = lines.next().unwrap_or("").split(' '); + let (method, path) = (first.next().unwrap_or(""), first.next().unwrap_or("")); + let length = lines.filter_map(|l| l.split_once(':')).find(|(k, _)| k.eq_ignore_ascii_case("content-length")).and_then(|(_, v)| v.trim().parse::().ok()); + match (method, path) { + ("GET", "/v1/health") => respond(&s, 200, &json!({"ok": true, "capability": sp::CAPABILITY})), + ("POST", "/v1/verify") => { + let Some(n) = length else { return respond(&s, 400, &json!({"error": "Content-Length required"})) }; + if n > cfg.max_body { + return respond(&s, 413, &json!({"error": format!("a bundle of at most {} bytes", cfg.max_body)})); + } + let mut body = buf[head_end..].to_vec(); + while body.len() < n { + let left = deadline.saturating_duration_since(Instant::now()); + if left.is_zero() || s.set_read_timeout(Some(left)).is_err() { + return respond(&s, 408, &json!({"error": "request body not received in time"})); + } + match (&s).read(&mut chunk) { + Ok(0) | Err(_) => return respond(&s, 408, &json!({"error": "request body not received in time"})), + Ok(k) => body.extend_from_slice(&chunk[..k]), + } + } + body.truncate(n); + match statement(&body) { + Ok(v) => respond(&s, 200, &v), + Err(e) => respond(&s, 400, &json!({"error": e})), + } + } + (_, "/v1/health" | "/v1/verify") => respond(&s, 405, &json!({"error": "method not allowed"})), + _ => respond(&s, 404, &json!({"error": "not found"})), + } +} + +fn serve(a: &[String]) -> i32 { + let listen = opt(a, "--listen").unwrap_or("127.0.0.1:9690"); + let addr: SocketAddr = match listen.parse() { + Ok(x) => x, + Err(_) => { + eprintln!("--listen: an address and port"); + return 2; + } + }; + if !addr.ip().is_loopback() { + eprintln!("--listen: a loopback address; a reverse proxy publishes the service"); + return 2; + } + let num = |k: &str, d: usize| opt(a, k).map_or(Some(d), |v| v.parse().ok()); + let (Some(max_body), Some(max_conn), Some(read_ms)) = (num("--max-body", 8 << 20), num("--max-connections", 32), num("--read-timeout-ms", 10_000)) else { + eprintln!("--max-body, --max-connections and --read-timeout-ms take integers"); + return 2; + }; + let listener = match TcpListener::bind(addr) { + Ok(l) => l, + Err(e) => { + eprintln!("bind {addr}: {e}"); + return 1; + } + }; + eprintln!("wai_stitched_presentation: serving on http://{addr}"); + let cfg = Arc::new(Config { max_body, read_timeout: Duration::from_millis(read_ms as u64) }); + let open = Arc::new(AtomicUsize::new(0)); + for stream in listener.incoming().flatten() { + if open.load(Ordering::SeqCst) >= max_conn { + respond(&stream, 503, &json!({"error": "busy"})); + continue; + } + open.fetch_add(1, Ordering::SeqCst); + let (cfg, open) = (Arc::clone(&cfg), Arc::clone(&open)); + std::thread::spawn(move || { + handle(&cfg, stream); + open.fetch_sub(1, Ordering::SeqCst); + }); + } + 0 +} diff --git a/wai/wai-rs/src/lib.rs b/wai/wai-rs/src/lib.rs index 38b0b7fc4..6979b55ca 100644 --- a/wai/wai-rs/src/lib.rs +++ b/wai/wai-rs/src/lib.rs @@ -548,6 +548,16 @@ pub mod live_watermark; /// cbindgen:ignore #[cfg(feature = "live_ads")] pub mod live_ads; +// Stitched ad presentation (extensions/stitched-presentation.md): `wai.video.stitched_presentation` +// — a player's signed claim (WPS1) that it presented an ad another party stitched into an HLS or +// DASH stream, named by the marker the stream carries (a date range, an interstitial's asset, an +// event) in the manifest it read; the signed pod-input record (WPI1) of the ad server's response +// digest, the chosen creatives, prices and the avail; composited frames from live-compose §5.9; +// and the presentations, quartiles and spend recomputed from the claims that verify. Rust-only, +// like the live modules: its constants must not become macros in include/wai.h. +/// cbindgen:ignore +#[cfg(feature = "stitched_presentation")] +pub mod stitched_presentation; // Live provenance (extensions/live-provenance.md): `wai.live.provenance` — the live video // provenance of C2PA 2.4 §19 for a room's CMAF tracks and a device's CMAF take: per-segment // manifests chained by manifest identifier, Verifiable Segment Info under rotating session keys, diff --git a/wai/wai-rs/src/live_ads/presentation_corpus.rs b/wai/wai-rs/src/live_ads/presentation_corpus.rs index 2628ccf8f..7f83419a3 100644 --- a/wai/wai-rs/src/live_ads/presentation_corpus.rs +++ b/wai/wai-rs/src/live_ads/presentation_corpus.rs @@ -33,12 +33,12 @@ pub const RFC9578_CHALLENGE: &str = "0002000e6973737565722e6578616d706c65208e7ac /// The same vector's token. pub const RFC9578_TOKEN: &str = "0002aa72019d1f951df197021ce63876fe8b0a02dc1c31a12b0a2dd1508d07827f055969f643b4cfda5196d4aa86aeb5368834f4f06de46950ed435b3b81bd036d44ca572f8982a9ca248a3056186322d93ca147266121ddeb5632c07f1f71cd2708bc6a21b533d07294b5e900faf5537dd3eb33cee4e08c9670d1e5358fd184b0e00c637174f5206b14c7bb0e724ebf6b56271e5aa2ed94c051c4a433d302b23bc52460810d489fb050f9de5c868c6c1b06e3849fd087629f704cc724bc0d0984d5c339686fcdd75f9a9cdd25f37f855f6f4c584d84f716864f546b696d620c5bd41a811498de84ff9740ba3003ba2422d26b91eb745c084758974642a42078201543246ddb58030ea8e722376aa82484dca9610a8fb7e018e396165462e17a03e40ea7e128c090a911ecc708066cb201833010c1ebd4e910fc8e27a1be467f78671836a508257123a45e4e0ae2180a434bd1037713466347a8ebe46439d3da1970"; /// The modulus of that issuer key, big-endian. -const ISSUER_N: &str = "cb1aed6b6a95f5b1ce013a4cfcab25b94b2e64a23034e4250a7eab43c0df3a8c12993af12b111908d4b471bec31d4b6c9ad9cdda90612a2ee903523e6de5a224d6b02f09e5c374d0cfe01d8f529c500a78a2f67908fa682b5a2b430c81eaf1af72d7b5e794fc98a3139276879757ce453b526ef9bf6ceb99979b8423b90f4461a22af37aab0cf5733f7597abe44d31c732db68a181c6cbbe607d8c0e52e0655fd9996dc584eca0be87afbcd78a337d17b1dba9e828bbd81e291317144e7ff89f55619709b096cbb9ea474cead264c2073fe49740c01f00e109106066983d21e5f83f086e2e823c879cd43cef700d2a352a9babd612d03cad02db134b7e225a5f"; +pub(crate) const ISSUER_N: &str = "cb1aed6b6a95f5b1ce013a4cfcab25b94b2e64a23034e4250a7eab43c0df3a8c12993af12b111908d4b471bec31d4b6c9ad9cdda90612a2ee903523e6de5a224d6b02f09e5c374d0cfe01d8f529c500a78a2f67908fa682b5a2b430c81eaf1af72d7b5e794fc98a3139276879757ce453b526ef9bf6ceb99979b8423b90f4461a22af37aab0cf5733f7597abe44d31c732db68a181c6cbbe607d8c0e52e0655fd9996dc584eca0be87afbcd78a337d17b1dba9e828bbd81e291317144e7ff89f55619709b096cbb9ea474cead264c2073fe49740c01f00e109106066983d21e5f83f086e2e823c879cd43cef700d2a352a9babd612d03cad02db134b7e225a5f"; /// Its private exponent, big-endian: a test key the RFC publishes, used here to sign the /// corpus's tokens. -const ISSUER_D: &str = "2f309b7738b58cf779f0c915f822dfc9f490dbeadf1e7adfac578ffd5703c6bba2da9d5a49725889b7ba112f8c6ff30551d9473e000bc35c50e167ab7fa73a0c2b21965c4b59257c1ac06cbdcf28e863f6718ea8c29043c1c6da84629490fd0491bf52d172c959c1921b96949c725747b8a7c7871995fa0b9eb1107ba854c6c32a6f4747c5d1c304c7467d55b080aa233a3a52ffdf26114cbb541ffa6b696b7a096f8dcff69ca08ab58efb0fb753aa5185c73001696bbce8c099e3f435dcbbfe05e7dbaad23db2a659c23add521407f150b1af2d9054c4164229d6840b7a32a4f8481493ae8959cf0aea8def0c3c9f75b9a34c75718b7072c8dbab17cefeca49"; +pub(crate) const ISSUER_D: &str = "2f309b7738b58cf779f0c915f822dfc9f490dbeadf1e7adfac578ffd5703c6bba2da9d5a49725889b7ba112f8c6ff30551d9473e000bc35c50e167ab7fa73a0c2b21965c4b59257c1ac06cbdcf28e863f6718ea8c29043c1c6da84629490fd0491bf52d172c959c1921b96949c725747b8a7c7871995fa0b9eb1107ba854c6c32a6f4747c5d1c304c7467d55b080aa233a3a52ffdf26114cbb541ffa6b696b7a096f8dcff69ca08ab58efb0fb753aa5185c73001696bbce8c099e3f435dcbbfe05e7dbaad23db2a659c23add521407f150b1af2d9054c4164229d6840b7a32a4f8481493ae8959cf0aea8def0c3c9f75b9a34c75718b7072c8dbab17cefeca49"; /// The name the corpus's ad policies give the issuer. -const ISSUER_NAME: &[u8] = b"issuer.example"; +pub(crate) const ISSUER_NAME: &[u8] = b"issuer.example"; fn unhex(s: &str) -> Vec { base::unhex(s).expect("a hexadecimal constant") diff --git a/wai/wai-rs/src/live_compose/corpus.rs b/wai/wai-rs/src/live_compose/corpus.rs index 09a1b3f97..3e225f7c4 100644 --- a/wai/wai-rs/src/live_compose/corpus.rs +++ b/wai/wai-rs/src/live_compose/corpus.rs @@ -438,6 +438,35 @@ fn layout_cases() -> Vec { pip_featured[0] = e(0, Some((1280, 720)), star); out.push(case("pip_over_featured", "a PiP participant over a grid with a featured participant (§5.4, §5.5)", pip.clone(), pip_featured)); out.push(case("pip_limit", "a PiP participant and five others, at most four tiles: the window and three grid tiles (§5.2)", Params { max_tiles: 4, ..pip.clone() }, with_pip(6, 5))); + // Ad frames (§5.9). + let squeeze = Params { layout: lkind::SQUEEZE, pip_attr: "ad".into(), corner: corner::TOP_LEFT, pip_width: 1440, pip_height: 810, max_tiles: 2, ..g.clone() }; + let with_ad = |n: u8, at: u8| -> Vec { + let mut v = plain(n); + v[at as usize] = e(at, Some((1280, 720)), &[("ad", "true")]); + v + }; + for (c, cn) in [(corner::TOP_LEFT, "top_left"), (corner::TOP_RIGHT, "top_right"), (corner::BOTTOM_LEFT, "bottom_left"), (corner::BOTTOM_RIGHT, "bottom_right")] { + out.push(case( + format!("squeeze_{cn}"), + format!("a squeeze: the program in a 1440 × 810 window in the {} corner over the ad, which shows in the L around it; a third participant has no tile (§5.9)", cn.replace('_', " ")), + Params { corner: c, ..squeeze.clone() }, + with_ad(3, 0), + )); + } + let double = Params { layout: lkind::DOUBLE_BOX, pip_width: 0, pip_height: 0, gap: 16, ..squeeze.clone() }; + out.push(case("double_box_left", "a double box, the program on the left: two 16:9 tiles centred in boxes 16 pixels apart (§5.9)", double.clone(), with_ad(2, 1))); + out.push(case("double_box_right", "a double box, the program on the right (§5.9)", Params { corner: corner::TOP_RIGHT, ..double.clone() }, with_ad(2, 0))); + out.push(case("double_box_auto", "a double box under aspect auto: each tile its whole box, the ad's source contained by its fill (§5.9)", Params { aspect: aspect::AUTO, fill: fill::CONTAIN, ..double.clone() }, with_ad(2, 1))); + out.push(case("squeeze_program_alone", "a squeeze with no ad participant: the program alone, over the whole canvas (§5.9)", squeeze.clone(), plain(2))); + out.push(case("squeeze_ad_alone", "a squeeze with the ad participant alone: the ad over the whole canvas (§5.9)", squeeze.clone(), with_ad(1, 0))); + let mut featured_program = with_ad(3, 0); + featured_program[2] = e(2, Some((1280, 720)), star); + out.push(case("squeeze_featured_program", "a squeeze whose program participant is the featured one, not the first in order (§5.2, §5.9)", Params { corner: corner::BOTTOM_RIGHT, ..squeeze.clone() }, featured_program)); + out.push(case("squeeze_empty", "a squeeze with no participant: no tile (§5.9)", squeeze.clone(), vec![])); + out.push(bad("squeeze_full_width", "a squeeze whose window is as wide as the canvas, which leaves no L (§3.3)", Params { pip_width: 1920, ..squeeze.clone() }, with_ad(2, 0), Reason::ComposeInvalid)); + out.push(bad("squeeze_no_ad_attr", "an ad frame with no attribute to mark the ad (§3.3)", Params { pip_attr: String::new(), ..squeeze.clone() }, plain(2), Reason::ComposeInvalid)); + out.push(bad("double_box_corner", "a double box whose side is a bottom corner (§3.3)", Params { corner: corner::BOTTOM_LEFT, ..double.clone() }, with_ad(2, 0), Reason::ComposeInvalid)); + out.push(bad("ad_frame_three_tiles", "an ad frame of three tiles (§3.3)", Params { max_tiles: 3, ..squeeze.clone() }, with_ad(3, 0), Reason::ComposeInvalid)); // Attributes that do not read. for (name, k, v) in [ ("order_leading_zero", "order", "01"), diff --git a/wai/wai-rs/src/live_compose/layout.rs b/wai/wai-rs/src/live_compose/layout.rs index 73d267ce7..d37b66e6c 100644 --- a/wai/wai-rs/src/live_compose/layout.rs +++ b/wai/wai-rs/src/live_compose/layout.rs @@ -31,6 +31,14 @@ pub const SAMPLE_RATES: core::ops::RangeInclusive = 8_000..=192_000; pub mod kind { pub const GRID: u8 = 1; pub const PIP: u8 = 2; + /// An ad frame (§5.9): the program in a window in one corner, the ad in the L around it. + pub const SQUEEZE: u8 = 3; + /// An ad frame (§5.9): the program and the ad in two boxes side by side. + pub const DOUBLE_BOX: u8 = 4; + /// Is `k` one of the ad frames of §5.9? + pub fn is_ad_frame(k: u8) -> bool { + k == SQUEEZE || k == DOUBLE_BOX + } } /// The tile aspects (§5.2): `AUTO` lays tiles out as `VIDEO` does and lets each fill its @@ -70,11 +78,17 @@ pub mod place { pub const GRID: u8 = 0; pub const FEATURED: u8 = 1; pub const PIP: u8 = 2; + /// The program of an ad frame (§5.9). + pub const PROGRAM: u8 = 3; + /// The ad of an ad frame (§5.9). + pub const AD: u8 = 4; /// Each place's name, as the corpus writes it. pub fn name(p: u8) -> &'static str { match p { FEATURED => "featured", PIP => "pip", + PROGRAM => "program", + AD => "ad", _ => "grid", } } @@ -165,7 +179,7 @@ impl Params { pub fn check_fields(&self) -> Result<(), Refusal> { let bad = Err(Refusal::ComposeInvalid); let (w, h, g) = (self.width as u64, self.height as u64, self.gap as u64); - if !matches!(self.layout, kind::GRID | kind::PIP) || self.aspect > aspect::PORTRAIT || self.fill > fill::CONTAIN { + if !matches!(self.layout, kind::GRID | kind::PIP | kind::SQUEEZE | kind::DOUBLE_BOX) || self.aspect > aspect::PORTRAIT || self.fill > fill::CONTAIN { return bad; } for k in [&self.featured_attr, &self.order_attr, &self.pip_attr, &self.gain_attr, &self.pan_attr] { @@ -177,6 +191,14 @@ impl Params { if !self.pip_attr.is_empty() || self.corner != 0 || self.pip_width != 0 || self.pip_height != 0 || self.offset != 0 || self.behaviour != 0 { return bad; } + } else if kind::is_ad_frame(self.layout) { + // §5.9: the ad participant's attribute, two tiles, and the frame's own fields. + if self.pip_attr.is_empty() || self.max_tiles != 2 || self.offset != 0 || self.behaviour != 0 { + return bad; + } + if !frame_fields_ok(self.width, self.height, self.layout, self.corner, self.pip_width, self.pip_height, self.gap) { + return bad; + } } else { let o = self.offset as u64; if !(corner::TOP_LEFT..=corner::BOTTOM_RIGHT).contains(&self.corner) @@ -462,6 +484,9 @@ pub fn layout(p: &Params, entrants: &[Entrant]) -> Result { .collect(); // §5.2: the tiles' participants. let shown: Vec<&Member> = ms.iter().filter(|m| !p.omit_stopped || m.e.video.is_some()).collect(); + if kind::is_ad_frame(p.layout) { + return Ok(Layout { order, tiles: frame_tiles(p, shown), voices }); + } let pip = if p.layout == kind::PIP { shown.iter().position(|m| m.r.pip) } else { None }; let pip_member = pip.map(|i| shown[i]); let rest: Vec<&Member> = shown.iter().enumerate().filter(|(i, _)| Some(*i) != pip).map(|(_, m)| *m).collect(); @@ -495,7 +520,7 @@ fn featured_first<'a, 'b>(mut members: Vec<&'b Member<'a>>, cap: usize) -> (Vec< fn video_of(p: &Params, m: &Member, tile: Rect, place: u8) -> Option