Skip to main content

arkhe_kernel/abi/
principal.rs

1//! Authority principal — non-optional, exhaustive enum.
2//!
3//! A three-variant enum forces every reader site to handle the
4//! unauthenticated state explicitly. Under
5//! `clippy::wildcard_enum_match_arm = deny`, a catch-all `_ =>`
6//! match arm is a compile error; adding a new variant is therefore a
7//! breaking change surfaced at every reader.
8
9use serde::{Deserialize, Serialize};
10
11/// Opaque L2-supplied external identity. Kernel does not interpret its
12/// contents (`Mechanism != Policy`). The concrete byte shape is an L2
13/// concern — this newtype carries a `u64` for v0.13 and reserves the
14/// option to widen to a fixed-size cryptographic identifier in a future
15/// approximation (deferred `[u8; 32]` option).
16#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
17#[serde(transparent)]
18pub struct ExternalId(pub u64);
19
20/// Authority principal. Three states, exhaustively type-checked.
21///
22/// - `Unauthenticated`: caller identity has not been verified by L2
23///   (pre-auth lobby, anonymous submit path). Authorization paths must
24///   treat this distinctly from `External(_)`.
25/// - `External`: L2-verified external identity.
26/// - `System`: kernel-internal origin (init, signal-relay, cleanup cascade,
27///   replay re-injection). Ships as a payloadless variant; a `SystemOrigin`
28///   payload widening is reserved (deferred).
29///
30/// `#[non_exhaustive]` protects the ABI: external consumers cannot match
31/// exhaustively, so adding variants is not a breaking change for them.
32/// Within-crate consumers see every variant and are forced by the linter
33/// to match exhaustively.
34#[non_exhaustive]
35#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
36pub enum Principal {
37    /// Caller identity has not been verified by L2 (pre-auth lobby,
38    /// anonymous submit path).
39    Unauthenticated,
40    /// L2-verified external identity.
41    External(ExternalId),
42    /// Kernel-internal origin (init, signal-relay, cleanup cascade,
43    /// replay re-injection).
44    System,
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn principal_exhaustive_classify() {
53        // Classifier uses exhaustive match — this test fails to compile if
54        // a new variant is added without updating here, giving a visible
55        // reminder that every reader site needs attention.
56        fn classify(p: &Principal) -> &'static str {
57            match p {
58                Principal::Unauthenticated => "unauth",
59                Principal::External(_) => "external",
60                Principal::System => "system",
61            }
62        }
63        assert_eq!(classify(&Principal::Unauthenticated), "unauth");
64        assert_eq!(classify(&Principal::External(ExternalId(7))), "external");
65        assert_eq!(classify(&Principal::System), "system");
66    }
67
68    #[test]
69    fn external_id_is_transparent_u64() {
70        let id = ExternalId(0xDEAD_BEEF);
71        assert_eq!(id.0, 0xDEAD_BEEF);
72    }
73
74    #[test]
75    fn external_id_is_total_ordered() {
76        assert!(ExternalId(1) < ExternalId(2));
77    }
78
79    #[test]
80    fn principal_equality() {
81        assert_eq!(Principal::Unauthenticated, Principal::Unauthenticated);
82        assert_eq!(
83            Principal::External(ExternalId(3)),
84            Principal::External(ExternalId(3))
85        );
86        assert_ne!(
87            Principal::External(ExternalId(3)),
88            Principal::External(ExternalId(4))
89        );
90        assert_ne!(Principal::System, Principal::Unauthenticated);
91    }
92}