1use thiserror::Error;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub enum StorageErrorKind {
5 Transient,
6 Permanent,
7}
8
9#[derive(Debug, Clone)]
10pub struct A1StorageError {
11 pub kind: StorageErrorKind,
12 pub message: String,
13}
14
15impl PartialEq for A1StorageError {
16 fn eq(&self, other: &Self) -> bool {
17 self.kind == other.kind
18 }
19}
20
21impl Eq for A1StorageError {}
22
23impl A1StorageError {
24 pub fn transient(msg: impl Into<String>) -> Self {
25 Self {
26 kind: StorageErrorKind::Transient,
27 message: msg.into(),
28 }
29 }
30
31 pub fn permanent(msg: impl Into<String>) -> Self {
32 Self {
33 kind: StorageErrorKind::Permanent,
34 message: msg.into(),
35 }
36 }
37
38 pub fn is_transient(&self) -> bool {
39 self.kind == StorageErrorKind::Transient
40 }
41}
42
43impl std::fmt::Display for A1StorageError {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 let label = match self.kind {
46 StorageErrorKind::Transient => "transient",
47 StorageErrorKind::Permanent => "permanent",
48 };
49 write!(f, "{label} storage error: {}", self.message)
50 }
51}
52
53impl std::error::Error for A1StorageError {}
54
55#[derive(Debug, Error, PartialEq, Eq)]
56#[non_exhaustive]
57pub enum A1Error {
58 #[error("delegation chain is empty")]
59 EmptyChain,
60
61 #[error("storage backend failure: {0}")]
62 StorageFailure(A1StorageError),
63
64 #[error("chain does not anchor to the declared principal")]
65 RootMismatch,
66
67 #[error("delegation linkage broken at hop {0}")]
68 BrokenLinkage(usize),
69
70 #[error("invalid signature at hop {0}")]
71 InvalidSignature(usize),
72
73 #[error("delegation at hop {0} not yet valid (issued_at={1}, now={2})")]
74 NotYetValid(usize, u64, u64),
75
76 #[error("delegation at hop {0} has expired (expiry={1}, now={2})")]
77 Expired(usize, u64, u64),
78
79 #[error("temporal violation at hop {0}: child expiry {1} exceeds parent expiry {2}")]
80 TemporalViolation(usize, u64, u64),
81
82 #[error("depth limit exceeded at hop {0} (limit={1})")]
83 MaxDepthExceeded(usize, u8),
84
85 #[error("sub-scope proof is structurally invalid")]
86 InvalidSubScopeProof,
87
88 #[error(
89 "scope escalation at hop {0}: delegated scope is not within the delegator's authorization"
90 )]
91 ScopeEscalation(usize),
92
93 #[error("executing agent is not the terminal delegate")]
94 UnauthorizedLeaf,
95
96 #[error("execution intent is not within the terminal scope")]
97 ScopeViolation,
98
99 #[error("nonce has already been consumed")]
100 NonceReplay,
101
102 #[error("delegation certificate has been revoked")]
103 Revoked,
104
105 #[error("intent is not present in this tree")]
106 IntentNotFound,
107
108 #[error("intent tree requires at least one intent")]
109 EmptyTree,
110
111 #[error("wire format error: {0}")]
112 WireFormatError(String),
113
114 #[error("unsupported certificate version: expected {expected}, got {got}")]
115 UnsupportedVersion { expected: u8, got: u8 },
116
117 #[error("policy violation: {0}")]
118 PolicyViolation(String),
119
120 #[error("batch authorization failed at index {index}: {reason}")]
121 BatchItemFailed { index: usize, reason: String },
122
123 #[error("MAC verification failed")]
124 MacVerificationFailed,
125
126 #[error("namespace mismatch: chain namespace is '{chain}', authorization requested for '{requested}'")]
127 NamespaceMismatch { chain: String, requested: String },
128
129 #[error("rate limit exceeded for key")]
130 RateLimitExceeded,
131
132 #[error("storage health check failed: {0}")]
133 StorageUnhealthy(String),
134
135 #[error("passport capability narrowing violation: requested scope exceeds granted passport capabilities")]
136 PassportNarrowingViolation,
137
138 #[error("unsupported signature algorithm tag: {0}")]
139 UnsupportedAlgorithm(u8),
140
141 #[error("signature algorithm mismatch: expected {expected}, found {found}")]
142 AlgorithmMismatch {
143 expected: &'static str,
144 found: &'static str,
145 },
146
147 #[error("hybrid signature invalid: {component} component failed verification")]
148 HybridSignatureInvalid { component: &'static str },
149
150 #[error("post-quantum signature component is missing for algorithm {0}")]
151 PqSignatureMissing(&'static str),
152
153 #[error("invalid hybrid key length for {algorithm}: expected {expected} bytes, found {found}")]
154 InvalidHybridKeyLength {
155 algorithm: &'static str,
156 expected: usize,
157 found: usize,
158 },
159}
160
161impl A1Error {
162 pub fn as_storage_error(&self) -> Option<&A1StorageError> {
163 if let Self::StorageFailure(e) = self {
164 Some(e)
165 } else {
166 None
167 }
168 }
169
170 pub fn is_transient_storage_failure(&self) -> bool {
171 self.as_storage_error().is_some_and(|e| e.is_transient())
172 }
173
174 pub fn error_code(&self) -> &'static str {
175 match self {
176 Self::Expired(..) => "CERT_EXPIRED",
177 Self::Revoked => "CERT_REVOKED",
178 Self::NonceReplay => "NONCE_REPLAY",
179 Self::ScopeViolation => "SCOPE_VIOLATION",
180 Self::ScopeEscalation(_) => "SCOPE_ESCALATION",
181 Self::InvalidSignature(_) => "INVALID_SIGNATURE",
182 Self::BrokenLinkage(_) => "CHAIN_BROKEN_LINKAGE",
183 Self::MaxDepthExceeded(..) => "CHAIN_DEPTH_EXCEEDED",
184 Self::PolicyViolation(_) => "POLICY_VIOLATION",
185 Self::StorageFailure(_) => "STORAGE_ERROR",
186 Self::BatchItemFailed { .. } => "BATCH_ITEM_FAILED",
187 Self::MacVerificationFailed => "MAC_VERIFICATION_FAILED",
188 Self::NamespaceMismatch { .. } => "NAMESPACE_MISMATCH",
189 Self::RateLimitExceeded => "RATE_LIMIT_EXCEEDED",
190 Self::StorageUnhealthy(_) => "STORAGE_UNHEALTHY",
191 Self::PassportNarrowingViolation => "PASSPORT_NARROWING_VIOLATION",
192 Self::UnsupportedAlgorithm(_) => "UNSUPPORTED_ALGORITHM",
193 Self::AlgorithmMismatch { .. } => "ALGORITHM_MISMATCH",
194 Self::HybridSignatureInvalid { .. } => "HYBRID_SIGNATURE_INVALID",
195 Self::PqSignatureMissing(_) => "PQ_SIGNATURE_MISSING",
196 Self::InvalidHybridKeyLength { .. } => "INVALID_HYBRID_KEY_LENGTH",
197 _ => "AUTHORIZATION_FAILED",
198 }
199 }
200
201 pub fn http_status(&self) -> u16 {
202 match self {
203 Self::StorageFailure(e) if e.is_transient() => 503,
204 Self::StorageFailure(_) => 500,
205 Self::StorageUnhealthy(_) => 503,
206 Self::RateLimitExceeded => 429,
207 Self::EmptyChain | Self::WireFormatError(_) | Self::UnsupportedVersion { .. } => 400,
208 Self::Revoked | Self::Expired(..) | Self::NotYetValid(..) | Self::NonceReplay => 401,
209 Self::ScopeViolation | Self::ScopeEscalation(_) | Self::UnauthorizedLeaf => 403,
210 Self::InvalidSignature(_) | Self::RootMismatch | Self::BrokenLinkage(_) => 403,
211 Self::PolicyViolation(_) | Self::NamespaceMismatch { .. } => 403,
212 Self::MacVerificationFailed => 401,
213 Self::PassportNarrowingViolation => 403,
214 Self::UnsupportedAlgorithm(_) => 400,
215 Self::AlgorithmMismatch { .. } => 400,
216 Self::HybridSignatureInvalid { .. } => 403,
217 Self::PqSignatureMissing(_) => 400,
218 Self::InvalidHybridKeyLength { .. } => 400,
219 _ => 403,
220 }
221 }
222}