use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::precedence::{PolicyVerdict, VerdictKind};
pub const BENCH_AUDIT_ANCHOR: &str = "arXiv:2605.26079";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BenchTrustSummary {
pub suite_id: String,
pub bench_trust_score: f64,
pub total_tasks: u32,
pub flagged_task_count: u32,
pub audited_at: DateTime<Utc>,
#[serde(default = "default_anchor")]
pub anchor: String,
}
fn default_anchor() -> String {
BENCH_AUDIT_ANCHOR.to_string()
}
impl BenchTrustSummary {
pub fn flagged_task_ratio(&self) -> f64 {
if self.total_tasks == 0 {
return 0.0;
}
f64::from(self.flagged_task_count) / f64::from(self.total_tasks)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BenchGatedClaim {
pub decision_id: String,
pub suite_id: String,
pub delta_score: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BenchAuditPolicy {
pub min_trust_score: f64,
pub allow_above: f64,
pub margin_slack: f64,
}
impl Default for BenchAuditPolicy {
fn default() -> Self {
Self {
min_trust_score: 0.70,
allow_above: 0.85,
margin_slack: 1.0,
}
}
}
impl BenchAuditPolicy {
pub fn evaluate(
&self,
claim: &BenchGatedClaim,
summary: &BenchTrustSummary,
) -> Vec<PolicyVerdict> {
let mut verdicts = Vec::new();
if claim.suite_id != summary.suite_id {
verdicts.push(PolicyVerdict::new(
VerdictKind::Deny,
"bench_audit:suite_mismatch",
format!(
"claim references suite '{}' but audit summary is for '{}'",
claim.suite_id, summary.suite_id
),
));
return verdicts;
}
if summary.bench_trust_score < self.min_trust_score {
verdicts.push(PolicyVerdict::new(
VerdictKind::Deny,
"bench_audit:low_trust_score",
format!(
"bench_trust_score {:.4} below min_trust_score {:.4} ({})",
summary.bench_trust_score, self.min_trust_score, summary.anchor
),
));
}
if summary.bench_trust_score >= self.min_trust_score
&& summary.bench_trust_score < self.allow_above
{
verdicts.push(PolicyVerdict::new(
VerdictKind::RequiresApproval,
"bench_audit:hitl_band",
format!(
"bench_trust_score {:.4} in HITL band [{:.4}, {:.4}); routing requires approval",
summary.bench_trust_score, self.min_trust_score, self.allow_above
),
));
}
let margin = summary.flagged_task_ratio() * self.margin_slack;
if claim.delta_score.abs() <= margin && summary.flagged_task_count > 0 {
verdicts.push(PolicyVerdict::new(
VerdictKind::Deny,
"bench_audit:within_flagged_margin",
format!(
"delta {:+.4} within flagged-task margin {:.4} ({} flagged / {} total)",
claim.delta_score, margin, summary.flagged_task_count, summary.total_tasks
),
));
}
if summary.bench_trust_score >= self.allow_above {
verdicts.push(PolicyVerdict::new(
VerdictKind::Allow,
"bench_audit:high_trust_score",
format!(
"bench_trust_score {:.4} ≥ allow_above {:.4} (delta {:+.4}, margin {:.4})",
summary.bench_trust_score, self.allow_above, claim.delta_score, margin
),
));
}
verdicts
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::precedence::resolve_conflicts;
fn ts(secs: i64) -> DateTime<Utc> {
DateTime::<Utc>::from_timestamp(secs, 0).expect("valid timestamp")
}
fn clean_summary() -> BenchTrustSummary {
BenchTrustSummary {
suite_id: "smoke".into(),
bench_trust_score: 0.92,
total_tasks: 20,
flagged_task_count: 0,
audited_at: ts(1_700_000_000),
anchor: BENCH_AUDIT_ANCHOR.into(),
}
}
fn dirty_summary() -> BenchTrustSummary {
BenchTrustSummary {
suite_id: "smoke".into(),
bench_trust_score: 0.40,
total_tasks: 20,
flagged_task_count: 12,
audited_at: ts(1_700_000_000),
anchor: BENCH_AUDIT_ANCHOR.into(),
}
}
fn claim(delta: f64) -> BenchGatedClaim {
BenchGatedClaim {
decision_id: "dec_001".into(),
suite_id: "smoke".into(),
delta_score: delta,
}
}
#[test]
fn clean_suite_with_strong_delta_emits_allow_only() {
let policy = BenchAuditPolicy::default();
let verdicts = policy.evaluate(&claim(0.05), &clean_summary());
assert_eq!(verdicts.len(), 1);
assert_eq!(verdicts[0].kind, VerdictKind::Allow);
assert_eq!(verdicts[0].source, "bench_audit:high_trust_score");
}
#[test]
fn low_trust_emits_low_trust_score_deny() {
let policy = BenchAuditPolicy::default();
let verdicts = policy.evaluate(&claim(0.20), &dirty_summary());
let kinds: Vec<VerdictKind> = verdicts.iter().map(|v| v.kind).collect();
assert!(kinds.contains(&VerdictKind::Deny));
assert!(
verdicts
.iter()
.any(|v| v.source == "bench_audit:low_trust_score"),
"expected a bench_audit:low_trust_score verdict, got {verdicts:?}"
);
let resolved = resolve_conflicts(verdicts);
assert_eq!(
resolved.winning.expect("verdict survives precedence").kind,
VerdictKind::Deny,
);
}
#[test]
fn mid_band_trust_emits_hitl_band_approval() {
let policy = BenchAuditPolicy::default();
let mid_summary = BenchTrustSummary {
bench_trust_score: 0.75,
..clean_summary()
};
let verdicts = policy.evaluate(&claim(0.10), &mid_summary);
assert!(
verdicts
.iter()
.any(|v| v.kind == VerdictKind::RequiresApproval
&& v.source == "bench_audit:hitl_band"),
"expected hitl_band approval verdict, got {verdicts:?}"
);
assert!(
!verdicts.iter().any(|v| v.kind == VerdictKind::Allow),
"mid-band must not also emit an Allow"
);
}
#[test]
fn within_flagged_margin_denies_even_for_clean_suite() {
let policy = BenchAuditPolicy::default();
let summary = BenchTrustSummary {
flagged_task_count: 5,
..clean_summary()
};
let verdicts = policy.evaluate(&claim(0.20), &summary);
assert!(
verdicts
.iter()
.any(|v| v.kind == VerdictKind::Deny
&& v.source == "bench_audit:within_flagged_margin"),
"expected within_flagged_margin deny, got {verdicts:?}"
);
assert!(
verdicts
.iter()
.any(|v| v.kind == VerdictKind::Allow
&& v.source == "bench_audit:high_trust_score"),
"clean suite must still emit the high_trust_score Allow for trace fidelity"
);
let resolved = resolve_conflicts(verdicts);
assert_eq!(
resolved.winning.expect("verdict survives precedence").kind,
VerdictKind::Deny,
"Deny must win precedence over high_trust_score Allow",
);
}
#[test]
fn suite_id_mismatch_short_circuits_to_deny() {
let policy = BenchAuditPolicy::default();
let summary = clean_summary();
let mismatched = BenchGatedClaim {
decision_id: "dec_001".into(),
suite_id: "regression".into(),
delta_score: 0.30,
};
let verdicts = policy.evaluate(&mismatched, &summary);
assert_eq!(verdicts.len(), 1);
assert_eq!(verdicts[0].kind, VerdictKind::Deny);
assert_eq!(verdicts[0].source, "bench_audit:suite_mismatch");
}
#[test]
fn no_flagged_tasks_skips_margin_check() {
let policy = BenchAuditPolicy::default();
let summary = BenchTrustSummary {
flagged_task_count: 0,
..clean_summary()
};
let verdicts = policy.evaluate(&claim(0.001), &summary);
assert!(
!verdicts
.iter()
.any(|v| v.source == "bench_audit:within_flagged_margin"),
"margin check must skip when nothing is flagged"
);
assert!(verdicts.iter().any(|v| v.kind == VerdictKind::Allow));
}
#[test]
fn flagged_task_ratio_is_zero_for_empty_suite() {
let summary = BenchTrustSummary {
total_tasks: 0,
flagged_task_count: 0,
..clean_summary()
};
assert_eq!(summary.flagged_task_ratio(), 0.0);
}
#[test]
fn anchor_round_trips_through_serde() {
let summary = clean_summary();
let json = serde_json::to_string(&summary).expect("serialise");
assert!(json.contains("\"anchor\":\"arXiv:2605.26079\""));
let parsed: BenchTrustSummary = serde_json::from_str(&json).expect("deserialise");
assert_eq!(parsed.anchor, BENCH_AUDIT_ANCHOR);
}
}