use std::path::{Path, PathBuf};
pub const CONTEXT_RUNGS_FILE: &str = "evidence/release/context-rungs.json";
pub const CONTEXT_SCHEMA: &str = "apr-release-context-rungs/v1";
pub const KERNEL_EVIDENCE_DIR: &str = "evidence/dogfood/kernels";
pub const KERNEL_SCHEMA: &str = "apr-kernel-diff-receipt/v1";
pub const TOKENIZER_EVIDENCE_DIR: &str = "evidence/dogfood/tokenizer";
pub const TOKENIZER_SCHEMA: &str = "apr-tokenizer-parity-receipt/v1";
pub const SURFACE_RATCHET_FILE: &str = "evidence/release/surface-ratchet.json";
pub const SURFACE_RATCHET_SCHEMA: &str = "apr-release-surface-ratchet/v1";
pub const CONSUMER_MAX: &str = "consumer-max";
pub const DECLARED: &str = "declared";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Subject {
pub version: String,
pub commit: String,
pub receipts_commit: Option<String>,
pub receipts_dir: Option<PathBuf>,
pub kernel_receipts_dir: Option<PathBuf>,
pub dogfood_receipt: Option<PathBuf>,
pub tokenizer_receipts_dir: Option<PathBuf>,
pub surface: Option<PathBuf>,
pub crux_receipts_dir: Option<PathBuf>,
}
impl Subject {
pub fn new(version: &str, commit: &str) -> Result<Self, ReleaseError> {
if version.trim().is_empty() {
return Err(ReleaseError::Subject("--release-version is empty".into()));
}
Ok(Self {
version: version.trim().to_string(),
commit: full_sha("--release-commit", commit)?,
receipts_commit: None,
receipts_dir: None,
kernel_receipts_dir: None,
dogfood_receipt: None,
tokenizer_receipts_dir: None,
surface: None,
crux_receipts_dir: None,
})
}
pub fn with_receipts_commit(mut self, sha: &str) -> Result<Self, ReleaseError> {
self.receipts_commit = Some(full_sha("--receipts-commit", sha)?);
Ok(self)
}
#[must_use]
pub fn measured_commit(&self) -> &str {
self.receipts_commit.as_deref().unwrap_or(&self.commit)
}
#[must_use]
pub fn model_dir(&self, root: &Path) -> PathBuf {
self.receipts_dir.clone().unwrap_or_else(|| {
root.join(crate::ontology::receipts::EVIDENCE_DIR)
.join(&self.version)
})
}
#[must_use]
pub fn tokenizer_dir(&self, root: &Path) -> PathBuf {
self.tokenizer_receipts_dir
.clone()
.unwrap_or_else(|| root.join(TOKENIZER_EVIDENCE_DIR).join(&self.version))
}
#[must_use]
pub fn crux_dir(&self, root: &Path) -> PathBuf {
self.crux_receipts_dir
.clone()
.unwrap_or_else(|| root.join("evidence/crux").join(&self.version))
}
#[must_use]
pub fn kernel_dir(&self, root: &Path) -> PathBuf {
self.kernel_receipts_dir
.clone()
.unwrap_or_else(|| root.join(KERNEL_EVIDENCE_DIR).join(&self.version))
}
}
fn full_sha(flag: &str, s: &str) -> Result<String, ReleaseError> {
let s = s.trim().to_ascii_lowercase();
if s.len() == 40 && s.bytes().all(|b| b.is_ascii_hexdigit()) {
Ok(s)
} else {
Err(ReleaseError::Subject(format!(
"{flag} {s:?} is not a full 40-hex commit (a prefix is refused, never matched)"
)))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReleaseError {
Subject(String),
Receipt(crate::ontology::receipts::ReceiptError),
Input { file: String, what: String },
}
impl std::fmt::Display for ReleaseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Subject(w) => write!(f, "release subject: {w}"),
Self::Receipt(e) => write!(f, "receipt {e}"),
Self::Input { file, what } => write!(f, "{file}: {what}"),
}
}
}
impl std::error::Error for ReleaseError {}
fn input_err(file: &str, what: impl Into<String>) -> ReleaseError {
ReleaseError::Input {
file: file.to_string(),
what: what.into(),
}
}
fn read_json(path: &Path, name: &str, want: &str) -> Result<serde_json::Value, ReleaseError> {
let text =
std::fs::read_to_string(path).map_err(|e| input_err(name, format!("unreadable: {e}")))?;
let v: serde_json::Value =
serde_json::from_str(&text).map_err(|e| input_err(name, format!("not JSON: {e}")))?;
let schema = v
.get("schema")
.and_then(serde_json::Value::as_str)
.unwrap_or("");
if schema != want {
return Err(input_err(
name,
format!("schema {schema:?} is not {want} — refused by name"),
));
}
Ok(v)
}
fn str_of(v: &serde_json::Value, key: &str) -> Option<String> {
v.get(key)
.and_then(serde_json::Value::as_str)
.map(str::to_string)
}
fn list<'a>(v: &'a serde_json::Value, key: &str) -> impl Iterator<Item = &'a serde_json::Value> {
v.get(key)
.and_then(serde_json::Value::as_array)
.into_iter()
.flatten()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Consumer {
pub name: String,
pub max_prompt_tokens: Option<u64>,
pub basis: String,
pub source: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContextRung {
pub id: String,
pub tokens: Option<u64>,
pub long: bool,
pub derived_from: Vec<String>,
pub unmeasured_consumers: Vec<String>,
}
pub type ContextDecl = (Vec<ContextRung>, Vec<Consumer>);
pub fn read_context_rungs(root: &Path) -> Result<Option<ContextDecl>, ReleaseError> {
let path = root.join(CONTEXT_RUNGS_FILE);
if !path.exists() {
return Ok(None);
}
let v = read_json(&path, CONTEXT_RUNGS_FILE, CONTEXT_SCHEMA)?;
let consumers: Vec<Consumer> = list(&v, "consumers")
.map(|c| Consumer {
name: str_of(c, "consumer").unwrap_or_default(),
max_prompt_tokens: c
.get("max_prompt_tokens")
.and_then(serde_json::Value::as_u64),
basis: str_of(c, "basis").unwrap_or_default(),
source: str_of(c, "source").unwrap_or_default(),
})
.collect();
if let Some(r) = list(&v, "rungs").find(|r| str_of(r, "id").as_deref() == Some(DECLARED)) {
return Err(input_err(
CONTEXT_RUNGS_FILE,
format!("rung {r} redeclares `{DECLARED}`, which is each model's own GGUF context_length and is never written down"),
));
}
let rungs = list(&v, "rungs")
.map(|r| ContextRung {
id: str_of(r, "id").unwrap_or_default(),
tokens: r.get("tokens").and_then(serde_json::Value::as_u64),
long: r
.get("long")
.and_then(serde_json::Value::as_bool)
.unwrap_or(false),
derived_from: list(r, "derived_from")
.filter_map(serde_json::Value::as_str)
.map(str::to_string)
.collect(),
unmeasured_consumers: Vec::new(),
})
.collect();
Ok(Some((derive_rungs(rungs, &consumers), consumers)))
}
#[must_use]
pub fn derive_rungs(rungs: Vec<ContextRung>, consumers: &[Consumer]) -> Vec<ContextRung> {
rungs
.into_iter()
.map(|mut r| {
if r.id == CONSUMER_MAX {
r.tokens = consumers.iter().filter_map(|c| c.max_prompt_tokens).max();
r.derived_from = consumers
.iter()
.filter(|c| !c.source.is_empty())
.map(|c| format!("{}: {}", c.name, c.source))
.collect();
r.unmeasured_consumers = consumers
.iter()
.filter(|c| c.max_prompt_tokens.is_none())
.map(|c| c.name.clone())
.collect();
}
r
})
.collect()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Dogfood {
pub file: String,
pub verdict: String,
pub commit: String,
pub version: String,
}
pub fn read_dogfood(path: &Path) -> Result<Dogfood, ReleaseError> {
let name = path.display().to_string();
let text =
std::fs::read_to_string(path).map_err(|e| input_err(&name, format!("unreadable: {e}")))?;
let v: serde_json::Value =
serde_json::from_str(&text).map_err(|e| input_err(&name, format!("not JSON: {e}")))?;
Ok(Dogfood {
file: name,
verdict: str_of(&v, "verdict").unwrap_or_default(),
commit: str_of(&v, "commit")
.unwrap_or_default()
.to_ascii_lowercase(),
version: str_of(&v, "version").unwrap_or_default(),
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Dispatch {
pub sha256: Option<String>,
pub file: String,
pub kernels: Vec<(String, String)>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct KernelRow {
pub kernel: String,
pub quant: String,
pub reference: String,
pub max_err: Option<f64>,
pub index_mismatch: Option<u64>,
pub tie_margin: Option<f64>,
pub bound: Option<f64>,
pub bound_source: String,
pub verdict: String,
pub reason: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct KernelReceipt {
pub file: String,
pub host: String,
pub version: String,
pub apr_sha: Option<String>,
pub sm: String,
pub dispatch: Vec<Dispatch>,
pub rows: Vec<KernelRow>,
}
pub fn read_kernel_receipts(dir: &Path, root: &Path) -> Result<Vec<KernelReceipt>, ReleaseError> {
let mut files = Vec::new();
walk_json(dir, &mut files);
files.sort();
files
.iter()
.map(|f| {
let name = f
.strip_prefix(root)
.unwrap_or(f)
.to_string_lossy()
.replace('\\', "/");
let v = read_json(f, &name, KERNEL_SCHEMA)?;
Ok(parse_kernel_receipt(&name, &v))
})
.collect()
}
fn walk_json(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(rd) = std::fs::read_dir(dir) else {
return;
};
for e in rd.flatten() {
let p = e.path();
if p.is_dir() {
walk_json(&p, out);
} else if p.extension().and_then(|x| x.to_str()) == Some("json") {
out.push(p);
}
}
}
fn parse_kernel_receipt(file: &str, v: &serde_json::Value) -> KernelReceipt {
KernelReceipt {
file: file.to_string(),
host: str_of(v, "host").unwrap_or_default(),
version: str_of(v, "version").unwrap_or_default(),
apr_sha: str_of(v, "apr_sha").map(|s| s.to_ascii_lowercase()),
sm: str_of(v, "sm").unwrap_or_default(),
dispatch: list(v, "dispatch")
.map(|d| Dispatch {
sha256: str_of(d, "sha256").map(|s| s.to_ascii_lowercase()),
file: str_of(d, "file").unwrap_or_default(),
kernels: list(d, "kernels").filter_map(kernel_key).collect(),
})
.collect(),
rows: list(v, "rows")
.map(|r| KernelRow {
kernel: str_of(r, "kernel").unwrap_or_default(),
quant: str_of(r, "quant").unwrap_or_default().to_ascii_lowercase(),
reference: str_of(r, "reference").unwrap_or_default(),
max_err: r.get("max_err").and_then(serde_json::Value::as_f64),
index_mismatch: r.get("index_mismatch").and_then(serde_json::Value::as_u64),
tie_margin: r.get("tie_margin").and_then(serde_json::Value::as_f64),
bound: r.get("bound").and_then(serde_json::Value::as_f64),
bound_source: str_of(r, "bound_source").unwrap_or_default(),
verdict: str_of(r, "verdict")
.unwrap_or_default()
.to_ascii_lowercase(),
reason: str_of(r, "reason").unwrap_or_default(),
})
.collect(),
}
}
fn kernel_key(k: &serde_json::Value) -> Option<(String, String)> {
match k {
serde_json::Value::String(s) => Some((s.clone(), String::new())),
serde_json::Value::Object(_) => Some((
str_of(k, "kernel")?,
str_of(k, "quant").unwrap_or_default().to_ascii_lowercase(),
)),
_ => None,
}
}
impl KernelRow {
#[must_use]
pub fn within_bound(&self) -> bool {
match self.index_mismatch {
Some(0) => true,
Some(_) => matches!((self.tie_margin, self.bound), (Some(t), Some(b)) if t <= b),
None => matches!((self.max_err, self.bound), (Some(e), Some(b)) if e <= b),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokRow {
pub sha256: Option<String>,
pub file: String,
pub family: String,
pub corpus_sha256: String,
pub comparator: String,
pub comparator_sha: String,
pub tokens_compared: Option<u64>,
pub mismatches: Option<u64>,
pub roundtrip_ok: Option<bool>,
pub verdict: String,
pub reason: String,
}
impl TokRow {
#[must_use]
pub fn exact(&self) -> bool {
self.mismatches == Some(0) && self.tokens_compared.is_some_and(|n| n > 0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TokReceipt {
pub file: String,
pub version: String,
pub apr_sha: Option<String>,
pub rows: Vec<TokRow>,
}
pub fn read_tokenizer_receipts(dir: &Path, root: &Path) -> Result<Vec<TokReceipt>, ReleaseError> {
let mut files = Vec::new();
walk_json(dir, &mut files);
files.sort();
files
.iter()
.map(|f| {
let name = f
.strip_prefix(root)
.unwrap_or(f)
.to_string_lossy()
.replace('\\', "/");
let v = read_json(f, &name, TOKENIZER_SCHEMA)?;
Ok(TokReceipt {
file: name,
version: str_of(&v, "version").unwrap_or_default(),
apr_sha: str_of(&v, "apr_sha").map(|s| s.to_ascii_lowercase()),
rows: list(&v, "rows").map(parse_tok_row).collect(),
})
})
.collect()
}
fn parse_tok_row(r: &serde_json::Value) -> TokRow {
TokRow {
sha256: str_of(r, "sha256").map(|s| s.to_ascii_lowercase()),
file: str_of(r, "file").unwrap_or_default(),
family: str_of(r, "family").unwrap_or_default(),
corpus_sha256: str_of(r, "corpus_sha256").unwrap_or_default(),
comparator: str_of(r, "comparator").unwrap_or_default(),
comparator_sha: str_of(r, "comparator_sha").unwrap_or_default(),
tokens_compared: r.get("tokens_compared").and_then(serde_json::Value::as_u64),
mismatches: r.get("mismatches").and_then(serde_json::Value::as_u64),
roundtrip_ok: r.get("roundtrip_ok").and_then(serde_json::Value::as_bool),
verdict: str_of(r, "verdict")
.unwrap_or_default()
.to_ascii_lowercase(),
reason: str_of(r, "reason").unwrap_or_default(),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SurfaceRatchet {
pub unknown_args: u64,
pub stdin_undeclared: u64,
}
pub fn read_surface_ratchet(root: &Path) -> Result<Option<SurfaceRatchet>, ReleaseError> {
let path = root.join(SURFACE_RATCHET_FILE);
if !path.exists() {
return Ok(None);
}
let v = read_json(&path, SURFACE_RATCHET_FILE, SURFACE_RATCHET_SCHEMA)?;
let n = |k: &str| {
v.get(k)
.and_then(serde_json::Value::as_u64)
.ok_or_else(|| input_err(SURFACE_RATCHET_FILE, format!("`{k}` is not a count")))
};
Ok(Some(SurfaceRatchet {
unknown_args: n("unknown_args")?,
stdin_undeclared: n("stdin_undeclared")?,
}))
}
#[cfg(test)]
mod tests {
use super::*;
const SHA: &str = "0123456789abcdef0123456789abcdef01234567";
fn tmp(tag: &str) -> PathBuf {
let d =
std::env::temp_dir().join(format!("pv-release-inputs-{tag}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&d);
std::fs::create_dir_all(&d).expect("mkdir");
d
}
#[test]
fn crux_dir_defaults_to_evidence_crux_version_and_honours_the_override() {
let mut s = Subject::new("0.70.0", SHA).expect("subject");
let root = Path::new("/r");
assert_eq!(s.crux_dir(root), PathBuf::from("/r/evidence/crux/0.70.0"));
s.crux_receipts_dir = Some(PathBuf::from("/elsewhere"));
assert_eq!(s.crux_dir(root), PathBuf::from("/elsewhere"));
}
#[test]
fn read_surface_ratchet_reads_absent_valid_and_refuses_the_rest() {
let d = tmp("ratchet");
assert_eq!(read_surface_ratchet(&d), Ok(None));
let f = d.join(SURFACE_RATCHET_FILE);
std::fs::create_dir_all(f.parent().expect("parent")).expect("mkdir");
std::fs::write(
&f,
format!(
r#"{{"schema":"{SURFACE_RATCHET_SCHEMA}","unknown_args":4,"stdin_undeclared":9}}"#
),
)
.expect("write");
assert_eq!(
read_surface_ratchet(&d),
Ok(Some(SurfaceRatchet {
unknown_args: 4,
stdin_undeclared: 9
}))
);
std::fs::write(
&f,
format!(r#"{{"schema":"{SURFACE_RATCHET_SCHEMA}","unknown_args":4}}"#),
)
.expect("write");
let e = read_surface_ratchet(&d).expect_err("missing count");
assert!(
e.to_string().contains("`stdin_undeclared` is not a count"),
"{e}"
);
std::fs::write(
&f,
r#"{"schema":"other/v1","unknown_args":1,"stdin_undeclared":1}"#,
)
.expect("write");
let e = read_surface_ratchet(&d).expect_err("foreign schema");
assert!(e.to_string().contains("refused by name"), "{e}");
let _ = std::fs::remove_dir_all(&d);
}
}