1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//! Shared error enums for the ops layer.
use thiserror::Error;
/// Build-time configuration error. Raised only by `OpsRuntime::build`.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum BuildError {
/// A registered primitive needs another primitive that wasn't provided.
#[error("primitive `{needed_by}` requires `{missing}` to be configured")]
MissingDependency {
/// The primitive that has the unmet dependency.
needed_by: String,
/// The dependency that was not supplied.
missing: String,
},
}
/// Runtime error surfaced by ops primitives + the SupervisedAgent wrapper.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum OpsError {
/// A gate refused; the operation is denied.
#[error("denied: {code}")]
Denied {
/// Stable policy code.
code: String,
/// Optional human-readable reason.
reason: Option<String>,
},
/// A gate suspended; downstream must wait for resolution (Phase B).
#[error("require approval")]
RequireApproval,
/// Operation timed out at the named phase.
#[error("timed out at phase `{phase}`")]
TimedOut {
/// Phase name.
phase: String,
},
/// Governor refused — budget exhausted.
#[error("saturated: {scope}")]
Saturated {
/// Scope display.
scope: String,
},
/// Kill-switch tripped; runtime is halted.
#[error("halted")]
Halted,
/// Downstream storage / channel unreachable.
#[error("unavailable: {component}")]
Unavailable {
/// Affected component name.
component: String,
},
/// Integrity / signature / canonicalisation failure.
#[error("integrity failed: {reasons}")]
IntegrityFailed {
/// Concatenated failure reasons.
reasons: String,
},
/// Build-time configuration error promoted to runtime path.
#[error(transparent)]
Build(#[from] BuildError),
/// Any other internal error.
#[error("internal: {source}")]
Internal {
/// Wrapped source.
source: Box<dyn std::error::Error + Send + Sync>,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_error_displays_missing_dependency() {
let e = BuildError::MissingDependency {
needed_by: "gates".into(),
missing: "escalation".into(),
};
let s = format!("{e}");
assert!(s.contains("gates"));
assert!(s.contains("escalation"));
}
#[test]
fn ops_error_halted_is_distinct_from_unavailable() {
let a = OpsError::Halted;
let b = OpsError::Unavailable {
component: "kv".into(),
};
assert_ne!(format!("{a}"), format!("{b}"));
}
}