m2m/
error.rs

1//! M2M Protocol error types with epistemic classification.
2//!
3//! # Epistemic Error Taxonomy
4//!
5//! This module organizes errors by their epistemic nature - what they tell us
6//! about the state of knowledge at the time of failure. This classification
7//! helps developers understand:
8//!
9//! 1. **Whether the error was predictable** (and thus should be handled gracefully)
10//! 2. **What remediation is appropriate** (retry, fail-fast, degrade gracefully)
11//! 3. **Where defensive code is needed** (validation boundaries, I/O edges)
12//!
13//! ## Categories
14//!
15//! ### B_i Falsified (Belief Proven Wrong)
16//!
17//! The caller held a belief about validity that was proven false at runtime.
18//! These are "expected" errors in the sense that the system was designed to
19//! detect and report them. Examples:
20//!
21//! - Invalid input format (caller believed input was valid)
22//! - Session not established (caller believed session was ready)
23//! - Capability mismatch (caller believed peers were compatible)
24//!
25//! **Handling**: Validate early, return descriptive errors, don't retry.
26//!
27//! ### I^B (Bounded Ignorance Materialized)
28//!
29//! The system could not know the outcome at compile time - it depends on
30//! external state (network, filesystem, RNG, upstream services). These errors
31//! represent the inherent uncertainty of distributed systems.
32//!
33//! - Network errors (connectivity is I^B until we try)
34//! - Upstream service errors (availability is I^B)
35//! - RNG failures (entropy availability is I^B)
36//!
37//! **Handling**: Timeouts, retries with backoff, circuit breakers, fallbacks.
38//!
39//! ### K_i Violated (Invariant Broken)
40//!
41//! A known invariant that "should never happen" was violated. These indicate
42//! bugs in the code or corruption. They are NOT expected in normal operation.
43//!
44//! - Internal state corruption
45//! - Logic errors (unreachable code reached)
46//!
47//! **Handling**: Log extensively, fail fast, alert operators.
48//!
49//! ## Usage Example
50//!
51//! ```rust,ignore
52//! use m2m::error::{M2MError, Result};
53//!
54//! fn process_message(session: &Session, data: &[u8]) -> Result<Response> {
55//!     // B_i: Caller believes session is established
56//!     if !session.is_established() {
57//!         return Err(M2MError::SessionNotEstablished);
58//!     }
59//!     
60//!     // B_i: Caller believes data is valid JSON
61//!     let parsed = serde_json::from_slice(data)?;
62//!     
63//!     // I^B: Network availability unknown until we try
64//!     let response = send_upstream(parsed).await?;
65//!     
66//!     Ok(response)
67//! }
68//! ```
69
70use thiserror::Error;
71
72use crate::codec::m2m::crypto::CryptoError;
73
74/// M2M Protocol errors, organized by epistemic category.
75///
76/// See module documentation for the full epistemic taxonomy.
77#[derive(Error, Debug)]
78pub enum M2MError {
79    // ═══════════════════════════════════════════════════════════════════════
80    // B_i FALSIFIED — Caller's belief about validity proven wrong
81    // ═══════════════════════════════════════════════════════════════════════
82    //
83    // These errors indicate the caller made an assumption that was incorrect.
84    // They are "expected" errors - the system is designed to detect them.
85    // Handling: Validate inputs, return clear errors, don't retry.
86    // ═══════════════════════════════════════════════════════════════════════
87    /// Compression failed due to invalid input or unsupported content.
88    ///
89    /// **Epistemic**: B_i falsified — caller believed content was compressible.
90    #[error("Compression error: {0}")]
91    Compression(String),
92
93    /// Decompression failed due to corrupted or invalid wire format.
94    ///
95    /// **Epistemic**: B_i falsified — caller believed data was valid M2M format.
96    #[error("Decompression error: {0}")]
97    Decompression(String),
98
99    /// Codec identifier not recognized or not supported.
100    ///
101    /// **Epistemic**: B_i falsified — caller believed codec was available.
102    #[error("Invalid codec: {0}")]
103    InvalidCodec(String),
104
105    /// Protocol state machine violation.
106    ///
107    /// **Epistemic**: B_i falsified — caller believed operation was valid
108    /// in current state.
109    #[error("Protocol error: {0}")]
110    Protocol(String),
111
112    /// Session handshake failed to agree on capabilities.
113    ///
114    /// **Epistemic**: B_i falsified — caller believed negotiation would succeed.
115    #[error("Negotiation failed: {0}")]
116    NegotiationFailed(String),
117
118    /// Operation requires an established session, but session is not ready.
119    ///
120    /// **Epistemic**: B_i falsified — caller believed session was established.
121    #[error("Session not established")]
122    SessionNotEstablished,
123
124    /// Session has exceeded its timeout duration.
125    ///
126    /// **Epistemic**: B_i falsified — caller believed session was still valid.
127    #[error("Session expired")]
128    SessionExpired,
129
130    /// Message does not conform to expected format.
131    ///
132    /// **Epistemic**: B_i falsified — caller believed message was well-formed.
133    #[error("Invalid message format: {0}")]
134    InvalidMessage(String),
135
136    /// Peers have incompatible capabilities for the requested operation.
137    ///
138    /// **Epistemic**: B_i falsified — caller believed capabilities were compatible.
139    #[error("Capability mismatch: {0}")]
140    CapabilityMismatch(String),
141
142    /// ML model was expected to be loaded but isn't.
143    ///
144    /// **Epistemic**: B_i falsified — caller believed model was available.
145    #[error("Model not loaded: {0}")]
146    ModelNotLoaded(String),
147
148    /// Requested model not found in the registry.
149    ///
150    /// **Epistemic**: B_i falsified — caller believed model existed.
151    #[error("Model not found: {0}")]
152    ModelNotFound(String),
153
154    /// Tokenizer operation failed (invalid encoding, unknown token).
155    ///
156    /// **Epistemic**: B_i falsified — caller believed input was tokenizable.
157    #[error("Tokenizer error: {0}")]
158    Tokenizer(String),
159
160    /// Configuration is invalid or missing required values.
161    ///
162    /// **Epistemic**: B_i falsified — caller believed config was valid.
163    #[error("Config error: {0}")]
164    Config(String),
165
166    /// JSON serialization/deserialization failed.
167    ///
168    /// **Epistemic**: B_i falsified — caller believed data was valid JSON.
169    #[error("JSON error: {0}")]
170    Json(#[from] serde_json::Error),
171
172    // ═══════════════════════════════════════════════════════════════════════
173    // I^B — Bounded Ignorance (External State Unknown Until Runtime)
174    // ═══════════════════════════════════════════════════════════════════════
175    //
176    // These errors stem from inherent uncertainty about external systems.
177    // We cannot know network state, service availability, or RNG entropy
178    // at compile time - it's bounded ignorance that materializes at runtime.
179    // Handling: Timeouts, retries, circuit breakers, graceful degradation.
180    // ═══════════════════════════════════════════════════════════════════════
181    /// Network operation failed (connection, timeout, DNS).
182    ///
183    /// **Epistemic**: I^B materialized — network availability was unknown
184    /// until we attempted the operation.
185    ///
186    /// **Handling**: Retry with exponential backoff, circuit breaker.
187    #[error("Network error: {0}")]
188    Network(String),
189
190    /// Upstream service returned an error or is unavailable.
191    ///
192    /// **Epistemic**: I^B materialized — upstream health was unknown
193    /// until we made the request.
194    ///
195    /// **Handling**: Retry, failover to alternate upstream, degrade gracefully.
196    #[error("Upstream error: {0}")]
197    Upstream(String),
198
199    /// Server-side processing error.
200    ///
201    /// **Epistemic**: I^B materialized — server state was unknown to client.
202    #[error("Server error: {0}")]
203    Server(String),
204
205    /// ML inference failed during execution.
206    ///
207    /// **Epistemic**: I^B materialized — model execution success depends on
208    /// input characteristics and runtime state.
209    #[error("Inference error: {0}")]
210    Inference(String),
211
212    /// Failed to load ML model from filesystem or network.
213    ///
214    /// **Epistemic**: I^B materialized — model file availability unknown
215    /// until load attempted.
216    #[error("Model load error: {0}")]
217    ModelLoad(String),
218
219    /// I/O operation failed.
220    ///
221    /// **Epistemic**: I^B materialized — filesystem/resource state unknown
222    /// until operation attempted.
223    #[error("IO error: {0}")]
224    Io(#[from] std::io::Error),
225
226    /// Cryptographic operation failed (key derivation, encryption, auth).
227    ///
228    /// **Epistemic**: Mixed — may be B_i (invalid key) or I^B (RNG failure).
229    /// The inner `CryptoError` provides specific classification.
230    ///
231    /// This variant preserves the full error chain via `#[source]`,
232    /// enabling tools like `anyhow` to display complete context.
233    #[error("Crypto error: {0}")]
234    Crypto(#[source] CryptoError),
235
236    // ═══════════════════════════════════════════════════════════════════════
237    // SECURITY — Policy Violations (Special Category)
238    // ═══════════════════════════════════════════════════════════════════════
239    //
240    // Security errors are epistemically B_i (content was believed safe),
241    // but they warrant special handling due to their nature. They should
242    // NOT be retried and may require alerting.
243    // ═══════════════════════════════════════════════════════════════════════
244    /// Security scanner detected a threat in content.
245    ///
246    /// **Epistemic**: B_i falsified — content was believed to be safe.
247    ///
248    /// **Handling**: Do NOT retry, log for security audit, consider blocking source.
249    #[error("Security threat detected: {threat_type}")]
250    SecurityThreat {
251        /// Type of threat detected (e.g., "prompt_injection", "jailbreak").
252        threat_type: String,
253        /// Detection confidence score (0.0-1.0).
254        confidence: f32,
255    },
256
257    /// Content blocked by security policy.
258    ///
259    /// **Epistemic**: B_i falsified — content was believed to comply with policy.
260    ///
261    /// **Handling**: Do NOT retry, inform user of policy violation.
262    #[error("Content blocked: {0}")]
263    ContentBlocked(String),
264}
265
266/// Result type alias for M2M operations.
267pub type Result<T> = std::result::Result<T, M2MError>;
268
269// ═══════════════════════════════════════════════════════════════════════════
270// From Implementations
271// ═══════════════════════════════════════════════════════════════════════════
272
273impl From<CryptoError> for M2MError {
274    fn from(err: CryptoError) -> Self {
275        M2MError::Crypto(err)
276    }
277}
278
279impl From<reqwest::Error> for M2MError {
280    fn from(err: reqwest::Error) -> Self {
281        M2MError::Network(err.to_string())
282    }
283}
284
285impl From<toml::de::Error> for M2MError {
286    fn from(err: toml::de::Error) -> Self {
287        M2MError::Config(err.to_string())
288    }
289}
290
291impl From<base64::DecodeError> for M2MError {
292    fn from(err: base64::DecodeError) -> Self {
293        M2MError::Decompression(format!("Base64 decode error: {err}"))
294    }
295}
296
297// ═══════════════════════════════════════════════════════════════════════════
298// Helper Methods
299// ═══════════════════════════════════════════════════════════════════════════
300
301impl M2MError {
302    /// Returns `true` if this error is retryable.
303    ///
304    /// I^B errors (network, upstream, inference) are generally retryable.
305    /// B_i errors (validation failures) are NOT retryable without changes.
306    ///
307    /// # Example
308    ///
309    /// ```rust,ignore
310    /// match operation() {
311    ///     Err(e) if e.is_retryable() => retry_with_backoff(operation),
312    ///     Err(e) => return Err(e),
313    ///     Ok(v) => v,
314    /// }
315    /// ```
316    pub fn is_retryable(&self) -> bool {
317        matches!(
318            self,
319            M2MError::Network(_)
320                | M2MError::Upstream(_)
321                | M2MError::Server(_)
322                | M2MError::Inference(_)
323                | M2MError::Io(_)
324        )
325    }
326
327    /// Returns `true` if this error is security-related.
328    ///
329    /// Security errors should NOT be retried and may warrant special logging.
330    pub fn is_security_error(&self) -> bool {
331        matches!(
332            self,
333            M2MError::SecurityThreat { .. } | M2MError::ContentBlocked(_)
334        )
335    }
336
337    /// Returns `true` if this error indicates bounded ignorance (I^B).
338    ///
339    /// These errors stem from external system state that was unknown
340    /// at compile time.
341    pub fn is_bounded_ignorance(&self) -> bool {
342        matches!(
343            self,
344            M2MError::Network(_)
345                | M2MError::Upstream(_)
346                | M2MError::Server(_)
347                | M2MError::Inference(_)
348                | M2MError::ModelLoad(_)
349                | M2MError::Io(_)
350                | M2MError::Crypto(_)
351        )
352    }
353
354    /// Returns `true` if this error indicates a falsified belief (B_i).
355    ///
356    /// These errors indicate the caller made an incorrect assumption
357    /// about input validity or system state.
358    pub fn is_belief_falsified(&self) -> bool {
359        !self.is_bounded_ignorance()
360    }
361}
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366
367    #[test]
368    fn test_retryable_classification() {
369        // I^B errors are retryable
370        assert!(M2MError::Network("timeout".to_string()).is_retryable());
371        assert!(M2MError::Upstream("503".to_string()).is_retryable());
372        assert!(M2MError::Server("overloaded".to_string()).is_retryable());
373
374        // B_i errors are NOT retryable
375        assert!(!M2MError::SessionNotEstablished.is_retryable());
376        assert!(!M2MError::InvalidMessage("bad format".to_string()).is_retryable());
377        assert!(!M2MError::Decompression("corrupt".to_string()).is_retryable());
378    }
379
380    #[test]
381    fn test_security_classification() {
382        assert!(M2MError::SecurityThreat {
383            threat_type: "injection".to_string(),
384            confidence: 0.95
385        }
386        .is_security_error());
387
388        assert!(M2MError::ContentBlocked("policy".to_string()).is_security_error());
389
390        // Non-security errors
391        assert!(!M2MError::Network("timeout".to_string()).is_security_error());
392    }
393
394    #[test]
395    fn test_bounded_ignorance_classification() {
396        // I^B errors
397        assert!(M2MError::Network("timeout".to_string()).is_bounded_ignorance());
398        assert!(M2MError::Upstream("503".to_string()).is_bounded_ignorance());
399        assert!(M2MError::ModelLoad("not found".to_string()).is_bounded_ignorance());
400
401        // B_i errors (NOT bounded ignorance)
402        assert!(!M2MError::SessionNotEstablished.is_bounded_ignorance());
403        assert!(!M2MError::InvalidCodec("unknown".to_string()).is_bounded_ignorance());
404    }
405
406    #[test]
407    fn test_belief_falsified_is_inverse() {
408        let network_err = M2MError::Network("timeout".to_string());
409        let session_err = M2MError::SessionNotEstablished;
410
411        // These should be inverses
412        assert_eq!(
413            network_err.is_bounded_ignorance(),
414            !network_err.is_belief_falsified()
415        );
416        assert_eq!(
417            session_err.is_bounded_ignorance(),
418            !session_err.is_belief_falsified()
419        );
420    }
421}