Skip to main content

klieo_ops/
error.rs

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