agentic_payments/error/
mod.rs

1//! Error types for the agentic-payments system
2//!
3//! This module provides comprehensive error handling with:
4//! - Cryptographic operation errors
5//! - Consensus and BFT voting errors
6//! - Agent lifecycle and communication errors
7//! - Trust chain and validation errors
8//! - System configuration and initialization errors
9//!
10//! All errors include context information and support backtrace capture
11//! when the `RUST_BACKTRACE` environment variable is set.
12
13use std::fmt;
14use std::io;
15use std::time::Duration;
16
17use thiserror::Error;
18
19/// Result type alias for agentic-payments operations
20pub type Result<T> = std::result::Result<T, Error>;
21
22/// Top-level error type for all agentic-payments operations
23#[derive(Debug, Error)]
24pub enum Error {
25    /// Cryptographic operation errors
26    #[error("Cryptographic error: {0}")]
27    Crypto(#[from] CryptoError),
28
29    /// Consensus and voting errors
30    #[error("Consensus error: {0}")]
31    Consensus(#[from] ConsensusError),
32
33    /// Agent lifecycle and communication errors
34    #[error("Agent error: {0}")]
35    Agent(#[from] AgentError),
36
37    /// Validation and trust chain errors
38    #[error("Validation error: {0}")]
39    Validation(#[from] ValidationError),
40
41    /// System configuration and initialization errors
42    #[error("System error: {0}")]
43    System(#[from] SystemError),
44
45    /// Network and communication errors
46    #[error("Network error: {0}")]
47    Network(#[from] NetworkError),
48
49    /// I/O operation errors
50    #[error("I/O error: {0}")]
51    Io(#[from] io::Error),
52
53    /// Serialization/deserialization errors
54    #[error("Serialization error: {0}")]
55    Serialization(#[from] SerializationError),
56
57    /// Timeout errors
58    #[error("Operation timed out after {duration:?}: {operation}")]
59    Timeout {
60        /// The operation that timed out
61        operation: String,
62        /// The timeout duration
63        duration: Duration,
64    },
65
66    /// Generic error with context
67    #[error("{message}")]
68    Other {
69        /// Error message
70        message: String,
71        /// Optional source error
72        #[source]
73        source: Option<Box<dyn std::error::Error + Send + Sync>>,
74    },
75
76    /// Key not found error
77    #[error("Key not found: {0}")]
78    KeyNotFound(String),
79
80    /// Configuration error
81    #[error("Configuration error: {0}")]
82    Configuration(String),
83
84    /// Task join error
85    #[error("Task join error: {0}")]
86    TaskJoin(String),
87
88    /// Pool exhausted error
89    #[error("Agent pool exhausted")]
90    PoolExhausted,
91
92    /// Config error (alias for Configuration)
93    #[error("Config error: {0}")]
94    Config(String),
95
96    /// DID error
97    #[error("DID error: {0}")]
98    Did(String),
99
100    /// Invalid state error
101    #[error("Invalid state: {message}")]
102    InvalidState {
103        /// Error message
104        message: String,
105    },
106
107    /// Authority not found
108    #[error("Authority not found: {authority}")]
109    AuthorityNotFound {
110        /// Authority ID
111        authority: String,
112    },
113
114    /// Invalid vote
115    #[error("Invalid vote from {authority}: {reason}")]
116    InvalidVote {
117        /// Authority that cast invalid vote
118        authority: String,
119        /// Reason vote is invalid
120        reason: String,
121    },
122
123    /// Duplicate vote
124    #[error("Duplicate vote from {authority}")]
125    DuplicateVote {
126        /// Authority that cast duplicate vote
127        authority: String,
128    },
129
130    /// Byzantine fault
131    #[error("Byzantine fault: {message}")]
132    ByzantineFault {
133        /// Fault description
134        message: String,
135    },
136
137    /// View change required
138    #[error("View change required: {0}")]
139    ViewChangeRequired(String),
140
141    /// Consensus already reached
142    #[error("Consensus already reached")]
143    AlreadyReached,
144}
145
146/// Cryptographic operation errors
147#[derive(Debug, Error)]
148pub enum CryptoError {
149    /// Ed25519 signature verification failed
150    #[error("Signature verification failed: {reason}")]
151    SignatureVerificationFailed {
152        /// Reason for verification failure
153        reason: String,
154        /// Optional public key context
155        public_key: Option<String>,
156    },
157
158    /// Invalid signature format
159    #[error("Invalid signature format: {details}")]
160    InvalidSignature {
161        /// Details about the invalid signature
162        details: String,
163    },
164
165    /// Invalid public key format
166    #[error("Invalid public key: {details}")]
167    InvalidPublicKey {
168        /// Details about the invalid key
169        details: String,
170    },
171
172    /// Invalid private key format
173    #[error("Invalid private key: {details}")]
174    InvalidPrivateKey {
175        /// Details about the invalid key
176        details: String,
177    },
178
179    /// Key generation failed
180    #[error("Key generation failed: {reason}")]
181    KeyGenerationFailed {
182        /// Reason for generation failure
183        reason: String,
184    },
185
186    /// Key derivation failed
187    #[error("Key derivation failed: {reason}")]
188    KeyDerivationFailed {
189        /// Reason for derivation failure
190        reason: String,
191    },
192
193    /// PKCS#8 encoding/decoding error
194    #[error("PKCS#8 error: {details}")]
195    Pkcs8Error {
196        /// Error details
197        details: String,
198    },
199
200    /// Hash computation failed
201    #[error("Hash computation failed: {algorithm}")]
202    HashError {
203        /// Hash algorithm that failed
204        algorithm: String,
205    },
206
207    /// Random number generation failed
208    #[error("Random number generation failed: {reason}")]
209    RandomGenerationFailed {
210        /// Reason for RNG failure
211        reason: String,
212    },
213
214    /// Cryptographic batch operation failed
215    #[error("Batch verification failed: {passed}/{total} signatures valid")]
216    BatchVerificationFailed {
217        /// Number of signatures that passed
218        passed: usize,
219        /// Total number of signatures
220        total: usize,
221    },
222
223    /// HSM (Hardware Security Module) operation failed
224    #[error("HSM operation failed: {operation}")]
225    HsmError {
226        /// HSM operation that failed
227        operation: String,
228    },
229}
230
231/// Consensus and Byzantine Fault Tolerance errors
232#[derive(Debug, Error)]
233pub enum ConsensusError {
234    /// Insufficient votes to reach consensus
235    #[error("Consensus not reached: {votes_for}/{total_votes} votes (required {required})")]
236    ConsensusNotReached {
237        /// Votes in favor
238        votes_for: usize,
239        /// Total votes cast
240        total_votes: usize,
241        /// Required votes for consensus
242        required: usize,
243    },
244
245    /// Quorum not achieved
246    #[error("Quorum not achieved: {available}/{required} agents available")]
247    QuorumNotAchieved {
248        /// Number of available agents
249        available: usize,
250        /// Required number of agents
251        required: usize,
252    },
253
254    /// BFT voting round failed
255    #[error("BFT voting round {round} failed: {reason}")]
256    VotingRoundFailed {
257        /// Voting round number
258        round: u64,
259        /// Failure reason
260        reason: String,
261    },
262
263    /// Byzantine agent detected
264    #[error("Byzantine agent detected: {agent_id} - {behavior}")]
265    ByzantineAgentDetected {
266        /// ID of the Byzantine agent
267        agent_id: String,
268        /// Detected malicious behavior
269        behavior: String,
270    },
271
272    /// Vote validation failed
273    #[error("Invalid vote from agent {agent_id}: {reason}")]
274    InvalidVote {
275        /// Agent that cast the invalid vote
276        agent_id: String,
277        /// Reason the vote is invalid
278        reason: String,
279    },
280
281    /// Consensus timeout exceeded
282    #[error("Consensus timeout after {duration:?} with {votes_cast}/{total_agents} votes")]
283    ConsensusTimeout {
284        /// Time elapsed before timeout
285        duration: Duration,
286        /// Votes cast before timeout
287        votes_cast: usize,
288        /// Total number of agents
289        total_agents: usize,
290    },
291
292    /// Pool size too small for BFT
293    #[error("Pool size {size} too small for BFT (minimum {minimum} required)")]
294    InsufficientPoolSize {
295        /// Current pool size
296        size: usize,
297        /// Minimum required size
298        minimum: usize,
299    },
300
301    /// Multiple conflicting consensus results
302    #[error("Conflicting consensus results: {count} different outcomes")]
303    ConflictingResults {
304        /// Number of different outcomes
305        count: usize,
306    },
307
308    /// Invalid consensus state
309    #[error("Invalid consensus state: {0}")]
310    InvalidState(String),
311
312    /// Authority not found in consensus group
313    #[error("Authority not found: {0}")]
314    AuthorityNotFound(String),
315
316    /// Duplicate vote from same authority
317    #[error("Duplicate vote from authority: {0}")]
318    DuplicateVote(String),
319
320    /// Byzantine fault detected in consensus
321    #[error("Byzantine fault detected: {0}")]
322    ByzantineFault(String),
323
324    /// View change required for consensus
325    #[error("View change required: {0}")]
326    ViewChangeRequired(String),
327
328    /// Consensus already reached
329    #[error("Consensus already reached")]
330    AlreadyReached,
331}
332
333/// Agent lifecycle and communication errors
334#[derive(Debug, Error)]
335pub enum AgentError {
336    /// Agent spawning failed
337    #[error("Failed to spawn agent {agent_type}: {reason}")]
338    SpawnFailed {
339        /// Type of agent that failed to spawn
340        agent_type: String,
341        /// Reason for spawn failure
342        reason: String,
343    },
344
345    /// Agent not found
346    #[error("Agent not found: {agent_id}")]
347    AgentNotFound {
348        /// ID of the missing agent
349        agent_id: String,
350    },
351
352    /// Agent communication timeout
353    #[error("Agent {agent_id} communication timeout after {duration:?}")]
354    CommunicationTimeout {
355        /// ID of the unresponsive agent
356        agent_id: String,
357        /// Timeout duration
358        duration: Duration,
359    },
360
361    /// Agent crashed or terminated unexpectedly
362    #[error("Agent {agent_id} crashed: {reason}")]
363    AgentCrashed {
364        /// ID of the crashed agent
365        agent_id: String,
366        /// Crash reason
367        reason: String,
368    },
369
370    /// Agent recovery failed
371    #[error("Failed to recover agent {agent_id}: {reason}")]
372    RecoveryFailed {
373        /// ID of the agent that couldn't be recovered
374        agent_id: String,
375        /// Recovery failure reason
376        reason: String,
377    },
378
379    /// Agent pool exhausted
380    #[error("Agent pool exhausted: {active}/{maximum} agents, cannot spawn more")]
381    PoolExhausted {
382        /// Number of active agents
383        active: usize,
384        /// Maximum pool size
385        maximum: usize,
386    },
387
388    /// Invalid agent state transition
389    #[error("Invalid state transition for agent {agent_id}: {from} -> {to}")]
390    InvalidStateTransition {
391        /// Agent ID
392        agent_id: String,
393        /// Current state
394        from: String,
395        /// Attempted new state
396        to: String,
397    },
398
399    /// Agent task execution failed
400    #[error("Agent {agent_id} task execution failed: {task}")]
401    TaskExecutionFailed {
402        /// Agent ID
403        agent_id: String,
404        /// Task description
405        task: String,
406        /// Optional error details
407        details: Option<String>,
408    },
409
410    /// Inter-agent communication error
411    #[error("Communication error between {from_agent} and {to_agent}: {reason}")]
412    InterAgentCommunicationFailed {
413        /// Source agent
414        from_agent: String,
415        /// Destination agent
416        to_agent: String,
417        /// Failure reason
418        reason: String,
419    },
420
421    /// Agent pool error
422    #[error("Agent pool error: {reason}")]
423    AgentPoolError {
424        /// Error reason
425        reason: String,
426    },
427
428    /// Health check failed
429    #[error("Health check failed: {reason}")]
430    HealthCheckFailed {
431        /// Failure reason
432        reason: String,
433    },
434}
435
436/// Validation and trust chain errors
437#[derive(Debug, Error)]
438pub enum ValidationError {
439    /// Trust chain validation failed
440    #[error("Trust chain validation failed: {reason}")]
441    TrustChainInvalid {
442        /// Reason for validation failure
443        reason: String,
444        /// Chain depth at failure
445        depth: Option<usize>,
446    },
447
448    /// Certificate not found in chain
449    #[error("Certificate not found: {cert_id}")]
450    CertificateNotFound {
451        /// Certificate identifier
452        cert_id: String,
453    },
454
455    /// Certificate expired
456    #[error("Certificate expired: {cert_id} (expired at {expired_at})")]
457    CertificateExpired {
458        /// Certificate identifier
459        cert_id: String,
460        /// Expiration timestamp
461        expired_at: String,
462    },
463
464    /// Certificate revoked
465    #[error("Certificate revoked: {cert_id} (reason: {reason})")]
466    CertificateRevoked {
467        /// Certificate identifier
468        cert_id: String,
469        /// Revocation reason
470        reason: String,
471    },
472
473    /// Certificate issuer not trusted
474    #[error("Untrusted certificate issuer: {issuer}")]
475    UntrustedIssuer {
476        /// Issuer identifier
477        issuer: String,
478    },
479
480    /// Certificate chain too long
481    #[error("Certificate chain too long: {length} (maximum {maximum})")]
482    ChainTooLong {
483        /// Actual chain length
484        length: usize,
485        /// Maximum allowed length
486        maximum: usize,
487    },
488
489    /// Invalid certificate format
490    #[error("Invalid certificate format: {details}")]
491    InvalidCertificateFormat {
492        /// Format error details
493        details: String,
494    },
495
496    /// Verifiable Credential validation failed
497    #[error("Verifiable Credential validation failed: {reason}")]
498    CredentialValidationFailed {
499        /// Validation failure reason
500        reason: String,
501    },
502
503    /// DID (Decentralized Identifier) resolution failed
504    #[error("DID resolution failed: {did}")]
505    DidResolutionFailed {
506        /// DID that failed to resolve
507        did: String,
508    },
509
510    /// Mandate validation failed
511    #[error("Mandate validation failed: {reason}")]
512    MandateValidationFailed {
513        /// Validation failure reason
514        reason: String,
515    },
516
517    /// Invalid proof format
518    #[error("Invalid proof: {details}")]
519    InvalidProof {
520        /// Proof validation error details
521        details: String,
522    },
523}
524
525/// System configuration and initialization errors
526#[derive(Debug, Error)]
527pub enum SystemError {
528    /// System initialization failed
529    #[error("System initialization failed: {reason}")]
530    InitializationFailed {
531        /// Initialization failure reason
532        reason: String,
533    },
534
535    /// Invalid configuration
536    #[error("Invalid configuration: {parameter} = {value}")]
537    InvalidConfiguration {
538        /// Configuration parameter name
539        parameter: String,
540        /// Invalid value
541        value: String,
542        /// Optional expected value description
543        expected: Option<String>,
544    },
545
546    /// Resource allocation failed
547    #[error("Resource allocation failed: {resource}")]
548    ResourceAllocationFailed {
549        /// Resource type
550        resource: String,
551        /// Optional details
552        details: Option<String>,
553    },
554
555    /// System not initialized
556    #[error("System not initialized: {component}")]
557    NotInitialized {
558        /// Component that requires initialization
559        component: String,
560    },
561
562    /// System already initialized
563    #[error("System already initialized: {component}")]
564    AlreadyInitialized {
565        /// Component that's already initialized
566        component: String,
567    },
568
569    /// Graceful shutdown failed
570    #[error("Shutdown failed: {reason}")]
571    ShutdownFailed {
572        /// Shutdown failure reason
573        reason: String,
574    },
575
576    /// Database connection error
577    #[error("Database error: {operation}")]
578    DatabaseError {
579        /// Database operation that failed
580        operation: String,
581        /// Optional error details
582        details: Option<String>,
583    },
584
585    /// Metrics system error
586    #[error("Metrics error: {reason}")]
587    MetricsError {
588        /// Metrics error reason
589        reason: String,
590    },
591
592    /// Thread pool error
593    #[error("Thread pool error: {reason}")]
594    ThreadPoolError {
595        /// Thread pool error reason
596        reason: String,
597    },
598}
599
600/// Network and communication errors
601#[derive(Debug, Error)]
602pub enum NetworkError {
603    /// Connection failed
604    #[error("Connection failed to {endpoint}: {reason}")]
605    ConnectionFailed {
606        /// Target endpoint
607        endpoint: String,
608        /// Connection failure reason
609        reason: String,
610    },
611
612    /// Connection timeout
613    #[error("Connection timeout to {endpoint} after {duration:?}")]
614    ConnectionTimeout {
615        /// Target endpoint
616        endpoint: String,
617        /// Timeout duration
618        duration: Duration,
619    },
620
621    /// Network partition detected
622    #[error("Network partition detected: {details}")]
623    NetworkPartition {
624        /// Partition details
625        details: String,
626    },
627
628    /// Message send failed
629    #[error("Failed to send message to {recipient}: {reason}")]
630    SendFailed {
631        /// Message recipient
632        recipient: String,
633        /// Send failure reason
634        reason: String,
635    },
636
637    /// Message receive failed
638    #[error("Failed to receive message from {sender}: {reason}")]
639    ReceiveFailed {
640        /// Message sender
641        sender: String,
642        /// Receive failure reason
643        reason: String,
644    },
645
646    /// Protocol error
647    #[error("Protocol error: {protocol} - {details}")]
648    ProtocolError {
649        /// Protocol name
650        protocol: String,
651        /// Error details
652        details: String,
653    },
654
655    /// Peer disconnected
656    #[error("Peer disconnected: {peer_id}")]
657    PeerDisconnected {
658        /// Disconnected peer ID
659        peer_id: String,
660    },
661
662    /// Invalid network address
663    #[error("Invalid network address: {address}")]
664    InvalidAddress {
665        /// Invalid address
666        address: String,
667    },
668}
669
670/// Serialization and deserialization errors
671#[derive(Debug, Error)]
672pub enum SerializationError {
673    /// JSON serialization failed
674    #[error("JSON serialization error: {0}")]
675    Json(#[from] serde_json::Error),
676
677    /// Base64 encoding/decoding failed
678    #[error("Base64 error: {details}")]
679    Base64 {
680        /// Error details
681        details: String,
682    },
683
684    /// Invalid data format
685    #[error("Invalid data format: expected {expected}, got {actual}")]
686    InvalidFormat {
687        /// Expected format
688        expected: String,
689        /// Actual format
690        actual: String,
691    },
692
693    /// Data corruption detected
694    #[error("Data corruption detected: {details}")]
695    DataCorruption {
696        /// Corruption details
697        details: String,
698    },
699}
700
701// Conversion implementations for external error types
702
703impl From<ed25519_dalek::SignatureError> for Error {
704    fn from(err: ed25519_dalek::SignatureError) -> Self {
705        Error::Crypto(CryptoError::SignatureVerificationFailed {
706            reason: err.to_string(),
707            public_key: None,
708        })
709    }
710}
711
712impl From<pkcs8::Error> for Error {
713    fn from(err: pkcs8::Error) -> Self {
714        Error::Crypto(CryptoError::Pkcs8Error {
715            details: err.to_string(),
716        })
717    }
718}
719
720impl From<base64::DecodeError> for Error {
721    fn from(err: base64::DecodeError) -> Self {
722        Error::Serialization(SerializationError::Base64 {
723            details: err.to_string(),
724        })
725    }
726}
727
728impl From<uuid::Error> for Error {
729    fn from(err: uuid::Error) -> Self {
730        Error::Serialization(SerializationError::InvalidFormat {
731            expected: "valid UUID".to_string(),
732            actual: err.to_string(),
733        })
734    }
735}
736
737// Helper methods for creating common errors
738
739impl Error {
740    /// Create a timeout error
741    pub fn timeout(operation: impl Into<String>, duration: Duration) -> Self {
742        Self::Timeout {
743            operation: operation.into(),
744            duration,
745        }
746    }
747
748    /// Create a generic error with a message
749    pub fn other(message: impl Into<String>) -> Self {
750        Self::Other {
751            message: message.into(),
752            source: None,
753        }
754    }
755
756    /// Create a generic error with a message and source
757    pub fn with_source(
758        message: impl Into<String>,
759        source: impl std::error::Error + Send + Sync + 'static,
760    ) -> Self {
761        Self::Other {
762            message: message.into(),
763            source: Some(Box::new(source)),
764        }
765    }
766
767    /// Create an agent pool error
768    pub fn agent_pool(message: impl Into<String>) -> Self {
769        Self::Agent(AgentError::AgentPoolError {
770            reason: message.into(),
771        })
772    }
773
774    /// Create a configuration error
775    pub fn config(message: impl Into<String>) -> Self {
776        Self::Configuration(message.into())
777    }
778
779    /// Create a verification error
780    pub fn verification(message: impl Into<String>) -> Self {
781        Self::Crypto(CryptoError::SignatureVerificationFailed {
782            reason: message.into(),
783            public_key: None,
784        })
785    }
786
787    /// Create a health check error
788    pub fn health_check(message: impl Into<String>) -> Self {
789        Self::Agent(AgentError::HealthCheckFailed {
790            reason: message.into(),
791        })
792    }
793}
794
795impl CryptoError {
796    /// Create a signature verification failure error
797    pub fn signature_failed(reason: impl Into<String>) -> Self {
798        Self::SignatureVerificationFailed {
799            reason: reason.into(),
800            public_key: None,
801        }
802    }
803
804    /// Create a signature verification failure error with public key context
805    pub fn signature_failed_with_key(reason: impl Into<String>, public_key: impl Into<String>) -> Self {
806        Self::SignatureVerificationFailed {
807            reason: reason.into(),
808            public_key: Some(public_key.into()),
809        }
810    }
811}
812
813impl ConsensusError {
814    /// Create a consensus not reached error
815    pub fn not_reached(votes_for: usize, total_votes: usize, required: usize) -> Self {
816        Self::ConsensusNotReached {
817            votes_for,
818            total_votes,
819            required,
820        }
821    }
822
823    /// Create a quorum not achieved error
824    pub fn no_quorum(available: usize, required: usize) -> Self {
825        Self::QuorumNotAchieved { available, required }
826    }
827}
828
829impl AgentError {
830    /// Create a spawn failed error
831    pub fn spawn_failed(agent_type: impl Into<String>, reason: impl Into<String>) -> Self {
832        Self::SpawnFailed {
833            agent_type: agent_type.into(),
834            reason: reason.into(),
835        }
836    }
837
838    /// Create an agent not found error
839    pub fn not_found(agent_id: impl Into<String>) -> Self {
840        Self::AgentNotFound {
841            agent_id: agent_id.into(),
842        }
843    }
844}
845
846impl ValidationError {
847    /// Create a trust chain invalid error
848    pub fn trust_chain_invalid(reason: impl Into<String>) -> Self {
849        Self::TrustChainInvalid {
850            reason: reason.into(),
851            depth: None,
852        }
853    }
854
855    /// Create a certificate expired error
856    pub fn certificate_expired(cert_id: impl Into<String>, expired_at: impl Into<String>) -> Self {
857        Self::CertificateExpired {
858            cert_id: cert_id.into(),
859            expired_at: expired_at.into(),
860        }
861    }
862}
863
864impl SystemError {
865    /// Create an initialization failed error
866    pub fn init_failed(reason: impl Into<String>) -> Self {
867        Self::InitializationFailed {
868            reason: reason.into(),
869        }
870    }
871
872    /// Create an invalid configuration error
873    pub fn invalid_config(parameter: impl Into<String>, value: impl Into<String>) -> Self {
874        Self::InvalidConfiguration {
875            parameter: parameter.into(),
876            value: value.into(),
877            expected: None,
878        }
879    }
880}
881
882#[cfg(test)]
883mod tests {
884    use super::*;
885
886    #[test]
887    fn test_error_display() {
888        let err = Error::Crypto(CryptoError::SignatureVerificationFailed {
889            reason: "invalid signature".to_string(),
890            public_key: Some("abc123".to_string()),
891        });
892        assert!(err.to_string().contains("Cryptographic error"));
893        assert!(err.to_string().contains("Signature verification failed"));
894    }
895
896    #[test]
897    fn test_consensus_error_display() {
898        let err = ConsensusError::not_reached(3, 5, 4);
899        assert!(err.to_string().contains("3/5"));
900        assert!(err.to_string().contains("required 4"));
901    }
902
903    #[test]
904    fn test_error_helper_methods() {
905        let err = Error::timeout("verification", Duration::from_secs(5));
906        assert!(matches!(err, Error::Timeout { .. }));
907
908        let err = Error::other("something went wrong");
909        assert!(matches!(err, Error::Other { .. }));
910    }
911
912    #[test]
913    fn test_crypto_error_helper() {
914        let err = CryptoError::signature_failed("bad sig");
915        assert!(matches!(err, CryptoError::SignatureVerificationFailed { .. }));
916    }
917
918    #[test]
919    fn test_agent_error_helper() {
920        let err = AgentError::spawn_failed("verifier", "out of memory");
921        assert!(matches!(err, AgentError::SpawnFailed { .. }));
922    }
923
924    #[test]
925    fn test_error_source_chain() {
926        let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
927        let err: Error = io_err.into();
928        assert!(matches!(err, Error::Io(_)));
929    }
930
931    #[test]
932    fn test_serialization_error_from_json() {
933        let json_str = "{invalid json}";
934        let json_err = serde_json::from_str::<serde_json::Value>(json_str).unwrap_err();
935        let ser_err: SerializationError = json_err.into();
936        let err: Error = ser_err.into();
937        assert!(matches!(err, Error::Serialization(SerializationError::Json(_))));
938    }
939}