1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5use crate::contracts::Capability;
6use crate::errors::{KernelError, PolicyError};
7
8#[non_exhaustive]
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub enum Fault {
16 Panic {
17 message: String,
18 },
19 CapabilityViolation {
20 token_id: String,
21 capability: Capability,
22 },
23 TokenExpired {
24 token_id: String,
25 expires_at_epoch_s: u64,
26 },
27 ProtocolViolation {
28 detail: String,
29 },
30 PolicyDenied {
31 reason: String,
32 },
33}
34
35impl fmt::Display for Fault {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 match self {
38 Self::Panic { message } => write!(f, "panic: {message}"),
39 Self::CapabilityViolation {
40 token_id,
41 capability,
42 } => {
43 write!(
44 f,
45 "capability violation: token {token_id} missing {capability:?}"
46 )
47 }
48 Self::TokenExpired {
49 token_id,
50 expires_at_epoch_s,
51 } => {
52 write!(f, "token {token_id} expired at {expires_at_epoch_s}")
53 }
54 Self::ProtocolViolation { detail } => write!(f, "protocol violation: {detail}"),
55 Self::PolicyDenied { reason } => write!(f, "policy denied: {reason}"),
56 }
57 }
58}
59
60impl std::error::Error for Fault {}
61
62impl Fault {
63 pub fn from_policy_error(err: PolicyError) -> Self {
64 match err {
65 PolicyError::ExpiredToken {
66 token_id,
67 expires_at_epoch_s,
68 } => Self::TokenExpired {
69 token_id,
70 expires_at_epoch_s,
71 },
72 PolicyError::MissingCapability {
73 token_id,
74 capability,
75 } => Self::CapabilityViolation {
76 token_id,
77 capability,
78 },
79 PolicyError::RevokedToken { token_id } => Self::PolicyDenied {
80 reason: format!("token {token_id} revoked"),
81 },
82 PolicyError::PackMismatch {
83 token_pack_id,
84 runtime_pack_id,
85 } => Self::PolicyDenied {
86 reason: format!("pack mismatch: token={token_pack_id} runtime={runtime_pack_id}"),
87 },
88 PolicyError::ExtensionDenied { extension, reason } => Self::PolicyDenied {
89 reason: format!("extension {extension}: {reason}"),
90 },
91 PolicyError::ToolCallDenied { tool_name, reason } => Self::PolicyDenied {
92 reason: format!("tool {tool_name}: {reason}"),
93 },
94 }
95 }
96
97 pub fn from_kernel_error(err: KernelError) -> Self {
98 match err {
99 KernelError::Policy(policy_err) => Self::from_policy_error(policy_err),
100 KernelError::PackCapabilityBoundary {
101 capability,
102 pack_id,
103 } => Self::CapabilityViolation {
104 token_id: format!("pack:{pack_id}"),
105 capability,
106 },
107 KernelError::PackNotFound(_)
108 | KernelError::DuplicatePack(_)
109 | KernelError::ConnectorNotAllowed { .. }
110 | KernelError::Pack(_)
111 | KernelError::Harness(_)
112 | KernelError::Connector(_)
113 | KernelError::RuntimePlane(_)
114 | KernelError::ToolPlane(_)
115 | KernelError::MemoryPlane(_)
116 | KernelError::Integration(_)
117 | KernelError::Audit(_) => Self::Panic {
118 message: err.to_string(),
119 },
120 }
121 }
122}