parlov-core 0.5.0

Shared types, error types, and oracle class definitions for parlov.
Documentation
//! Probe generation metadata carried end-to-end through the pipeline.

use serde::{Deserialize, Serialize};

use crate::OracleClass;

/// Why these probes were generated and what normative basis justifies the expected differential.
///
/// Set by a strategy at probe generation time, carried through execution unchanged, and consumed
/// by the analyzer for confidence calibration and evidence labeling.
///
/// Signal extraction is unconditional — the analyzer runs all extractors on every
/// `DifferentialSet`. Technique metadata is for attribution and confidence calibration, not for
/// gating which signals are extracted.
#[derive(Debug, Clone)]
pub struct Technique {
    /// Machine-readable identifier, e.g. `"if-none-match"` or `"get-200-404"`.
    pub id: &'static str,
    /// Human-readable name, e.g. `"If-None-Match conditional request"`.
    pub name: &'static str,
    /// Which oracle class this technique targets.
    pub oracle_class: OracleClass,
    /// Detection vector: how the differential is produced.
    pub vector: Vector,
    /// How strongly the RFC mandates the expected server behavior.
    pub strength: NormativeStrength,
}

/// Detection method being used to produce the differential.
///
/// Strategies declare their vector. The analyzer uses it to select which signal extractors to run.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Vector {
    /// Differential produced by comparing status codes across baseline/probe inputs.
    StatusCodeDiff,
    /// Differential produced by manipulating cache-related headers (e.g. `If-None-Match`).
    CacheProbing,
    /// Differential produced by comparing error message body content across baseline/probe inputs.
    ErrorMessageGranularity,
}

#[cfg(test)]
mod tests {
    use super::Vector;

    #[test]
    fn error_message_granularity_variant_exists() {
        let v = Vector::ErrorMessageGranularity;
        assert_ne!(v, Vector::StatusCodeDiff);
        assert_ne!(v, Vector::CacheProbing);
    }

    #[test]
    fn error_message_granularity_serializes() {
        let original = Vector::ErrorMessageGranularity;
        let json = serde_json::to_string(&original).expect("serialization must succeed");
        let roundtrip: Vector =
            serde_json::from_str(&json).expect("deserialization must succeed");
        assert_eq!(original, roundtrip);
    }

    #[test]
    fn error_message_granularity_is_copy() {
        let a = Vector::ErrorMessageGranularity;
        let b = a;
        assert_eq!(a, b);
    }
}

/// How strongly the RFC mandates the expected server behavior.
///
/// Directly affects confidence calibration: a `Must`-level differential is stronger evidence than
/// a `May`-level one.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum NormativeStrength {
    /// RFC MUST: server is required to behave this way.
    Must,
    /// RFC MUST NOT: violation of this is definitive evidence.
    MustNot,
    /// RFC SHOULD: server is expected but not required to comply.
    Should,
    /// RFC MAY: server is permitted but not expected to exhibit this behavior.
    May,
}