Skip to main content

parlov_analysis/aggregation/
coverage_gate.rs

1//! Coverage gate for the `NotPresent` verdict.
2//!
3//! Posterior alone is insufficient evidence. Even when the Bayesian aggregate falls
4//! below the `NotPresent` threshold, the verdict should only be issued when the
5//! endpoint has been meaningfully tested — multiple contradictory techniques across
6//! enough independent strategies, with at least one technique that actually probes
7//! the oracle-relevant boundary.
8
9use crate::aggregation::reducer::{EvidenceEvent, EvidencePolarity};
10
11/// Minimum number of distinct Contradictory technique firings required to claim `NotPresent`.
12pub const MIN_CONTRADICTORY_TECHNIQUES: usize = 3;
13
14/// Minimum weight for a Contradictory event to count as a "Strong" technique. Matches the
15/// Strong bucket from the bucketed-prior calibration: `low_privilege` / `auth_strip` /
16/// `scope_manipulation` all fire at 0.25.
17pub const STRONG_THRESHOLD: f64 = 0.20;
18
19/// Returns `true` iff the events satisfy the gate for `NotPresent`.
20///
21/// Three requirements:
22/// 1. No `Positive` events. Any positive signal — at any weight — disqualifies `NotPresent`.
23/// 2. At least `MIN_CONTRADICTORY_TECHNIQUES` distinct contradictory events fired.
24/// 3. At least one contradictory event has `weight >= STRONG_THRESHOLD`.
25#[must_use]
26pub fn passes_not_present_gate(events: &[EvidenceEvent]) -> bool {
27    let mut has_positive = false;
28    let mut contradictory_count = 0usize;
29    let mut has_strong = false;
30
31    for event in events {
32        match event.polarity {
33            EvidencePolarity::Positive => {
34                has_positive = true;
35            }
36            EvidencePolarity::Contradictory => {
37                contradictory_count += 1;
38                if event.weight >= STRONG_THRESHOLD {
39                    has_strong = true;
40                }
41            }
42        }
43    }
44
45    !has_positive && contradictory_count >= MIN_CONTRADICTORY_TECHNIQUES && has_strong
46}
47
48#[cfg(test)]
49#[path = "coverage_gate_tests.rs"]
50mod tests;