Skip to main content

cbtop/ironman/
types.rs

1//! Types, enums, constants, and scorecard for the Ironman falsification suite.
2
3use std::collections::HashMap;
4
5/// Ironman quality gate result
6#[derive(Debug, Clone, PartialEq)]
7pub enum GateResult {
8    /// Gate passed with optional details
9    Pass(String),
10    /// Gate failed with reason
11    Fail(String),
12    /// Gate skipped (e.g., tool not installed)
13    Skip(String),
14    /// Gate result pending (async check)
15    Pending,
16}
17
18impl GateResult {
19    /// Check if the gate passed
20    pub fn passed(&self) -> bool {
21        matches!(self, GateResult::Pass(_))
22    }
23
24    /// Check if the gate failed
25    pub fn failed(&self) -> bool {
26        matches!(self, GateResult::Fail(_))
27    }
28
29    /// Get score contribution (0 if failed/skipped, weight if passed)
30    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/// Quality gate definition
39#[derive(Debug, Clone)]
40pub struct QualityGate {
41    /// Falsification ID (F901-F920)
42    pub id: &'static str,
43    /// Human-readable name
44    pub name: &'static str,
45    /// Tool used for validation
46    pub tool: &'static str,
47    /// Target threshold
48    pub target: &'static str,
49    /// Maximum points for this gate
50    pub weight: u32,
51    /// Category for grouping
52    pub category: GateCategory,
53}
54
55/// Gate category for grouping
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
57pub enum GateCategory {
58    /// Mutation and fuzzing resilience
59    Resilience,
60    /// Memory safety (Miri, sanitizers)
61    Safety,
62    /// Code quality (complexity, docs, deps)
63    Quality,
64    /// Performance (size, startup, latency)
65    Performance,
66    /// Usability (a11y, i18n)
67    Usability,
68}
69
70impl GateCategory {
71    /// Get category name
72    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
83/// All Ironman quality gates per section 34
84pub 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/// Ironman validation scorecard
248#[derive(Debug, Clone)]
249pub struct IronmanScorecard {
250    /// Results for each gate
251    pub results: HashMap<&'static str, GateResult>,
252    /// Total score achieved
253    pub total_score: u32,
254    /// Maximum possible score
255    pub max_score: u32,
256    /// Pass threshold (default 90%)
257    pub pass_threshold: f64,
258    /// Timestamp of validation
259    pub timestamp: std::time::SystemTime,
260}
261
262impl IronmanScorecard {
263    /// Create a new empty scorecard
264    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    /// Record a gate result
276    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    /// Get percentage score
285    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    /// Check if overall validation passed
293    pub fn passed(&self) -> bool {
294        self.percentage() >= self.pass_threshold * 100.0
295    }
296
297    /// Get score by category
298    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    /// Get failed gates
313    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    /// Get skipped gates
321    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}