Skip to main content

arkhe_kernel/abi/
error.rs

1//! Kernel-level error taxonomy.
2//!
3//! The `Domain` variant is opaque to the kernel — code and payload are
4//! L2/L1 concerns. This prevents domain-level error paths from being
5//! forced into string smuggling or `EmitEvent` side-channels.
6//!
7//! `#[non_exhaustive]` defends the ABI: external matchers are forbidden
8//! from exhaustive-matching, so future variants are not breaking for
9//! external consumers.
10
11use bytes::Bytes;
12
13/// Top-level kernel error.
14#[non_exhaustive]
15#[derive(Clone, Debug)]
16pub enum ArkheError {
17    /// Requested `InstanceId` is not live.
18    InstanceNotFound,
19
20    /// Caller lacks one or more required capability bits.
21    CapabilityDenied,
22
23    /// A per-instance hard quota (e.g. `max_scheduled`) would be exceeded.
24    /// Returned by `Kernel::submit` as back-pressure rather than admitting
25    /// an unbounded enqueue.
26    QuotaExceeded,
27
28    /// Domain-level error. The kernel does not interpret `code` or
29    /// `payload`; they are an L1/L2 protocol. Payload is canonical bytes
30    /// (CanonicalEncode discipline) so cross-replay determinism holds.
31    Domain {
32        /// L1/L2-defined error code; kernel-opaque.
33        code: u32,
34        /// Canonical bytes carrying L1/L2-defined error payload.
35        payload: Bytes,
36    },
37}
38
39impl core::fmt::Display for ArkheError {
40    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
41        match self {
42            Self::InstanceNotFound => write!(f, "instance not found"),
43            Self::CapabilityDenied => write!(f, "capability denied"),
44            Self::QuotaExceeded => write!(f, "quota exceeded"),
45            Self::Domain { code, payload } => {
46                write!(
47                    f,
48                    "domain error: code={} payload_len={}",
49                    code,
50                    payload.len()
51                )
52            }
53        }
54    }
55}
56
57impl std::error::Error for ArkheError {}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn domain_error_carries_code_and_payload() {
65        let err = ArkheError::Domain {
66            code: 42,
67            payload: Bytes::from_static(b"opaque"),
68        };
69        match err {
70            ArkheError::Domain { code, payload } => {
71                assert_eq!(code, 42);
72                assert_eq!(&payload[..], b"opaque");
73            }
74            _ => panic!("expected Domain variant"),
75        }
76    }
77
78    #[test]
79    fn domain_error_payload_can_be_empty() {
80        let err = ArkheError::Domain {
81            code: 0,
82            payload: Bytes::new(),
83        };
84        match err {
85            ArkheError::Domain { payload, .. } => assert!(payload.is_empty()),
86            _ => panic!("expected Domain variant"),
87        }
88    }
89
90    #[test]
91    fn error_display_instance_not_found() {
92        let err = ArkheError::InstanceNotFound;
93        assert_eq!(format!("{}", err), "instance not found");
94    }
95
96    #[test]
97    fn error_display_capability_denied() {
98        let err = ArkheError::CapabilityDenied;
99        assert_eq!(format!("{}", err), "capability denied");
100    }
101
102    #[test]
103    fn error_display_domain_includes_code() {
104        let err = ArkheError::Domain {
105            code: 7,
106            payload: Bytes::from_static(b"xyz"),
107        };
108        let s = format!("{}", err);
109        assert!(s.contains("code=7"));
110        assert!(s.contains("payload_len=3"));
111    }
112
113    #[test]
114    fn error_implements_std_error() {
115        fn assert_std_error<E: std::error::Error>() {}
116        assert_std_error::<ArkheError>();
117    }
118}