1use crate::{
4 BudgetKind, ConformanceReport, ConformanceStatus, LtsPolicy, PerformanceBudget,
5 PerformanceBudgetReport, ReleaseGate, ReleaseGateDecision, SecurityChecklist,
6 StabilityRegistry,
7};
8
9const REQUIRED_CASES: &[&str] = &[
10 "vision-full-rust",
11 "vision-properties",
12 "vision-python-bindings",
13 "vision-opencv-correctness",
14 "vision-opencv-performance",
15 "vision-gpu-explicit-transfers",
16 "vision-linux",
17 "vision-windows",
18 "vision-macos",
19 "vision-unsafe-audit",
20];
21
22const REQUIRED_COMPARISONS: &[&str] = &[
23 "opencv-vision-correctness",
24 "opencv-vision-performance",
25 "opencv-rgbd-performance",
26 "opencv-calibration",
27 "opencv-video",
28 "opencv-odometry",
29 "opencv-photography",
30];
31
32const REQUIRED_EXAMPLES: &[&str] = &["vision_1_cpu", "vision_1_release_gate"];
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub struct Vision1Measurements {
37 pub cpu_vga_latency_us: u64,
39 pub cpu_1080p_latency_us: u64,
41 pub gpu_4k_chain_latency_us: u64,
43 pub explicit_copy_bytes: u64,
45}
46
47#[derive(Clone, Debug)]
49pub struct Vision1ReleaseEvidence {
50 pub conformance: ConformanceReport,
52 pub security: SecurityChecklist,
54 pub measurements: Vision1Measurements,
56 pub passed_comparison_suites: Vec<String>,
58 pub verified_examples: Vec<String>,
60 pub migration_policy: String,
62}
63
64#[derive(Clone, Copy, Debug, Default)]
66pub struct Vision1ReleaseGate;
67
68impl Vision1ReleaseGate {
69 pub const fn required_conformance_cases() -> &'static [&'static str] {
71 REQUIRED_CASES
72 }
73 pub const fn required_comparison_suites() -> &'static [&'static str] {
75 REQUIRED_COMPARISONS
76 }
77 pub const fn required_examples() -> &'static [&'static str] {
79 REQUIRED_EXAMPLES
80 }
81
82 pub fn evaluate(evidence: &Vision1ReleaseEvidence) -> ReleaseGateDecision {
84 let budgets = vision_budgets(evidence.measurements);
85 let base = ReleaseGate {
86 stability: Some(StabilityRegistry::vision_v1_surface()),
87 conformance: Some(evidence.conformance.clone()),
88 security: Some(evidence.security.clone()),
89 lts: Some(LtsPolicy::spatialrust_v1()),
90 budgets: Some(budgets),
91 reject_experimental: true,
92 };
93 let mut decision = base.evaluate();
94 for required in REQUIRED_CASES {
95 let matching = evidence
96 .conformance
97 .cases()
98 .iter()
99 .filter(|case| case.id == *required)
100 .collect::<Vec<_>>();
101 match matching.as_slice() {
102 [case] if case.status == ConformanceStatus::Pass => {}
103 [case] => decision
104 .reasons
105 .push(format!("required conformance `{required}` is {:?}", case.status)),
106 [] => {
107 decision.reasons.push(format!("required conformance `{required}` is missing"))
108 }
109 _ => decision
110 .reasons
111 .push(format!("required conformance `{required}` is duplicated")),
112 }
113 }
114 require_names(
115 &mut decision.reasons,
116 "comparison suite",
117 REQUIRED_COMPARISONS,
118 &evidence.passed_comparison_suites,
119 );
120 require_names(
121 &mut decision.reasons,
122 "example",
123 REQUIRED_EXAMPLES,
124 &evidence.verified_examples,
125 );
126 if evidence.migration_policy != "vision-1" {
127 decision.reasons.push("migration policy `vision-1` was not acknowledged".into());
128 }
129 decision.allowed = decision.reasons.is_empty();
130 decision
131 }
132}
133
134fn require_names(reasons: &mut Vec<String>, kind: &str, required: &[&str], actual: &[String]) {
135 for name in required {
136 if !actual.iter().any(|value| value == name) {
137 reasons.push(format!("required {kind} `{name}` is missing"));
138 }
139 }
140}
141
142fn vision_budgets(measurements: Vision1Measurements) -> PerformanceBudgetReport {
143 let mut report = PerformanceBudgetReport::new();
144 for (id, kind, ceiling, observed) in [
145 ("vision-cpu-vga-us", BudgetKind::LatencyMicros, 50_000, measurements.cpu_vga_latency_us),
146 (
147 "vision-cpu-1080p-us",
148 BudgetKind::LatencyMicros,
149 250_000,
150 measurements.cpu_1080p_latency_us,
151 ),
152 (
153 "vision-gpu-4k-chain-us",
154 BudgetKind::LatencyMicros,
155 20_000,
156 measurements.gpu_4k_chain_latency_us,
157 ),
158 (
159 "vision-explicit-copy-bytes",
160 BudgetKind::BytesCopied,
161 64 * 1024 * 1024,
162 measurements.explicit_copy_bytes,
163 ),
164 ] {
165 report.declare(PerformanceBudget { id: id.into(), kind, ceiling });
166 report.sample(id, observed);
167 }
168 report
169}
170
171#[cfg(test)]
172mod tests {
173 use super::{Vision1Measurements, Vision1ReleaseEvidence, Vision1ReleaseGate};
174 use crate::{ConformanceReport, ConformanceStatus, SecurityChecklist};
175
176 fn passing() -> Vision1ReleaseEvidence {
177 let mut conformance = ConformanceReport::new();
178 for &id in Vision1ReleaseGate::required_conformance_cases() {
179 conformance.record(id, ConformanceStatus::Pass, None);
180 }
181 Vision1ReleaseEvidence {
182 conformance,
183 security: SecurityChecklist::north_star_baseline_satisfied(),
184 measurements: Vision1Measurements {
185 cpu_vga_latency_us: 10_000,
186 cpu_1080p_latency_us: 100_000,
187 gpu_4k_chain_latency_us: 13_523,
188 explicit_copy_bytes: 8 * 1024 * 1024,
189 },
190 passed_comparison_suites: Vision1ReleaseGate::required_comparison_suites()
191 .iter()
192 .map(ToString::to_string)
193 .collect(),
194 verified_examples: Vision1ReleaseGate::required_examples()
195 .iter()
196 .map(ToString::to_string)
197 .collect(),
198 migration_policy: "vision-1".into(),
199 }
200 }
201
202 #[test]
203 fn vision1_complete_evidence_is_allowed() {
204 assert!(Vision1ReleaseGate::evaluate(&passing()).allowed);
205 }
206
207 #[test]
208 fn vision1_rejects_missing_case_and_over_budget() {
209 let mut evidence = passing();
210 evidence.conformance = ConformanceReport::new();
211 evidence.measurements.gpu_4k_chain_latency_us = 20_001;
212 let decision = Vision1ReleaseGate::evaluate(&evidence);
213 assert!(!decision.allowed);
214 assert!(decision.reasons.iter().any(|reason| reason.contains("vision-linux")));
215 assert!(decision.reasons.iter().any(|reason| reason.contains("budget")));
216 }
217
218 #[test]
219 fn vision1_rejects_skipped_mandatory_case() {
220 let mut evidence = passing();
221 let mut conformance = ConformanceReport::new();
222 for &id in Vision1ReleaseGate::required_conformance_cases() {
223 let status = if id == "vision-macos" {
224 ConformanceStatus::Skip
225 } else {
226 ConformanceStatus::Pass
227 };
228 conformance.record(id, status, None);
229 }
230 evidence.conformance = conformance;
231 let decision = Vision1ReleaseGate::evaluate(&evidence);
232 assert!(!decision.allowed);
233 assert!(decision
234 .reasons
235 .iter()
236 .any(|reason| reason.contains("vision-macos") && reason.contains("Skip")));
237 }
238}