Skip to main content

verification_calibration/
lib.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use stack_ids::{CalibrationSnapshotId, NuisanceStateId, RegionId};
4
5pub const CALIBRATION_SNAPSHOT_V1_SCHEMA: &str = "calibration_snapshot_v1";
6pub const NUISANCE_STATE_ARTIFACT_V1_SCHEMA: &str = "nuisance_state_artifact_v1";
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
9pub struct CalibrationSnapshot {
10    pub schema_version: String,
11    pub snapshot_id: CalibrationSnapshotId,
12    pub case_id: stack_ids::VerificationCaseId,
13    pub recorded_at: String,
14    pub comparability_available: bool,
15    pub oracle_calibrated: bool,
16    pub abstention_threshold_micros: u64,
17    pub observed_risk_micros: u64,
18    #[serde(default)]
19    pub drift_markers: Vec<String>,
20    pub forces_advisory_only: bool,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
24pub struct NuisanceStateArtifact {
25    pub schema_version: String,
26    pub nuisance_state_id: NuisanceStateId,
27    pub recorded_at: String,
28    #[serde(default, skip_serializing_if = "Option::is_none")]
29    pub region_id: Option<RegionId>,
30    pub family: String,
31    pub markers: Vec<String>,
32    pub semantic_change_confounded: bool,
33    pub operator_families: Vec<String>,
34}
35
36impl CalibrationSnapshot {
37    pub fn evaluate(
38        case_id: stack_ids::VerificationCaseId,
39        recorded_at: impl Into<String>,
40        comparability_available: bool,
41        oracle_calibrated: bool,
42        abstention_threshold_micros: u64,
43        observed_risk_micros: u64,
44        drift_markers: Vec<String>,
45    ) -> Self {
46        let forces_advisory_only = !comparability_available
47            || !oracle_calibrated
48            || observed_risk_micros >= abstention_threshold_micros
49            || !drift_markers.is_empty();
50        Self {
51            schema_version: CALIBRATION_SNAPSHOT_V1_SCHEMA.into(),
52            snapshot_id: CalibrationSnapshotId::generate(),
53            case_id,
54            recorded_at: recorded_at.into(),
55            comparability_available,
56            oracle_calibrated,
57            abstention_threshold_micros,
58            observed_risk_micros,
59            drift_markers,
60            forces_advisory_only,
61        }
62    }
63}
64
65impl NuisanceStateArtifact {
66    pub fn new(
67        recorded_at: impl Into<String>,
68        region_id: Option<RegionId>,
69        family: impl Into<String>,
70        markers: Vec<String>,
71        semantic_change_confounded: bool,
72        operator_families: Vec<String>,
73    ) -> Self {
74        Self {
75            schema_version: NUISANCE_STATE_ARTIFACT_V1_SCHEMA.into(),
76            nuisance_state_id: NuisanceStateId::generate(),
77            recorded_at: recorded_at.into(),
78            region_id,
79            family: family.into(),
80            markers,
81            semantic_change_confounded,
82            operator_families,
83        }
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn missing_comparability_forces_advisory_only() {
93        let snapshot = CalibrationSnapshot::evaluate(
94            stack_ids::VerificationCaseId::new("case-1"),
95            "2026-03-12T00:00:00Z",
96            false,
97            true,
98            500_000,
99            100_000,
100            vec![],
101        );
102        assert!(snapshot.forces_advisory_only);
103    }
104
105    #[test]
106    fn nuisance_state_artifact_is_serializable_and_region_bound() {
107        let artifact = NuisanceStateArtifact::new(
108            "2026-03-13T00:00:00Z",
109            Some(RegionId::new("region:demo")),
110            "retrieval_widening_shift",
111            vec!["widened_scope".into(), "provider_retry_storm".into()],
112            true,
113            vec!["kernel_execution".into(), "kernel_oracles".into()],
114        );
115
116        assert_eq!(artifact.schema_version, NUISANCE_STATE_ARTIFACT_V1_SCHEMA);
117        assert_eq!(artifact.region_id, Some(RegionId::new("region:demo")));
118        assert!(artifact.semantic_change_confounded);
119    }
120}