parlov_core/technique.rs
1//! Probe generation metadata carried end-to-end through the pipeline.
2
3use serde::{Deserialize, Serialize};
4
5use crate::OracleClass;
6
7/// Why these probes were generated and what normative basis justifies the expected differential.
8///
9/// Set by a strategy at probe generation time, carried through execution unchanged, and consumed
10/// by the analyzer for confidence calibration and evidence labeling.
11///
12/// Signal extraction is unconditional — the analyzer runs all extractors on every
13/// `DifferentialSet`. Technique metadata is for attribution and confidence calibration, not for
14/// gating which signals are extracted.
15#[derive(Debug, Clone)]
16pub struct Technique {
17 /// Machine-readable identifier, e.g. `"if-none-match"` or `"get-200-404"`.
18 pub id: &'static str,
19 /// Human-readable name, e.g. `"If-None-Match conditional request"`.
20 pub name: &'static str,
21 /// Which oracle class this technique targets.
22 pub oracle_class: OracleClass,
23 /// Detection vector: how the differential is produced.
24 pub vector: Vector,
25 /// How strongly the RFC mandates the expected server behavior.
26 pub strength: NormativeStrength,
27}
28
29/// Detection method being used to produce the differential.
30///
31/// Strategies declare their vector. The analyzer uses it to select which signal extractors to run.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub enum Vector {
34 /// Differential produced by comparing status codes across baseline/probe inputs.
35 StatusCodeDiff,
36 /// Differential produced by manipulating cache-related headers (e.g. `If-None-Match`).
37 CacheProbing,
38 /// Differential produced by comparing error message body content across baseline/probe inputs.
39 ErrorMessageGranularity,
40}
41
42#[cfg(test)]
43mod tests {
44 use super::Vector;
45
46 #[test]
47 fn error_message_granularity_variant_exists() {
48 let v = Vector::ErrorMessageGranularity;
49 assert_ne!(v, Vector::StatusCodeDiff);
50 assert_ne!(v, Vector::CacheProbing);
51 }
52
53 #[test]
54 fn error_message_granularity_serializes() {
55 let original = Vector::ErrorMessageGranularity;
56 let json = serde_json::to_string(&original).expect("serialization must succeed");
57 let roundtrip: Vector =
58 serde_json::from_str(&json).expect("deserialization must succeed");
59 assert_eq!(original, roundtrip);
60 }
61
62 #[test]
63 fn error_message_granularity_is_copy() {
64 let a = Vector::ErrorMessageGranularity;
65 let b = a;
66 assert_eq!(a, b);
67 }
68}
69
70/// How strongly the RFC mandates the expected server behavior.
71///
72/// Directly affects confidence calibration: a `Must`-level differential is stronger evidence than
73/// a `May`-level one.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
75pub enum NormativeStrength {
76 /// RFC MUST: server is required to behave this way.
77 Must,
78 /// RFC MUST NOT: violation of this is definitive evidence.
79 MustNot,
80 /// RFC SHOULD: server is expected but not required to comply.
81 Should,
82 /// RFC MAY: server is permitted but not expected to exhibit this behavior.
83 May,
84}