Skip to main content

a1/
error.rs

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}