use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum GateResult {
Pass(String),
Fail(String),
Skip(String),
Pending,
}
impl GateResult {
pub fn passed(&self) -> bool {
matches!(self, GateResult::Pass(_))
}
pub fn failed(&self) -> bool {
matches!(self, GateResult::Fail(_))
}
pub fn score(&self, weight: u32) -> u32 {
match self {
GateResult::Pass(_) => weight,
GateResult::Fail(_) | GateResult::Skip(_) | GateResult::Pending => 0,
}
}
}
#[derive(Debug, Clone)]
pub struct QualityGate {
pub id: &'static str,
pub name: &'static str,
pub tool: &'static str,
pub target: &'static str,
pub weight: u32,
pub category: GateCategory,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GateCategory {
Resilience,
Safety,
Quality,
Performance,
Usability,
}
impl GateCategory {
pub fn name(&self) -> &'static str {
match self {
GateCategory::Resilience => "Resilience",
GateCategory::Safety => "Safety",
GateCategory::Quality => "Quality",
GateCategory::Performance => "Performance",
GateCategory::Usability => "Usability",
}
}
}
pub const IRONMAN_GATES: &[QualityGate] = &[
QualityGate {
id: "F901",
name: "Mutation Resilience",
tool: "cargo mutants",
target: ">90%",
weight: 15,
category: GateCategory::Resilience,
},
QualityGate {
id: "F902",
name: "Fuzzing Coverage",
tool: "cargo fuzz",
target: ">90%",
weight: 10,
category: GateCategory::Resilience,
},
QualityGate {
id: "F903",
name: "Miri UB-Free",
tool: "cargo miri test",
target: "0 UB",
weight: 15,
category: GateCategory::Safety,
},
QualityGate {
id: "F904",
name: "Loom Concurrency",
tool: "loom",
target: "0 races",
weight: 5,
category: GateCategory::Safety,
},
QualityGate {
id: "F905",
name: "ThreadSanitizer",
tool: "-Zsanitizer=thread",
target: "0 races",
weight: 5,
category: GateCategory::Safety,
},
QualityGate {
id: "F906",
name: "AddressSanitizer",
tool: "-Zsanitizer=address",
target: "0 errors",
weight: 5,
category: GateCategory::Safety,
},
QualityGate {
id: "F907",
name: "LeakSanitizer",
tool: "-Zsanitizer=leak",
target: "0 leaks",
weight: 5,
category: GateCategory::Safety,
},
QualityGate {
id: "F908",
name: "Panic Freedom",
tool: "fuzz inputs",
target: "0 panics",
weight: 5,
category: GateCategory::Resilience,
},
QualityGate {
id: "F909",
name: "Unsafe Audit",
tool: "cargo geiger",
target: "0 forbid",
weight: 10,
category: GateCategory::Quality,
},
QualityGate {
id: "F910",
name: "Dependency Audit",
tool: "cargo audit",
target: "0 vulns",
weight: 10,
category: GateCategory::Quality,
},
QualityGate {
id: "F911",
name: "Dead Code",
tool: "cargo udeps",
target: "0 unused",
weight: 5,
category: GateCategory::Quality,
},
QualityGate {
id: "F912",
name: "Cognitive Complexity",
tool: "clippy",
target: "<15/fn",
weight: 10,
category: GateCategory::Quality,
},
QualityGate {
id: "F913",
name: "Documentation",
tool: "rustdoc",
target: "100% pub",
weight: 5,
category: GateCategory::Quality,
},
QualityGate {
id: "F914",
name: "License Compliance",
tool: "cargo deny",
target: "approved",
weight: 5,
category: GateCategory::Quality,
},
QualityGate {
id: "F915",
name: "Binary Size",
tool: "strip",
target: "<8MB",
weight: 5,
category: GateCategory::Performance,
},
QualityGate {
id: "F916",
name: "Startup Time",
tool: "cold start",
target: "<20ms",
weight: 10,
category: GateCategory::Performance,
},
QualityGate {
id: "F917",
name: "Frame Latency",
tool: "P99 render",
target: "<8ms",
weight: 10,
category: GateCategory::Performance,
},
QualityGate {
id: "F918",
name: "Battery Impact",
tool: "powertop",
target: "<1W idle",
weight: 5,
category: GateCategory::Performance,
},
QualityGate {
id: "F919",
name: "Accessibility",
tool: "screen reader",
target: "readable",
weight: 5,
category: GateCategory::Usability,
},
QualityGate {
id: "F920",
name: "Internationalization",
tool: "non-ASCII",
target: "no crash",
weight: 5,
category: GateCategory::Usability,
},
];
#[derive(Debug, Clone)]
pub struct IronmanScorecard {
pub results: HashMap<&'static str, GateResult>,
pub total_score: u32,
pub max_score: u32,
pub pass_threshold: f64,
pub timestamp: std::time::SystemTime,
}
impl IronmanScorecard {
pub fn new() -> Self {
let max_score = IRONMAN_GATES.iter().map(|g| g.weight).sum();
Self {
results: HashMap::new(),
total_score: 0,
max_score,
pass_threshold: 0.90,
timestamp: std::time::SystemTime::now(),
}
}
pub fn record(&mut self, gate_id: &'static str, result: GateResult) {
if let Some(gate) = IRONMAN_GATES.iter().find(|g| g.id == gate_id) {
let score = result.score(gate.weight);
self.total_score += score;
self.results.insert(gate_id, result);
}
}
pub fn percentage(&self) -> f64 {
if self.max_score == 0 {
return 0.0;
}
(self.total_score as f64 / self.max_score as f64) * 100.0
}
pub fn passed(&self) -> bool {
self.percentage() >= self.pass_threshold * 100.0
}
pub fn category_score(&self, category: GateCategory) -> (u32, u32) {
let mut achieved = 0u32;
let mut max = 0u32;
for gate in IRONMAN_GATES.iter().filter(|g| g.category == category) {
max += gate.weight;
if let Some(result) = self.results.get(gate.id) {
achieved += result.score(gate.weight);
}
}
(achieved, max)
}
pub fn failed_gates(&self) -> Vec<&QualityGate> {
IRONMAN_GATES
.iter()
.filter(|g| self.results.get(g.id).map_or(false, |r| r.failed()))
.collect()
}
pub fn skipped_gates(&self) -> Vec<&QualityGate> {
IRONMAN_GATES
.iter()
.filter(|g| {
self.results
.get(g.id)
.map_or(true, |r| matches!(r, GateResult::Skip(_)))
})
.collect()
}
}
impl Default for IronmanScorecard {
fn default() -> Self {
Self::new()
}
}