Skip to main content

semantic_memory/
hostile_benchmark.rs

1//! Claim-bounded receipt types and invariant scoring for the hostile memory benchmark.
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct MetricThresholds {
7    pub minimum_pass_rate: f64,
8    pub maximum_stale_retrievals: u64,
9    pub maximum_unsupported_admissions: u64,
10    pub maximum_namespace_leakage: u64,
11    pub require_replay_equivalence: bool,
12}
13
14impl MetricThresholds {
15    /// Thresholds declared before the benchmark event stream is executed.
16    pub fn declared() -> Self {
17        Self {
18            minimum_pass_rate: 1.0,
19            maximum_stale_retrievals: 0,
20            maximum_unsupported_admissions: 0,
21            maximum_namespace_leakage: 0,
22            require_replay_equivalence: true,
23        }
24    }
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(rename_all = "snake_case")]
29pub enum ScenarioStatus {
30    Pass,
31    Fail,
32    NotTested,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ScenarioReceipt {
37    pub name: String,
38    pub status: ScenarioStatus,
39    pub latency_us: u64,
40    pub detail: String,
41}
42
43#[derive(Debug, Clone, Default, Serialize, Deserialize)]
44pub struct MetricCounts {
45    pub stale_retrievals: u64,
46    pub unsupported_admissions: u64,
47    pub contradictions_preserved: u64,
48    pub temporal_correct: u64,
49    pub namespace_leakage: u64,
50    pub replay_equivalent: bool,
51}
52
53#[derive(Debug, Clone, Default, Serialize, Deserialize)]
54pub struct ReceiptSummary {
55    pub tested: u64,
56    pub passed: u64,
57    pub failed: u64,
58    pub not_tested: u64,
59    pub pass_rate: f64,
60    pub thresholds_met: bool,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct BenchmarkReceipt {
65    pub schema_version: String,
66    pub subject: String,
67    pub thresholds: MetricThresholds,
68    pub scenarios: Vec<ScenarioReceipt>,
69    pub metrics: MetricCounts,
70    pub summary: ReceiptSummary,
71}
72
73impl BenchmarkReceipt {
74    pub fn new(subject: impl Into<String>, thresholds: MetricThresholds) -> Self {
75        Self {
76            schema_version: "hostile-memory-benchmark-v1".into(),
77            subject: subject.into(),
78            thresholds,
79            scenarios: Vec::new(),
80            metrics: MetricCounts::default(),
81            summary: ReceiptSummary::default(),
82        }
83    }
84}
85
86/// Recomputes summary fields; `not_tested` is never counted as a pass.
87pub fn evaluate_receipt(receipt: &mut BenchmarkReceipt) {
88    let passed = receipt
89        .scenarios
90        .iter()
91        .filter(|s| s.status == ScenarioStatus::Pass)
92        .count() as u64;
93    let failed = receipt
94        .scenarios
95        .iter()
96        .filter(|s| s.status == ScenarioStatus::Fail)
97        .count() as u64;
98    let not_tested = receipt
99        .scenarios
100        .iter()
101        .filter(|s| s.status == ScenarioStatus::NotTested)
102        .count() as u64;
103    let tested = passed + failed;
104    let pass_rate = if tested == 0 {
105        0.0
106    } else {
107        passed as f64 / tested as f64
108    };
109    receipt.summary = ReceiptSummary {
110        tested,
111        passed,
112        failed,
113        not_tested,
114        pass_rate,
115        thresholds_met: tested > 0
116            && pass_rate >= receipt.thresholds.minimum_pass_rate
117            && receipt.metrics.stale_retrievals <= receipt.thresholds.maximum_stale_retrievals
118            && receipt.metrics.unsupported_admissions
119                <= receipt.thresholds.maximum_unsupported_admissions
120            && receipt.metrics.namespace_leakage <= receipt.thresholds.maximum_namespace_leakage
121            && (!receipt.thresholds.require_replay_equivalence
122                || receipt.metrics.replay_equivalent),
123    };
124}