Skip to main content

parlov_elicit/
types.rs

1//! Core elicitation types: risk classification, probe specifications, and strategy metadata.
2
3use parlov_core::{ProbeDefinition, Technique};
4use serde::{Deserialize, Serialize};
5
6/// Provenance for Phase 2 chain-derived `ProbeSpec`s.
7///
8/// Attached by `generate_dag_chained_plan` so downstream consumers can trace
9/// the harvested signal that produced this probe — e.g. an `If-Match` probe
10/// whose value was harvested from an `ETag` response header carries
11/// `producer_kind = "Etag"` and `producer_value = "W/\"abc\""`.
12#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
13#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
14pub struct ChainProvenance {
15    /// Discriminant from `ProducerOutputKind` as a stable string, e.g. `"Etag"`.
16    pub producer_kind: String,
17    /// Serialized representation of the harvested value, e.g. `"W/\"abc123\""`.
18    pub producer_value: String,
19}
20
21/// Risk classification for a probing strategy.
22///
23/// Controls whether a strategy is eligible for execution given the operator's
24/// `max_risk` ceiling in `ScanContext`. Variants are ordered from safest to most
25/// destructive so that `<=` comparisons work correctly for ceiling enforcement.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
27pub enum RiskLevel {
28    /// Read-only probing. No state mutation; safe on any target.
29    Safe,
30    /// Uses non-idempotent HTTP methods (e.g. POST, PATCH, PUT) that may have
31    /// side-effects, but the strategy avoids permanent data loss.
32    MethodDestructive,
33    /// May trigger irreversible server-side state changes (e.g. DELETE, account
34    /// closure, resource exhaustion).
35    OperationDestructive,
36}
37
38/// Metadata describing a strategy: identity and risk rating.
39///
40/// Carried on every `ProbeSpec` so the scheduler and output layer can attribute
41/// each probe pair back to the strategy that generated it without holding a
42/// reference to the strategy object.
43#[derive(Debug, Clone)]
44pub struct StrategyMetadata {
45    /// e.g. `"existence-get-200-404"`
46    pub strategy_id: &'static str,
47    /// e.g. `"GET 200/404 existence"`
48    pub strategy_name: &'static str,
49    /// Risk classification.
50    pub risk: RiskLevel,
51}
52
53/// A baseline + probe pair for standard differential analysis.
54///
55/// The scheduler executes `baseline` and `probe` the required number of times,
56/// collecting `ResponseSurface` values into a `DifferentialSet` for analysis.
57#[derive(Debug, Clone)]
58pub struct ProbePair {
59    /// Control (known-existing).
60    pub baseline: ProbeDefinition,
61    /// Suspect (unknown).
62    pub probe: ProbeDefinition,
63    /// Optional unmutated baseline for control-integrity verification.
64    ///
65    /// Strategies that mutate the resource identifier or path (`case_normalize`,
66    /// `trailing_slash`) populate this with the original unmutated request. The runner
67    /// dispatches it as a third request; the result feeds into `control_integrity`.
68    /// Strategies that don't mutate the path leave it `None` (the gate is then inert
69    /// for those pairs).
70    pub canonical_baseline: Option<ProbeDefinition>,
71    /// Generating strategy.
72    pub metadata: StrategyMetadata,
73    /// Confidence calibration and evidence labeling.
74    pub technique: Technique,
75    /// Phase-2 chain provenance — `None` for phase-1 specs, `Some` when this
76    /// pair was generated by `generate_dag_chained_plan` from a harvested signal.
77    pub chain_provenance: Option<ChainProvenance>,
78}
79
80/// A burst probe: N requests to baseline URL, then N to probe URL.
81///
82/// Used for timing oracles where multiple samples are required for statistical
83/// significance. `burst_count` sets the minimum sample size per side; the
84/// adaptive timing analyzer may request additional rounds.
85#[derive(Debug, Clone)]
86pub struct BurstSpec {
87    /// Control (known-existing).
88    pub baseline: ProbeDefinition,
89    /// Suspect (unknown).
90    pub probe: ProbeDefinition,
91    /// Minimum samples per side before analysis.
92    pub burst_count: usize,
93    /// Generating strategy.
94    pub metadata: StrategyMetadata,
95    /// Confidence calibration and evidence labeling.
96    pub technique: Technique,
97    /// Phase-2 chain provenance — `None` for phase-1 specs, `Some` when this
98    /// burst was generated by `generate_dag_chained_plan` from a harvested signal.
99    pub chain_provenance: Option<ChainProvenance>,
100}
101
102/// The unit of work handed to the probe scheduler.
103///
104/// Each variant encodes a different execution and collection strategy. The
105/// scheduler dispatches on the variant; the analysis layer receives a `DifferentialSet`
106/// regardless of which variant produced it.
107#[derive(Debug, Clone)]
108pub enum ProbeSpec {
109    /// Standard adaptive loop: one baseline request, one probe request.
110    Pair(ProbePair),
111    /// Send `burst_count` requests to the baseline URL, then `burst_count` to
112    /// the probe URL, for statistical timing analysis.
113    Burst(BurstSpec),
114    /// One baseline + one probe; compare full response header sets rather than
115    /// bodies or status codes.
116    HeaderDiff(ProbePair),
117}
118
119impl ProbeSpec {
120    /// Technique metadata.
121    #[must_use]
122    pub fn technique(&self) -> &Technique {
123        match self {
124            Self::Pair(p) | Self::HeaderDiff(p) => &p.technique,
125            Self::Burst(b) => &b.technique,
126        }
127    }
128
129    /// Chain provenance — `Some` when generated by phase-2 DAG walking, else `None`.
130    #[must_use]
131    pub fn chain_provenance(&self) -> Option<&ChainProvenance> {
132        match self {
133            Self::Pair(p) | Self::HeaderDiff(p) => p.chain_provenance.as_ref(),
134            Self::Burst(b) => b.chain_provenance.as_ref(),
135        }
136    }
137
138    /// Returns a clone of this spec with `chain_provenance` set on whichever
139    /// variant it carries. Used by `generate_dag_chained_plan` to attach
140    /// provenance after a consumer returns generic specs.
141    #[must_use]
142    pub fn with_chain_provenance(self, prov: ChainProvenance) -> Self {
143        match self {
144            Self::Pair(mut p) => {
145                p.chain_provenance = Some(prov);
146                Self::Pair(p)
147            }
148            Self::HeaderDiff(mut p) => {
149                p.chain_provenance = Some(prov);
150                Self::HeaderDiff(p)
151            }
152            Self::Burst(mut b) => {
153                b.chain_provenance = Some(prov);
154                Self::Burst(b)
155            }
156        }
157    }
158}
159
160#[cfg(test)]
161#[path = "types_tests.rs"]
162mod tests;