1use std::fmt::Write;
4
5use crate::{
6 BudgetKind, ConformanceReport, ConformanceStatus, LtsPolicy, PerformanceBudget,
7 PerformanceBudgetReport, ReleaseGate, ReleaseGateDecision, SecurityChecklist,
8 StabilityRegistry,
9};
10
11const REQUIRED_CASES: &[&str] = &[
12 "vision2-rust-accuracy",
13 "vision2-python-accuracy",
14 "vision2-linux",
15 "vision2-windows",
16 "vision2-macos",
17 "vision2-native-performance",
18 "vision2-python-performance",
19 "vision2-resource-budgets",
20 "vision2-gpu-transfer",
21 "vision2-pages",
22 "vision2-unsafe-audit",
23];
24
25const REQUIRED_RECEIPTS: &[&str] = &[
26 "vision2-component-baseline",
27 "vision2-resize-color",
28 "vision2-gaussian-sobel",
29 "vision2-morphology",
30 "vision2-canny",
31 "vision2-gpu-resident-chain",
32];
33
34const REQUIRED_EXAMPLES: &[&str] = &["vision_2_release_gate"];
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub struct Vision2Measurements {
39 pub native_allocate_1080p_us: u64,
41 pub native_reuse_1080p_us: u64,
43 pub python_allocate_1080p_us: u64,
45 pub python_reuse_1080p_us: u64,
47 pub peak_host_memory_bytes: u64,
49 pub steady_state_allocations: u64,
51 pub worker_threads: u64,
53 pub gpu_host_to_device_bytes: u64,
55 pub gpu_device_to_host_bytes: u64,
57}
58
59#[derive(Clone, Debug)]
61pub struct Vision2ReleaseEvidence {
62 pub conformance: ConformanceReport,
64 pub security: SecurityChecklist,
66 pub measurements: Vision2Measurements,
68 pub passed_receipts: Vec<String>,
70 pub verified_examples: Vec<String>,
72 pub migration_policy: String,
74}
75
76#[derive(Clone, Copy, Debug, Default)]
78pub struct Vision2ReleaseGate;
79
80impl Vision2ReleaseGate {
81 pub const fn required_conformance_cases() -> &'static [&'static str] {
83 REQUIRED_CASES
84 }
85
86 pub const fn required_receipts() -> &'static [&'static str] {
88 REQUIRED_RECEIPTS
89 }
90
91 pub const fn required_examples() -> &'static [&'static str] {
93 REQUIRED_EXAMPLES
94 }
95
96 pub fn evaluate(evidence: &Vision2ReleaseEvidence) -> ReleaseGateDecision {
98 let base = ReleaseGate {
99 stability: Some(StabilityRegistry::vision_v2_surface()),
100 conformance: Some(evidence.conformance.clone()),
101 security: Some(evidence.security.clone()),
102 lts: Some(LtsPolicy::spatialrust_v1()),
103 budgets: Some(vision2_budgets(evidence.measurements)),
104 reject_experimental: true,
105 };
106 let mut decision = base.evaluate();
107 require_passing_cases(&mut decision.reasons, &evidence.conformance);
108 require_names(
109 &mut decision.reasons,
110 "receipt",
111 REQUIRED_RECEIPTS,
112 &evidence.passed_receipts,
113 );
114 require_names(
115 &mut decision.reasons,
116 "example",
117 REQUIRED_EXAMPLES,
118 &evidence.verified_examples,
119 );
120 if evidence.migration_policy != "vision-2" {
121 decision.reasons.push("migration policy `vision-2` was not acknowledged".into());
122 }
123 decision.allowed = decision.reasons.is_empty();
124 decision
125 }
126
127 #[must_use]
129 pub fn render_markdown(evidence: &Vision2ReleaseEvidence) -> String {
130 let decision = Self::evaluate(evidence);
131 let values = evidence.measurements;
132 let mut output = String::from("# Vision 2 release receipt\n\n");
133 let _ = writeln!(
134 output,
135 "Decision: **{}**\n",
136 if decision.allowed { "allowed" } else { "denied" }
137 );
138 output.push_str("| Measurement | Observed | Ceiling |\n");
139 output.push_str("| --- | ---: | ---: |\n");
140 for (label, observed, ceiling) in measurement_rows(values) {
141 let _ = writeln!(output, "| {label} | {observed} | {ceiling} |");
142 }
143 output.push_str("\nRequired receipts:\n\n");
144 for receipt in REQUIRED_RECEIPTS {
145 let present = evidence.passed_receipts.iter().any(|value| value == receipt);
146 let _ = writeln!(output, "- [{}] `{receipt}`", if present { "x" } else { " " });
147 }
148 if !decision.reasons.is_empty() {
149 output.push_str("\nDenial reasons:\n\n");
150 for reason in decision.reasons {
151 let _ = writeln!(output, "- {reason}");
152 }
153 }
154 output
155 }
156}
157
158fn require_passing_cases(reasons: &mut Vec<String>, conformance: &ConformanceReport) {
159 for required in REQUIRED_CASES {
160 let matching =
161 conformance.cases().iter().filter(|case| case.id == *required).collect::<Vec<_>>();
162 match matching.as_slice() {
163 [case] if case.status == ConformanceStatus::Pass => {}
164 [case] => {
165 reasons.push(format!("required conformance `{required}` is {:?}", case.status))
166 }
167 [] => reasons.push(format!("required conformance `{required}` is missing")),
168 _ => reasons.push(format!("required conformance `{required}` is duplicated")),
169 }
170 }
171}
172
173fn require_names(reasons: &mut Vec<String>, kind: &str, required: &[&str], actual: &[String]) {
174 for name in required {
175 let count = actual.iter().filter(|value| value.as_str() == *name).count();
176 match count {
177 1 => {}
178 0 => reasons.push(format!("required {kind} `{name}` is missing")),
179 _ => reasons.push(format!("required {kind} `{name}` is duplicated")),
180 }
181 }
182}
183
184fn measurement_rows(values: Vision2Measurements) -> [(&'static str, u64, u64); 9] {
185 [
186 ("native allocate 1080p (us)", values.native_allocate_1080p_us, 1_000),
187 ("native reuse 1080p (us)", values.native_reuse_1080p_us, 400),
188 ("Python allocate 1080p (us)", values.python_allocate_1080p_us, 1_200),
189 ("Python reuse 1080p (us)", values.python_reuse_1080p_us, 400),
190 ("peak host memory (bytes)", values.peak_host_memory_bytes, 64 * 1024 * 1024),
191 ("steady-state allocations", values.steady_state_allocations, 0),
192 ("worker threads", values.worker_threads, 12),
193 ("GPU upload (bytes)", values.gpu_host_to_device_bytes, 3840 * 2160 * 4),
194 ("GPU resident readback (bytes)", values.gpu_device_to_host_bytes, 0),
195 ]
196}
197
198fn vision2_budgets(values: Vision2Measurements) -> PerformanceBudgetReport {
199 let kinds = [
200 BudgetKind::LatencyMicros,
201 BudgetKind::LatencyMicros,
202 BudgetKind::LatencyMicros,
203 BudgetKind::LatencyMicros,
204 BudgetKind::MemoryBytes,
205 BudgetKind::AllocationCount,
206 BudgetKind::ThreadCount,
207 BudgetKind::BytesCopied,
208 BudgetKind::BytesCopied,
209 ];
210 let mut report = PerformanceBudgetReport::new();
211 for (((label, observed, ceiling), kind), id) in
212 measurement_rows(values).into_iter().zip(kinds).zip([
213 "vision2-native-allocate-1080p-us",
214 "vision2-native-reuse-1080p-us",
215 "vision2-python-allocate-1080p-us",
216 "vision2-python-reuse-1080p-us",
217 "vision2-peak-host-memory-bytes",
218 "vision2-steady-state-allocations",
219 "vision2-worker-threads",
220 "vision2-gpu-upload-bytes",
221 "vision2-gpu-resident-readback-bytes",
222 ])
223 {
224 let _ = label;
225 report.declare(PerformanceBudget { id: id.into(), kind, ceiling });
226 report.sample(id, observed);
227 }
228 report
229}
230
231#[cfg(test)]
232mod tests {
233 use super::{Vision2Measurements, Vision2ReleaseEvidence, Vision2ReleaseGate};
234 use crate::{ConformanceReport, ConformanceStatus, SecurityChecklist};
235
236 fn passing() -> Vision2ReleaseEvidence {
237 let mut conformance = ConformanceReport::new();
238 for &id in Vision2ReleaseGate::required_conformance_cases() {
239 conformance.record(id, ConformanceStatus::Pass, Some("CI receipt".into()));
240 }
241 Vision2ReleaseEvidence {
242 conformance,
243 security: SecurityChecklist::north_star_baseline_satisfied(),
244 measurements: Vision2Measurements {
245 native_allocate_1080p_us: 648,
246 native_reuse_1080p_us: 195,
247 python_allocate_1080p_us: 825,
248 python_reuse_1080p_us: 232,
249 peak_host_memory_bytes: 6_220_800,
250 steady_state_allocations: 0,
251 worker_threads: 12,
252 gpu_host_to_device_bytes: 3840 * 2160 * 4,
253 gpu_device_to_host_bytes: 0,
254 },
255 passed_receipts: Vision2ReleaseGate::required_receipts()
256 .iter()
257 .map(ToString::to_string)
258 .collect(),
259 verified_examples: Vision2ReleaseGate::required_examples()
260 .iter()
261 .map(ToString::to_string)
262 .collect(),
263 migration_policy: "vision-2".into(),
264 }
265 }
266
267 #[test]
268 fn vision2_complete_evidence_is_allowed_and_rendered() {
269 let evidence = passing();
270 assert!(Vision2ReleaseGate::evaluate(&evidence).allowed);
271 let markdown = Vision2ReleaseGate::render_markdown(&evidence);
272 assert!(markdown.contains("Decision: **allowed**"));
273 assert!(markdown.contains("vision2-gpu-resident-chain"));
274 }
275
276 #[test]
277 fn vision2_rejects_missing_skipped_and_duplicate_evidence() {
278 let mut evidence = passing();
279 let mut conformance = ConformanceReport::new();
280 for &id in Vision2ReleaseGate::required_conformance_cases() {
281 if id == "vision2-macos" {
282 conformance.record(id, ConformanceStatus::Skip, None);
283 } else if id == "vision2-pages" {
284 conformance.record(id, ConformanceStatus::Pass, None);
285 conformance.record(id, ConformanceStatus::Pass, None);
286 } else if id != "vision2-python-accuracy" {
287 conformance.record(id, ConformanceStatus::Pass, None);
288 }
289 }
290 evidence.conformance = conformance;
291 evidence.passed_receipts.pop();
292 evidence.verified_examples.push("vision_2_release_gate".into());
293 evidence.migration_policy = "vision-1".into();
294 let decision = Vision2ReleaseGate::evaluate(&evidence);
295 assert!(!decision.allowed);
296 for needle in [
297 "vision2-python-accuracy",
298 "vision2-macos",
299 "vision2-pages",
300 "vision2-gpu-resident-chain",
301 "vision_2_release_gate",
302 "migration policy",
303 ] {
304 assert!(decision.reasons.iter().any(|reason| reason.contains(needle)), "{needle}");
305 }
306 }
307
308 #[test]
309 fn vision2_rejects_each_resource_budget_overrun() {
310 let overruns: &[(&str, fn(&mut Vision2Measurements))] = &[
311 ("native-allocate", |values| values.native_allocate_1080p_us = 1_001),
312 ("native-reuse", |values| values.native_reuse_1080p_us = 401),
313 ("python-allocate", |values| values.python_allocate_1080p_us = 1_201),
314 ("python-reuse", |values| values.python_reuse_1080p_us = 401),
315 ("peak-host-memory", |values| {
316 values.peak_host_memory_bytes = 64 * 1024 * 1024 + 1;
317 }),
318 ("steady-state-allocations", |values| values.steady_state_allocations = 1),
319 ("worker-threads", |values| values.worker_threads = 13),
320 ("gpu-upload", |values| {
321 values.gpu_host_to_device_bytes = 3840 * 2160 * 4 + 1;
322 }),
323 ("gpu-resident-readback", |values| values.gpu_device_to_host_bytes = 4),
324 ];
325 for &(budget_id, mutate) in overruns {
326 let mut evidence = passing();
327 mutate(&mut evidence.measurements);
328 let decision = Vision2ReleaseGate::evaluate(&evidence);
329 assert!(!decision.allowed, "{budget_id}");
330 assert!(
331 decision.reasons.iter().any(|reason| reason.contains(budget_id)),
332 "{budget_id}: {:?}",
333 decision.reasons
334 );
335 }
336 }
337}