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}
100
101impl AxCode {
102 pub const ALL: [AxCode; 41] = [
105 AxCode::PathNotFound,
106 AxCode::ToolUnknown,
107 AxCode::ToolUnavailable,
108 AxCode::InvalidArgs,
109 AxCode::OutsideWriteDomain,
110 AxCode::VersionConflict,
111 AxCode::GateDenied,
112 AxCode::BudgetExhausted,
113 AxCode::Timeout,
114 AxCode::Provider,
115 AxCode::EvidenceMissing,
116 AxCode::LoopSuspected,
117 AxCode::LocatorInvalid,
118 AxCode::SandboxDenied,
119 AxCode::Busy,
120 AxCode::DraftStale,
121 AxCode::GoalConflict,
122 AxCode::TaintedAction,
123 AxCode::RepairBusy,
124 AxCode::DelegationDepth,
125 AxCode::ApprovalPending,
126 AxCode::ApprovalDenied,
127 AxCode::CrossBuildingDenied,
128 AxCode::DigestSuspect,
129 AxCode::CredentialMissing,
130 AxCode::ModelUnchosen,
131 AxCode::ConfigInvalid,
132 AxCode::CasCorrupt,
133 AxCode::StorageFatal,
134 AxCode::WorktreeBusy,
135 AxCode::BrowserUnavailable,
136 AxCode::EndpointDialectUnsupported,
137 AxCode::WireMismatch,
138 AxCode::LogVersionUnsupported,
139 AxCode::LedgerHeld,
140 AxCode::HistoryUnproven,
141 AxCode::SecretEgress,
142 AxCode::DiscardIrreversible,
143 AxCode::BackpressureShed,
144 AxCode::ToolOutcomeUnknown,
145 AxCode::PlanMissing,
146 ];
147
148 pub fn as_str(&self) -> &'static str {
150 match self {
151 AxCode::PathNotFound => "E_PATH_NOT_FOUND",
152 AxCode::ToolUnknown => "E_TOOL_UNKNOWN",
153 AxCode::ToolUnavailable => "E_TOOL_UNAVAILABLE",
154 AxCode::InvalidArgs => "E_INVALID_ARGS",
155 AxCode::OutsideWriteDomain => "E_OUTSIDE_WRITE_DOMAIN",
156 AxCode::VersionConflict => "E_VERSION_CONFLICT",
157 AxCode::GateDenied => "E_GATE_DENIED",
158 AxCode::BudgetExhausted => "E_BUDGET_EXHAUSTED",
159 AxCode::Timeout => "E_TIMEOUT",
160 AxCode::Provider => "E_PROVIDER",
161 AxCode::EvidenceMissing => "E_EVIDENCE_MISSING",
162 AxCode::LoopSuspected => "E_LOOP_SUSPECTED",
163 AxCode::LocatorInvalid => "E_LOCATOR_INVALID",
164 AxCode::SandboxDenied => "E_SANDBOX_DENIED",
165 AxCode::Busy => "E_BUSY",
166 AxCode::DraftStale => "E_DRAFT_STALE",
167 AxCode::GoalConflict => "E_GOAL_CONFLICT",
168 AxCode::TaintedAction => "E_TAINTED_ACTION",
169 AxCode::RepairBusy => "E_REPAIR_BUSY",
170 AxCode::DelegationDepth => "E_DELEGATION_DEPTH",
171 AxCode::ApprovalPending => "E_APPROVAL_PENDING",
172 AxCode::ApprovalDenied => "E_APPROVAL_DENIED",
173 AxCode::CrossBuildingDenied => "E_CROSS_BUILDING_DENIED",
174 AxCode::DigestSuspect => "E_DIGEST_SUSPECT",
175 AxCode::CredentialMissing => "E_CREDENTIAL_MISSING",
176 AxCode::ModelUnchosen => "E_MODEL_UNCHOSEN",
177 AxCode::ConfigInvalid => "E_CONFIG_INVALID",
178 AxCode::CasCorrupt => "E_CAS_CORRUPT",
179 AxCode::StorageFatal => "E_STORAGE_FATAL",
180 AxCode::WorktreeBusy => "E_WORKTREE_BUSY",
181 AxCode::BrowserUnavailable => "E_BROWSER_UNAVAILABLE",
182 AxCode::EndpointDialectUnsupported => "E_ENDPOINT_DIALECT_UNSUPPORTED",
183 AxCode::WireMismatch => "E_WIRE_MISMATCH",
184 AxCode::LogVersionUnsupported => "E_LOG_VERSION_UNSUPPORTED",
185 AxCode::LedgerHeld => "E_LEDGER_HELD",
186 AxCode::HistoryUnproven => "E_HISTORY_UNPROVEN",
187 AxCode::SecretEgress => "E_SECRET_EGRESS",
188 AxCode::BackpressureShed => "E_BACKPRESSURE_SHED",
189 AxCode::DiscardIrreversible => "E_DISCARD_IRREVERSIBLE",
190 AxCode::ToolOutcomeUnknown => "E_TOOL_OUTCOME_UNKNOWN",
191 AxCode::PlanMissing => "E_PLAN_MISSING",
192 }
193 }
194
195 pub fn parse(raw: &str) -> Option<AxCode> {
197 AxCode::ALL.into_iter().find(|code| code.as_str() == raw)
198 }
199
200 pub fn carrier(&self) -> Carrier {
207 match self {
208 AxCode::GateDenied
209 | AxCode::OutsideWriteDomain
210 | AxCode::TaintedAction
211 | AxCode::CrossBuildingDenied
212 | AxCode::DiscardIrreversible
213 | AxCode::SecretEgress
214 | AxCode::DelegationDepth => Carrier::Event(EventKind::GateDenied),
215 AxCode::ApprovalPending => Carrier::Event(EventKind::ApprovalRequested),
216 AxCode::ApprovalDenied => Carrier::Event(EventKind::ApprovalResolved),
217 AxCode::BudgetExhausted => Carrier::Event(EventKind::BudgetLimit),
218 AxCode::LoopSuspected => Carrier::Event(EventKind::WatchdogFired),
219 AxCode::Provider => Carrier::Event(EventKind::ProviderDegraded),
220 AxCode::EndpointDialectUnsupported => Carrier::Event(EventKind::EndpointLost),
221 AxCode::ConfigInvalid
222 | AxCode::CasCorrupt
223 | AxCode::StorageFatal
224 | AxCode::WireMismatch
225 | AxCode::LogVersionUnsupported
226 | AxCode::LedgerHeld
227 | AxCode::HistoryUnproven => Carrier::Loadtime,
228 AxCode::PathNotFound
229 | AxCode::ToolUnknown
230 | AxCode::ToolUnavailable
231 | AxCode::InvalidArgs
232 | AxCode::VersionConflict
233 | AxCode::Timeout
234 | AxCode::EvidenceMissing
235 | AxCode::LocatorInvalid
236 | AxCode::SandboxDenied
237 | AxCode::Busy
238 | AxCode::DraftStale
239 | AxCode::GoalConflict
240 | AxCode::RepairBusy
241 | AxCode::DigestSuspect
242 | AxCode::CredentialMissing
243 | AxCode::ModelUnchosen
244 | AxCode::WorktreeBusy
245 | AxCode::BrowserUnavailable
246 | AxCode::ToolOutcomeUnknown
247 | AxCode::BackpressureShed
248 | AxCode::PlanMissing => Carrier::Event(EventKind::ToolResult),
249 }
250 }
251}
252
253impl std::fmt::Display for AxCode {
254 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255 f.write_str(self.as_str())
256 }
257}
258
259impl Serialize for AxCode {
260 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
261 serializer.serialize_str(self.as_str())
262 }
263}
264
265impl<'de> Deserialize<'de> for AxCode {
266 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
267 let raw = String::deserialize(deserializer)?;
268 AxCode::parse(&raw)
269 .ok_or_else(|| serde::de::Error::custom(format_args!("unknown AxCode `{raw}`")))
270 }
271}
272
273#[cfg(test)]
274#[allow(
275 clippy::unwrap_used,
276 clippy::expect_used,
277 clippy::panic,
278 clippy::indexing_slicing,
279 reason = "test code"
280)]
281mod tests {
282 use super::*;
283 use std::collections::BTreeSet;
284
285 #[test]
286 fn axcode_is_the_declared_length_and_spelling_is_bijective() {
287 assert_eq!(AxCode::ALL.len(), 41);
291 let spellings: BTreeSet<&str> = AxCode::ALL.iter().map(AxCode::as_str).collect();
292 assert_eq!(spellings.len(), 41);
293 for s in &spellings {
294 assert!(s.starts_with("E_"));
295 }
296 }
297
298 #[test]
299 fn axcode_parse_roundtrips_every_variant() {
300 for code in AxCode::ALL {
301 assert_eq!(AxCode::parse(code.as_str()), Some(code));
302 }
303 assert_eq!(AxCode::parse("E_NO_SUCH_CODE"), None);
304 }
305
306 #[test]
307 fn axcode_serde_uses_the_wire_spelling() {
308 let json = serde_json::to_string(&AxCode::PathNotFound).unwrap();
309 assert_eq!(json, "\"E_PATH_NOT_FOUND\"");
310 let back: AxCode = serde_json::from_str("\"E_LOCATOR_INVALID\"").unwrap();
311 assert_eq!(back, AxCode::LocatorInvalid);
312 assert!(serde_json::from_str::<AxCode>("\"E_BOGUS\"").is_err());
313 }
314}