parlov_analysis/lib.rs
1//! Analysis engine for parlov: signal detection, statistics, and oracle classification.
2//!
3//! This crate is pure synchronous computation — no I/O, no async, no network stack. Keeping it
4//! isolated from `parlov-probe` means changing statistical thresholds or adding a new oracle
5//! pattern does not recompile `reqwest` or `hyper`.
6//!
7//! # Trait contract
8//!
9//! Implementors of [`Analyzer`] incrementally evaluate a growing [`ProbeSet`] via [`Analyzer::evaluate`]
10//! and signal when enough samples have been collected. The provided [`Analyzer::analyze`] method
11//! wraps `evaluate` for callers that supply a complete `ProbeSet` in one shot.
12
13#![deny(clippy::all)]
14#![warn(clippy::pedantic)]
15#![deny(missing_docs)]
16
17pub mod existence;
18
19use parlov_core::{OracleClass, OracleResult, ProbeSet};
20
21/// Decision returned by [`Analyzer::evaluate`] after inspecting the current sample set.
22pub enum SampleDecision {
23 /// Enough samples collected; here is the final result.
24 Complete(OracleResult),
25 /// Differential detected but not yet confirmed stable — collect one more pair.
26 NeedMore,
27}
28
29/// Analyzes a set of baseline and probe response surfaces and produces an oracle verdict.
30///
31/// Implementors must be `Send + Sync` so they can be held in shared state across async tasks.
32/// All methods take `&self` — analyzers are stateless with respect to individual probe runs.
33pub trait Analyzer: Send + Sync {
34 /// Incrementally evaluate a growing `ProbeSet`.
35 ///
36 /// Called after each new pair is added. Returns `NeedMore` until enough samples are
37 /// collected to determine stability, then `Complete` with the final result.
38 fn evaluate(&self, data: &ProbeSet) -> SampleDecision;
39
40 /// The oracle class this analyzer handles.
41 fn oracle_class(&self) -> OracleClass;
42
43 /// Analyze a fully-collected `ProbeSet` and return a verdict with evidence and severity.
44 ///
45 /// This is a provided method that delegates to [`evaluate`][Self::evaluate]. It exists for
46 /// callers that supply a complete `ProbeSet` in one shot rather than driving the incremental
47 /// sampling loop. Panics if `evaluate` returns `NeedMore`, which indicates the `ProbeSet`
48 /// does not yet contain enough samples.
49 ///
50 /// # Panics
51 ///
52 /// Panics when `evaluate` returns `NeedMore`, meaning the supplied `ProbeSet` has fewer
53 /// samples than this analyzer requires.
54 fn analyze(&self, data: &ProbeSet) -> OracleResult {
55 match self.evaluate(data) {
56 SampleDecision::Complete(result) => result,
57 SampleDecision::NeedMore => {
58 panic!("analyze called with insufficient samples; use evaluate to drive sampling")
59 }
60 }
61 }
62}