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}