parlov-analysis 0.7.0

Analysis engine trait and signal detection for parlov.
Documentation
//! Runtime evidence modifiers applied to a Contradictory outcome's weight before
//! it enters the reducer.
//!
//! Three orthogonal multiplicative gates:
//! - `surface_relevance`: is the observed differential on the surface this technique tests?
//! - `precondition_confidence`: did the request reach the layer where the technique would matter?
//! - `control_integrity`: did the mutation preserve the intended baseline reference?
//!
//! All three default to 1.0 (full evidence). When any gate zeroes, the outcome becomes
//! `Inapplicable` rather than entering the reducer with a zero contribution.

use parlov_core::{DifferentialSet, Technique};

use super::control::{control_integrity, ControlDecision};
use super::precondition::{precondition_confidence, PreconditionBlock};
use super::surface::{surface_relevance, SurfaceDecision};

/// Multiplicative modifier triple gating Contradictory evidence weight.
///
/// `total()` is the product of the three fields. When any field is `0.0` the outcome
/// becomes `Inapplicable` rather than entering the reducer with a zero contribution.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EvidenceModifiers {
    /// Is the observed differential on the surface this technique tests?
    pub surface_relevance: f64,
    /// Did the request reach the layer where the technique would matter?
    pub precondition_confidence: f64,
    /// Did the mutation preserve the intended baseline reference?
    pub control_integrity: f64,
}

impl Default for EvidenceModifiers {
    fn default() -> Self {
        Self {
            surface_relevance: 1.0,
            precondition_confidence: 1.0,
            control_integrity: 1.0,
        }
    }
}

impl EvidenceModifiers {
    /// Multiplicative product of the three gates.
    #[must_use]
    pub fn total(&self) -> f64 {
        self.surface_relevance * self.precondition_confidence * self.control_integrity
    }

    /// True when any gate has zeroed.
    #[must_use]
    pub fn is_blocked(&self) -> bool {
        self.surface_relevance == 0.0
            || self.precondition_confidence == 0.0
            || self.control_integrity == 0.0
    }
}

/// Modifiers plus an optional structured block reason explaining why a gate fired.
///
/// `block_reason` is `Some` when a gate has zeroed — the analyzer surfaces it in
/// `Inapplicable(reason)` outcome strings.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ModifierResult {
    /// The three multiplicative gates.
    pub modifiers: EvidenceModifiers,
    /// Structured reason when a gate has zeroed; `None` otherwise.
    pub block_reason: Option<PreconditionBlock>,
}

impl ModifierResult {
    /// True when any gate has zeroed.
    #[must_use]
    pub fn is_blocked(&self) -> bool {
        self.modifiers.is_blocked()
    }
}

/// Computes the runtime evidence modifiers for a Contradictory outcome.
///
/// Runs three gates in parallel:
/// - `precondition_confidence`: auth/method/parser gates plus applicability markers.
/// - `surface_relevance`: detects mis-surfaced status contradictions.
/// - `control_integrity`: detects route-mutation control destruction.
///
/// Block-reason precedence when multiple gates fire: precondition > control > surface.
#[must_use]
pub fn compute_modifiers(technique: &Technique, differential: &DifferentialSet) -> ModifierResult {
    let pc = precondition_confidence(technique, differential);
    let ci = control_integrity(differential);
    let sr = surface_relevance(technique, differential);

    let control_block = if matches!(ci, ControlDecision::Blocked) {
        Some(PreconditionBlock::MutationDestroyedControl)
    } else {
        None
    };
    let surface_block = if matches!(sr, SurfaceDecision::Blocked) {
        Some(PreconditionBlock::SurfaceMismatch)
    } else {
        None
    };

    let block_reason = pc.block_reason().or(control_block).or(surface_block);

    ModifierResult {
        modifiers: EvidenceModifiers {
            surface_relevance: sr.confidence(),
            precondition_confidence: pc.confidence(),
            control_integrity: ci.confidence(),
        },
        block_reason,
    }
}

#[cfg(test)]
#[path = "modifiers_tests.rs"]
mod tests;