Skip to main content

parlov_analysis/aggregation/
modifiers.rs

1//! Runtime evidence modifiers applied to a Contradictory outcome's weight before
2//! it enters the reducer.
3//!
4//! Three orthogonal multiplicative gates:
5//! - `surface_relevance`: is the observed differential on the surface this technique tests?
6//! - `precondition_confidence`: did the request reach the layer where the technique would matter?
7//! - `control_integrity`: did the mutation preserve the intended baseline reference?
8//!
9//! All three default to 1.0 (full evidence). When any gate zeroes, the outcome becomes
10//! `Inapplicable` rather than entering the reducer with a zero contribution.
11
12use parlov_core::{DifferentialSet, Technique};
13
14use super::control::{control_integrity, ControlDecision};
15use super::precondition::{precondition_confidence, PreconditionBlock};
16use super::surface::{surface_relevance, SurfaceDecision};
17
18/// Multiplicative modifier triple gating Contradictory evidence weight.
19///
20/// `total()` is the product of the three fields. When any field is `0.0` the outcome
21/// becomes `Inapplicable` rather than entering the reducer with a zero contribution.
22#[derive(Debug, Clone, Copy, PartialEq)]
23pub struct EvidenceModifiers {
24    /// Is the observed differential on the surface this technique tests?
25    pub surface_relevance: f64,
26    /// Did the request reach the layer where the technique would matter?
27    pub precondition_confidence: f64,
28    /// Did the mutation preserve the intended baseline reference?
29    pub control_integrity: f64,
30}
31
32impl Default for EvidenceModifiers {
33    fn default() -> Self {
34        Self {
35            surface_relevance: 1.0,
36            precondition_confidence: 1.0,
37            control_integrity: 1.0,
38        }
39    }
40}
41
42impl EvidenceModifiers {
43    /// Multiplicative product of the three gates.
44    #[must_use]
45    pub fn total(&self) -> f64 {
46        self.surface_relevance * self.precondition_confidence * self.control_integrity
47    }
48
49    /// True when any gate has zeroed.
50    #[must_use]
51    pub fn is_blocked(&self) -> bool {
52        self.surface_relevance == 0.0
53            || self.precondition_confidence == 0.0
54            || self.control_integrity == 0.0
55    }
56}
57
58/// Modifiers plus an optional structured block reason explaining why a gate fired.
59///
60/// `block_reason` is `Some` when a gate has zeroed — the analyzer surfaces it in
61/// `Inapplicable(reason)` outcome strings.
62#[derive(Debug, Clone, Copy, PartialEq)]
63pub struct ModifierResult {
64    /// The three multiplicative gates.
65    pub modifiers: EvidenceModifiers,
66    /// Structured reason when a gate has zeroed; `None` otherwise.
67    pub block_reason: Option<PreconditionBlock>,
68}
69
70impl ModifierResult {
71    /// True when any gate has zeroed.
72    #[must_use]
73    pub fn is_blocked(&self) -> bool {
74        self.modifiers.is_blocked()
75    }
76}
77
78/// Computes the runtime evidence modifiers for a Contradictory outcome.
79///
80/// Runs three gates in parallel:
81/// - `precondition_confidence`: auth/method/parser gates plus applicability markers.
82/// - `surface_relevance`: detects mis-surfaced status contradictions.
83/// - `control_integrity`: detects route-mutation control destruction.
84///
85/// Block-reason precedence when multiple gates fire: precondition > control > surface.
86#[must_use]
87pub fn compute_modifiers(technique: &Technique, differential: &DifferentialSet) -> ModifierResult {
88    let pc = precondition_confidence(technique, differential);
89    let ci = control_integrity(differential);
90    let sr = surface_relevance(technique, differential);
91
92    let control_block = if matches!(ci, ControlDecision::Blocked) {
93        Some(PreconditionBlock::MutationDestroyedControl)
94    } else {
95        None
96    };
97    let surface_block = if matches!(sr, SurfaceDecision::Blocked) {
98        Some(PreconditionBlock::SurfaceMismatch)
99    } else {
100        None
101    };
102
103    let block_reason = pc.block_reason().or(control_block).or(surface_block);
104
105    ModifierResult {
106        modifiers: EvidenceModifiers {
107            surface_relevance: sr.confidence(),
108            precondition_confidence: pc.confidence(),
109            control_integrity: ci.confidence(),
110        },
111        block_reason,
112    }
113}
114
115#[cfg(test)]
116#[path = "modifiers_tests.rs"]
117mod tests;