1use serde::{Deserialize, Serialize};
11
12use crate::event::EventKind;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Carrier {
18 Event(EventKind),
19 Loadtime,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
25pub enum AxCode {
26 PathNotFound,
28 ToolUnknown,
29 ToolUnavailable,
30 InvalidArgs,
31 OutsideWriteDomain,
32 VersionConflict,
33 GateDenied,
34 BudgetExhausted,
35 Timeout,
36 Provider,
37 EvidenceMissing,
38 LoopSuspected,
39 LocatorInvalid,
40 SandboxDenied,
41 Busy,
48 DraftStale,
54 GoalConflict,
55 TaintedAction,
56 RepairBusy,
57 DelegationDepth,
58 ApprovalPending,
60 ApprovalDenied,
61 CrossBuildingDenied,
62 DigestSuspect,
63 CredentialMissing,
64 ModelUnchosen,
70 ConfigInvalid,
71 CasCorrupt,
72 StorageFatal,
73 WorktreeBusy,
74 BrowserUnavailable,
75 EndpointDialectUnsupported,
76 WireMismatch,
77 LogVersionUnsupported,
78 LedgerHeld,
81 HistoryUnproven,
86 SecretEgress,
88 DiscardIrreversible,
89 BackpressureShed,
93 ToolOutcomeUnknown,
95 PlanMissing,
99 ProviderAccountsExhausted,
104 AuthRequired,
107 PairingRefused,
108}
109
110impl AxCode {
111 pub const ALL: [AxCode; 44] = [
114 AxCode::PathNotFound,
115 AxCode::ToolUnknown,
116 AxCode::ToolUnavailable,
117 AxCode::InvalidArgs,
118 AxCode::OutsideWriteDomain,
119 AxCode::VersionConflict,
120 AxCode::GateDenied,
121 AxCode::BudgetExhausted,
122 AxCode::Timeout,
123 AxCode::Provider,
124 AxCode::EvidenceMissing,
125 AxCode::LoopSuspected,
126 AxCode::LocatorInvalid,
127 AxCode::SandboxDenied,
128 AxCode::Busy,
129 AxCode::DraftStale,
130 AxCode::GoalConflict,
131 AxCode::TaintedAction,
132 AxCode::RepairBusy,
133 AxCode::DelegationDepth,
134 AxCode::ApprovalPending,
135 AxCode::ApprovalDenied,
136 AxCode::CrossBuildingDenied,
137 AxCode::DigestSuspect,
138 AxCode::CredentialMissing,
139 AxCode::ModelUnchosen,
140 AxCode::ConfigInvalid,
141 AxCode::CasCorrupt,
142 AxCode::StorageFatal,
143 AxCode::WorktreeBusy,
144 AxCode::BrowserUnavailable,
145 AxCode::EndpointDialectUnsupported,
146 AxCode::WireMismatch,
147 AxCode::LogVersionUnsupported,
148 AxCode::LedgerHeld,
149 AxCode::HistoryUnproven,
150 AxCode::SecretEgress,
151 AxCode::DiscardIrreversible,
152 AxCode::BackpressureShed,
153 AxCode::ToolOutcomeUnknown,
154 AxCode::PlanMissing,
155 AxCode::ProviderAccountsExhausted,
156 AxCode::AuthRequired,
157 AxCode::PairingRefused,
158 ];
159
160 pub fn as_str(&self) -> &'static str {
162 match self {
163 AxCode::PathNotFound => "E_PATH_NOT_FOUND",
164 AxCode::ToolUnknown => "E_TOOL_UNKNOWN",
165 AxCode::ToolUnavailable => "E_TOOL_UNAVAILABLE",
166 AxCode::InvalidArgs => "E_INVALID_ARGS",
167 AxCode::OutsideWriteDomain => "E_OUTSIDE_WRITE_DOMAIN",
168 AxCode::VersionConflict => "E_VERSION_CONFLICT",
169 AxCode::GateDenied => "E_GATE_DENIED",
170 AxCode::BudgetExhausted => "E_BUDGET_EXHAUSTED",
171 AxCode::Timeout => "E_TIMEOUT",
172 AxCode::Provider => "E_PROVIDER",
173 AxCode::EvidenceMissing => "E_EVIDENCE_MISSING",
174 AxCode::LoopSuspected => "E_LOOP_SUSPECTED",
175 AxCode::LocatorInvalid => "E_LOCATOR_INVALID",
176 AxCode::SandboxDenied => "E_SANDBOX_DENIED",
177 AxCode::Busy => "E_BUSY",
178 AxCode::DraftStale => "E_DRAFT_STALE",
179 AxCode::GoalConflict => "E_GOAL_CONFLICT",
180 AxCode::TaintedAction => "E_TAINTED_ACTION",
181 AxCode::RepairBusy => "E_REPAIR_BUSY",
182 AxCode::DelegationDepth => "E_DELEGATION_DEPTH",
183 AxCode::ApprovalPending => "E_APPROVAL_PENDING",
184 AxCode::ApprovalDenied => "E_APPROVAL_DENIED",
185 AxCode::CrossBuildingDenied => "E_CROSS_BUILDING_DENIED",
186 AxCode::DigestSuspect => "E_DIGEST_SUSPECT",
187 AxCode::CredentialMissing => "E_CREDENTIAL_MISSING",
188 AxCode::ModelUnchosen => "E_MODEL_UNCHOSEN",
189 AxCode::ConfigInvalid => "E_CONFIG_INVALID",
190 AxCode::CasCorrupt => "E_CAS_CORRUPT",
191 AxCode::StorageFatal => "E_STORAGE_FATAL",
192 AxCode::WorktreeBusy => "E_WORKTREE_BUSY",
193 AxCode::BrowserUnavailable => "E_BROWSER_UNAVAILABLE",
194 AxCode::EndpointDialectUnsupported => "E_ENDPOINT_DIALECT_UNSUPPORTED",
195 AxCode::WireMismatch => "E_WIRE_MISMATCH",
196 AxCode::LogVersionUnsupported => "E_LOG_VERSION_UNSUPPORTED",
197 AxCode::LedgerHeld => "E_LEDGER_HELD",
198 AxCode::HistoryUnproven => "E_HISTORY_UNPROVEN",
199 AxCode::SecretEgress => "E_SECRET_EGRESS",
200 AxCode::BackpressureShed => "E_BACKPRESSURE_SHED",
201 AxCode::DiscardIrreversible => "E_DISCARD_IRREVERSIBLE",
202 AxCode::ToolOutcomeUnknown => "E_TOOL_OUTCOME_UNKNOWN",
203 AxCode::PlanMissing => "E_PLAN_MISSING",
204 AxCode::ProviderAccountsExhausted => "E_PROVIDER_ACCOUNTS_EXHAUSTED",
205 AxCode::AuthRequired => "E_AUTH_REQUIRED",
206 AxCode::PairingRefused => "E_PAIRING_REFUSED",
207 }
208 }
209
210 pub fn parse(raw: &str) -> Option<AxCode> {
212 AxCode::ALL.into_iter().find(|code| code.as_str() == raw)
213 }
214
215 pub fn carrier(&self) -> Carrier {
222 match self {
223 AxCode::GateDenied
224 | AxCode::OutsideWriteDomain
225 | AxCode::TaintedAction
226 | AxCode::CrossBuildingDenied
227 | AxCode::DiscardIrreversible
228 | AxCode::SecretEgress
229 | AxCode::DelegationDepth => Carrier::Event(EventKind::GateDenied),
230 AxCode::ApprovalPending => Carrier::Event(EventKind::ApprovalRequested),
231 AxCode::ApprovalDenied => Carrier::Event(EventKind::ApprovalResolved),
232 AxCode::BudgetExhausted => Carrier::Event(EventKind::BudgetLimit),
233 AxCode::LoopSuspected => Carrier::Event(EventKind::WatchdogFired),
234 AxCode::Provider | AxCode::ProviderAccountsExhausted => {
235 Carrier::Event(EventKind::ProviderDegraded)
236 }
237 AxCode::EndpointDialectUnsupported => Carrier::Event(EventKind::EndpointLost),
238 AxCode::ConfigInvalid
239 | AxCode::CasCorrupt
240 | AxCode::StorageFatal
241 | AxCode::WireMismatch
242 | AxCode::LogVersionUnsupported
243 | AxCode::LedgerHeld
244 | AxCode::HistoryUnproven => Carrier::Loadtime,
245 AxCode::PathNotFound
246 | AxCode::ToolUnknown
247 | AxCode::ToolUnavailable
248 | AxCode::InvalidArgs
249 | AxCode::VersionConflict
250 | AxCode::Timeout
251 | AxCode::EvidenceMissing
252 | AxCode::LocatorInvalid
253 | AxCode::SandboxDenied
254 | AxCode::Busy
255 | AxCode::DraftStale
256 | AxCode::GoalConflict
257 | AxCode::RepairBusy
258 | AxCode::DigestSuspect
259 | AxCode::CredentialMissing
260 | AxCode::ModelUnchosen
261 | AxCode::WorktreeBusy
262 | AxCode::BrowserUnavailable
263 | AxCode::ToolOutcomeUnknown
264 | AxCode::BackpressureShed
265 | AxCode::PlanMissing
266 | AxCode::AuthRequired
267 | AxCode::PairingRefused => Carrier::Event(EventKind::ToolResult),
268 }
269 }
270}
271
272impl std::fmt::Display for AxCode {
273 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274 f.write_str(self.as_str())
275 }
276}
277
278impl Serialize for AxCode {
279 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
280 serializer.serialize_str(self.as_str())
281 }
282}
283
284impl<'de> Deserialize<'de> for AxCode {
285 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
286 let raw = String::deserialize(deserializer)?;
287 AxCode::parse(&raw)
288 .ok_or_else(|| serde::de::Error::custom(format_args!("unknown AxCode `{raw}`")))
289 }
290}
291
292#[cfg(test)]
293#[allow(
294 clippy::unwrap_used,
295 clippy::expect_used,
296 clippy::panic,
297 clippy::indexing_slicing,
298 reason = "test code"
299)]
300mod tests {
301 use super::*;
302 use std::collections::BTreeSet;
303
304 #[test]
305 fn axcode_is_the_declared_length_and_spelling_is_bijective() {
306 assert_eq!(AxCode::ALL.len(), 44);
310 let spellings: BTreeSet<&str> = AxCode::ALL.iter().map(AxCode::as_str).collect();
311 assert_eq!(spellings.len(), 44);
312 for s in &spellings {
313 assert!(s.starts_with("E_"));
314 }
315 }
316
317 #[test]
318 fn axcode_parse_roundtrips_every_variant() {
319 for code in AxCode::ALL {
320 assert_eq!(AxCode::parse(code.as_str()), Some(code));
321 }
322 assert_eq!(AxCode::parse("E_NO_SUCH_CODE"), None);
323 }
324
325 #[test]
326 fn axcode_serde_uses_the_wire_spelling() {
327 let json = serde_json::to_string(&AxCode::PathNotFound).unwrap();
328 assert_eq!(json, "\"E_PATH_NOT_FOUND\"");
329 let back: AxCode = serde_json::from_str("\"E_LOCATOR_INVALID\"").unwrap();
330 assert_eq!(back, AxCode::LocatorInvalid);
331 assert!(serde_json::from_str::<AxCode>("\"E_BOGUS\"").is_err());
332 }
333}