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    /// Domain-level error. The kernel does not interpret `code` or
24    /// `payload`; they are an L1/L2 protocol. Payload is canonical bytes
25    /// (CanonicalEncode discipline) so cross-replay determinism holds.
26    Domain {
27        /// L1/L2-defined error code; kernel-opaque.
28        code: u32,
29        /// Canonical bytes carrying L1/L2-defined error payload.
30        payload: Bytes,
31    },
32}
33
34impl core::fmt::Display for ArkheError {
35    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
36        match self {
37            Self::InstanceNotFound => write!(f, "instance not found"),
38            Self::CapabilityDenied => write!(f, "capability denied"),
39            Self::Domain { code, payload } => {
40                write!(
41                    f,
42                    "domain error: code={} payload_len={}",
43                    code,
44                    payload.len()
45                )
46            }
47        }
48    }
49}
50
51impl std::error::Error for ArkheError {}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn domain_error_carries_code_and_payload() {
59        let err = ArkheError::Domain {
60            code: 42,
61            payload: Bytes::from_static(b"opaque"),
62        };
63        match err {
64            ArkheError::Domain { code, payload } => {
65                assert_eq!(code, 42);
66                assert_eq!(&payload[..], b"opaque");
67            }
68            _ => panic!("expected Domain variant"),
69        }
70    }
71
72    #[test]
73    fn domain_error_payload_can_be_empty() {
74        let err = ArkheError::Domain {
75            code: 0,
76            payload: Bytes::new(),
77        };
78        match err {
79            ArkheError::Domain { payload, .. } => assert!(payload.is_empty()),
80            _ => panic!("expected Domain variant"),
81        }
82    }
83
84    #[test]
85    fn error_display_instance_not_found() {
86        let err = ArkheError::InstanceNotFound;
87        assert_eq!(format!("{}", err), "instance not found");
88    }
89
90    #[test]
91    fn error_display_capability_denied() {
92        let err = ArkheError::CapabilityDenied;
93        assert_eq!(format!("{}", err), "capability denied");
94    }
95
96    #[test]
97    fn error_display_domain_includes_code() {
98        let err = ArkheError::Domain {
99            code: 7,
100            payload: Bytes::from_static(b"xyz"),
101        };
102        let s = format!("{}", err);
103        assert!(s.contains("code=7"));
104        assert!(s.contains("payload_len=3"));
105    }
106
107    #[test]
108    fn error_implements_std_error() {
109        fn assert_std_error<E: std::error::Error>() {}
110        assert_std_error::<ArkheError>();
111    }
112}