Skip to main content

jugar_probar/coverage/
jidoka.rs

1//! Soft Jidoka: Stop vs LogAndContinue
2//!
3//! Per spec §5.1.1: The Stop-the-Line Paradox
4//!
5//! In a renderfarm, if one bucket fails, you re-render it. In a test suite,
6//! a "hard stop" prevents collection of data from other independent blocks.
7//!
8//! Solution: Distinguish between:
9//! - Instrumentation Failures → Hard Stop (can't trust data)
10//! - Test Failures → Log & Continue (taint the block, collect others)
11
12use super::BlockId;
13use std::collections::HashSet;
14
15/// Jidoka response type (Kaizen: Stop vs Continue)
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum JidokaAction {
18    /// Hard stop - pull the Andon cord (instrumentation failure)
19    Stop,
20    /// Soft stop - log and continue (test failure, taint the block)
21    LogAndContinue,
22    /// Warning only - no action needed
23    Warn,
24}
25
26/// Coverage Jidoka violations with severity classification
27#[derive(Debug, Clone)]
28pub enum CoverageViolation {
29    /// Block executed but not instrumented (CRITICAL - stop)
30    UninstrumentedExecution {
31        /// The block that was executed without instrumentation
32        block_id: BlockId,
33    },
34    /// Counter overflow (>u64::MAX executions) (WARNING - continue)
35    CounterOverflow {
36        /// The block whose counter overflowed
37        block_id: BlockId,
38    },
39    /// Impossible edge taken (dead code executed) (CRITICAL - stop)
40    ImpossibleEdge {
41        /// Source block
42        from: BlockId,
43        /// Target block
44        to: BlockId,
45    },
46    /// Coverage regression detected (WARNING - continue)
47    CoverageRegression {
48        /// Expected coverage percentage
49        expected: f64,
50        /// Actual coverage percentage
51        actual: f64,
52    },
53}
54
55impl CoverageViolation {
56    /// Classify violation severity (Soft Jidoka)
57    #[must_use]
58    pub fn action(&self) -> JidokaAction {
59        match self {
60            // Instrumentation bugs = hard stop (can't trust data)
61            Self::UninstrumentedExecution { .. } | Self::ImpossibleEdge { .. } => {
62                JidokaAction::Stop
63            }
64
65            // Test failures = log and continue (collect other blocks)
66            Self::CounterOverflow { .. } | Self::CoverageRegression { .. } => {
67                JidokaAction::LogAndContinue
68            }
69        }
70    }
71
72    /// Get the affected block, if any
73    #[must_use]
74    pub fn affected_block(&self) -> Option<BlockId> {
75        match self {
76            Self::UninstrumentedExecution { block_id } | Self::CounterOverflow { block_id } => {
77                Some(*block_id)
78            }
79            Self::ImpossibleEdge { from, .. } => Some(*from),
80            Self::CoverageRegression { .. } => None,
81        }
82    }
83
84    /// Get a human-readable description
85    #[must_use]
86    pub fn description(&self) -> String {
87        match self {
88            Self::UninstrumentedExecution { block_id } => {
89                format!("Block {} executed but not instrumented", block_id.as_u32())
90            }
91            Self::CounterOverflow { block_id } => {
92                format!("Counter overflow for block {}", block_id.as_u32())
93            }
94            Self::ImpossibleEdge { from, to } => {
95                format!(
96                    "Impossible edge {} -> {} executed",
97                    from.as_u32(),
98                    to.as_u32()
99                )
100            }
101            Self::CoverageRegression { expected, actual } => {
102                format!(
103                    "Coverage regression: expected {:.1}%, got {:.1}%",
104                    expected, actual
105                )
106            }
107        }
108    }
109}
110
111/// Tainted block tracker (for Soft Jidoka)
112///
113/// Tracks blocks that encountered non-fatal violations during coverage collection.
114/// These blocks are still included in the report but marked as suspect.
115#[derive(Debug, Default)]
116pub struct TaintedBlocks {
117    /// Blocks that encountered non-fatal violations
118    tainted: HashSet<BlockId>,
119    /// Violation log for each tainted block
120    violations: Vec<(BlockId, CoverageViolation)>,
121    /// All violations (including those without specific blocks)
122    all_violations: Vec<CoverageViolation>,
123}
124
125impl TaintedBlocks {
126    /// Create a new empty tracker
127    #[must_use]
128    pub fn new() -> Self {
129        Self::default()
130    }
131
132    /// Mark block as tainted (Soft Jidoka)
133    pub fn taint(&mut self, block: BlockId, violation: CoverageViolation) {
134        let _ = self.tainted.insert(block);
135        self.violations.push((block, violation.clone()));
136        self.all_violations.push(violation);
137    }
138
139    /// Record a violation without a specific block
140    pub fn record_violation(&mut self, violation: CoverageViolation) {
141        if let Some(block) = violation.affected_block() {
142            self.taint(block, violation);
143        } else {
144            self.all_violations.push(violation);
145        }
146    }
147
148    /// Check if block is tainted
149    #[must_use]
150    pub fn is_tainted(&self, block: BlockId) -> bool {
151        self.tainted.contains(&block)
152    }
153
154    /// Get the number of tainted blocks
155    #[must_use]
156    pub fn tainted_count(&self) -> usize {
157        self.tainted.len()
158    }
159
160    /// Get the total number of violations (may exceed tainted blocks)
161    #[must_use]
162    pub fn violation_count(&self) -> usize {
163        self.all_violations.len()
164    }
165
166    /// Get all tainted blocks
167    #[must_use]
168    pub fn tainted_blocks(&self) -> Vec<BlockId> {
169        self.tainted.iter().copied().collect()
170    }
171
172    /// Get violations for a specific block
173    #[must_use]
174    pub fn violations_for(&self, block: BlockId) -> Vec<&CoverageViolation> {
175        self.violations
176            .iter()
177            .filter(|(b, _)| *b == block)
178            .map(|(_, v)| v)
179            .collect()
180    }
181
182    /// Get all violations
183    #[must_use]
184    pub fn all_violations(&self) -> &[CoverageViolation] {
185        &self.all_violations
186    }
187
188    /// Clear all tainted blocks and violations
189    pub fn clear(&mut self) {
190        self.tainted.clear();
191        self.violations.clear();
192        self.all_violations.clear();
193    }
194}