1use std::collections::HashMap;
4
5#[derive(Debug, Clone, PartialEq)]
7pub enum GateResult {
8 Pass(String),
10 Fail(String),
12 Skip(String),
14 Pending,
16}
17
18impl GateResult {
19 pub fn passed(&self) -> bool {
21 matches!(self, GateResult::Pass(_))
22 }
23
24 pub fn failed(&self) -> bool {
26 matches!(self, GateResult::Fail(_))
27 }
28
29 pub fn score(&self, weight: u32) -> u32 {
31 match self {
32 GateResult::Pass(_) => weight,
33 GateResult::Fail(_) | GateResult::Skip(_) | GateResult::Pending => 0,
34 }
35 }
36}
37
38#[derive(Debug, Clone)]
40pub struct QualityGate {
41 pub id: &'static str,
43 pub name: &'static str,
45 pub tool: &'static str,
47 pub target: &'static str,
49 pub weight: u32,
51 pub category: GateCategory,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
57pub enum GateCategory {
58 Resilience,
60 Safety,
62 Quality,
64 Performance,
66 Usability,
68}
69
70impl GateCategory {
71 pub fn name(&self) -> &'static str {
73 match self {
74 GateCategory::Resilience => "Resilience",
75 GateCategory::Safety => "Safety",
76 GateCategory::Quality => "Quality",
77 GateCategory::Performance => "Performance",
78 GateCategory::Usability => "Usability",
79 }
80 }
81}
82
83pub const IRONMAN_GATES: &[QualityGate] = &[
85 QualityGate {
86 id: "F901",
87 name: "Mutation Resilience",
88 tool: "cargo mutants",
89 target: ">90%",
90 weight: 15,
91 category: GateCategory::Resilience,
92 },
93 QualityGate {
94 id: "F902",
95 name: "Fuzzing Coverage",
96 tool: "cargo fuzz",
97 target: ">90%",
98 weight: 10,
99 category: GateCategory::Resilience,
100 },
101 QualityGate {
102 id: "F903",
103 name: "Miri UB-Free",
104 tool: "cargo miri test",
105 target: "0 UB",
106 weight: 15,
107 category: GateCategory::Safety,
108 },
109 QualityGate {
110 id: "F904",
111 name: "Loom Concurrency",
112 tool: "loom",
113 target: "0 races",
114 weight: 5,
115 category: GateCategory::Safety,
116 },
117 QualityGate {
118 id: "F905",
119 name: "ThreadSanitizer",
120 tool: "-Zsanitizer=thread",
121 target: "0 races",
122 weight: 5,
123 category: GateCategory::Safety,
124 },
125 QualityGate {
126 id: "F906",
127 name: "AddressSanitizer",
128 tool: "-Zsanitizer=address",
129 target: "0 errors",
130 weight: 5,
131 category: GateCategory::Safety,
132 },
133 QualityGate {
134 id: "F907",
135 name: "LeakSanitizer",
136 tool: "-Zsanitizer=leak",
137 target: "0 leaks",
138 weight: 5,
139 category: GateCategory::Safety,
140 },
141 QualityGate {
142 id: "F908",
143 name: "Panic Freedom",
144 tool: "fuzz inputs",
145 target: "0 panics",
146 weight: 5,
147 category: GateCategory::Resilience,
148 },
149 QualityGate {
150 id: "F909",
151 name: "Unsafe Audit",
152 tool: "cargo geiger",
153 target: "0 forbid",
154 weight: 10,
155 category: GateCategory::Quality,
156 },
157 QualityGate {
158 id: "F910",
159 name: "Dependency Audit",
160 tool: "cargo audit",
161 target: "0 vulns",
162 weight: 10,
163 category: GateCategory::Quality,
164 },
165 QualityGate {
166 id: "F911",
167 name: "Dead Code",
168 tool: "cargo udeps",
169 target: "0 unused",
170 weight: 5,
171 category: GateCategory::Quality,
172 },
173 QualityGate {
174 id: "F912",
175 name: "Cognitive Complexity",
176 tool: "clippy",
177 target: "<15/fn",
178 weight: 10,
179 category: GateCategory::Quality,
180 },
181 QualityGate {
182 id: "F913",
183 name: "Documentation",
184 tool: "rustdoc",
185 target: "100% pub",
186 weight: 5,
187 category: GateCategory::Quality,
188 },
189 QualityGate {
190 id: "F914",
191 name: "License Compliance",
192 tool: "cargo deny",
193 target: "approved",
194 weight: 5,
195 category: GateCategory::Quality,
196 },
197 QualityGate {
198 id: "F915",
199 name: "Binary Size",
200 tool: "strip",
201 target: "<8MB",
202 weight: 5,
203 category: GateCategory::Performance,
204 },
205 QualityGate {
206 id: "F916",
207 name: "Startup Time",
208 tool: "cold start",
209 target: "<20ms",
210 weight: 10,
211 category: GateCategory::Performance,
212 },
213 QualityGate {
214 id: "F917",
215 name: "Frame Latency",
216 tool: "P99 render",
217 target: "<8ms",
218 weight: 10,
219 category: GateCategory::Performance,
220 },
221 QualityGate {
222 id: "F918",
223 name: "Battery Impact",
224 tool: "powertop",
225 target: "<1W idle",
226 weight: 5,
227 category: GateCategory::Performance,
228 },
229 QualityGate {
230 id: "F919",
231 name: "Accessibility",
232 tool: "screen reader",
233 target: "readable",
234 weight: 5,
235 category: GateCategory::Usability,
236 },
237 QualityGate {
238 id: "F920",
239 name: "Internationalization",
240 tool: "non-ASCII",
241 target: "no crash",
242 weight: 5,
243 category: GateCategory::Usability,
244 },
245];
246
247#[derive(Debug, Clone)]
249pub struct IronmanScorecard {
250 pub results: HashMap<&'static str, GateResult>,
252 pub total_score: u32,
254 pub max_score: u32,
256 pub pass_threshold: f64,
258 pub timestamp: std::time::SystemTime,
260}
261
262impl IronmanScorecard {
263 pub fn new() -> Self {
265 let max_score = IRONMAN_GATES.iter().map(|g| g.weight).sum();
266 Self {
267 results: HashMap::new(),
268 total_score: 0,
269 max_score,
270 pass_threshold: 0.90,
271 timestamp: std::time::SystemTime::now(),
272 }
273 }
274
275 pub fn record(&mut self, gate_id: &'static str, result: GateResult) {
277 if let Some(gate) = IRONMAN_GATES.iter().find(|g| g.id == gate_id) {
278 let score = result.score(gate.weight);
279 self.total_score += score;
280 self.results.insert(gate_id, result);
281 }
282 }
283
284 pub fn percentage(&self) -> f64 {
286 if self.max_score == 0 {
287 return 0.0;
288 }
289 (self.total_score as f64 / self.max_score as f64) * 100.0
290 }
291
292 pub fn passed(&self) -> bool {
294 self.percentage() >= self.pass_threshold * 100.0
295 }
296
297 pub fn category_score(&self, category: GateCategory) -> (u32, u32) {
299 let mut achieved = 0u32;
300 let mut max = 0u32;
301
302 for gate in IRONMAN_GATES.iter().filter(|g| g.category == category) {
303 max += gate.weight;
304 if let Some(result) = self.results.get(gate.id) {
305 achieved += result.score(gate.weight);
306 }
307 }
308
309 (achieved, max)
310 }
311
312 pub fn failed_gates(&self) -> Vec<&QualityGate> {
314 IRONMAN_GATES
315 .iter()
316 .filter(|g| self.results.get(g.id).map_or(false, |r| r.failed()))
317 .collect()
318 }
319
320 pub fn skipped_gates(&self) -> Vec<&QualityGate> {
322 IRONMAN_GATES
323 .iter()
324 .filter(|g| {
325 self.results
326 .get(g.id)
327 .map_or(true, |r| matches!(r, GateResult::Skip(_)))
328 })
329 .collect()
330 }
331}
332
333impl Default for IronmanScorecard {
334 fn default() -> Self {
335 Self::new()
336 }
337}