verification_calibration/
lib.rs1use 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}