Skip to main content

orcs_types/
principal.rs

1//! Principal (actor identity) types.
2//!
3//! A [`Principal`] represents the actor performing an action,
4//! separating "who is acting" from "what they are allowed to do".
5//!
6//! # Design Rationale
7//!
8//! Principal is placed in `orcs-types` (not `orcs-auth`) because:
9//!
10//! 1. **Plugin SDK boundary**: Plugins receive Signals containing Principal
11//! 2. **No auth logic dependency**: Principal is pure identity, no permission logic
12//! 3. **Avoid circular dependency**: Event -> Auth -> Types would create issues
13//!
14//! Permission checking (Session, PrivilegeLevel) remains in the runtime layer.
15
16use crate::{ComponentId, PrincipalId};
17use serde::{Deserialize, Serialize};
18
19/// The actor performing an action.
20///
21/// A Principal represents identity only, not permission level.
22/// Permission is determined by the runtime layer's Session.
23///
24/// # Variants
25///
26/// | Variant | Description | Typical Use |
27/// |---------|-------------|-------------|
28/// | `User` | Human user | Interactive CLI usage |
29/// | `Component` | Autonomous component | Background processing |
30/// | `System` | Internal operations | Lifecycle, cleanup |
31///
32/// # Why Not Just Use ComponentId?
33///
34/// [`ComponentId`] identifies *what* is executing (LLM, Tool, etc.).
35/// [`Principal`] identifies *who* initiated the action.
36///
37/// A Tool component may execute a file write, but the Principal
38/// is the human user who requested it. This distinction enables:
39///
40/// - Audit trails attributing actions to users
41/// - Permission checks based on who initiated
42/// - Rate limiting per user, not per component
43///
44/// # Example
45///
46/// ```
47/// use orcs_types::{Principal, ComponentId, PrincipalId};
48///
49/// // Human user
50/// let user = Principal::User(PrincipalId::new());
51///
52/// // Component acting autonomously (e.g., scheduled task)
53/// let component = Principal::Component(ComponentId::builtin("scheduler"));
54///
55/// // System internal operation
56/// let system = Principal::System;
57/// ```
58#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59pub enum Principal {
60    /// Human user identified by [`PrincipalId`].
61    ///
62    /// Represents interactive CLI usage or API calls from
63    /// authenticated users.
64    User(PrincipalId),
65
66    /// Component acting autonomously.
67    ///
68    /// Used when a component initiates actions without direct
69    /// human involvement, such as:
70    ///
71    /// - Scheduled background tasks
72    /// - Event-driven reactions
73    /// - Plugin self-maintenance
74    Component(ComponentId),
75
76    /// System internal operations.
77    ///
78    /// Used for operations that are not attributable to any
79    /// specific user or component:
80    ///
81    /// - Lifecycle management (startup, shutdown)
82    /// - Garbage collection
83    /// - Internal housekeeping
84    System,
85}
86
87impl Principal {
88    /// Returns `true` if this is a [`Principal::User`].
89    #[must_use]
90    pub fn is_user(&self) -> bool {
91        matches!(self, Self::User(_))
92    }
93
94    /// Returns `true` if this is a [`Principal::Component`].
95    #[must_use]
96    pub fn is_component(&self) -> bool {
97        matches!(self, Self::Component(_))
98    }
99
100    /// Returns `true` if this is [`Principal::System`].
101    #[must_use]
102    pub fn is_system(&self) -> bool {
103        matches!(self, Self::System)
104    }
105
106    /// Returns the [`PrincipalId`] if this is a User, otherwise `None`.
107    #[must_use]
108    pub fn user_id(&self) -> Option<&PrincipalId> {
109        match self {
110            Self::User(id) => Some(id),
111            _ => None,
112        }
113    }
114
115    /// Returns the [`ComponentId`] if this is a Component, otherwise `None`.
116    #[must_use]
117    pub fn component_id(&self) -> Option<&ComponentId> {
118        match self {
119            Self::Component(id) => Some(id),
120            _ => None,
121        }
122    }
123}
124
125impl std::fmt::Display for Principal {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        match self {
128            Self::User(id) => write!(f, "user:{}", id.uuid()),
129            Self::Component(id) => write!(f, "component:{}", id.fqn()),
130            Self::System => write!(f, "system"),
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn principal_user() {
141        let id = PrincipalId::new();
142        let principal = Principal::User(id);
143
144        assert!(principal.is_user());
145        assert!(!principal.is_component());
146        assert!(!principal.is_system());
147        assert!(principal.user_id().is_some());
148        assert!(principal.component_id().is_none());
149    }
150
151    #[test]
152    fn principal_component() {
153        let id = ComponentId::builtin("llm");
154        let principal = Principal::Component(id);
155
156        assert!(!principal.is_user());
157        assert!(principal.is_component());
158        assert!(!principal.is_system());
159        assert!(principal.user_id().is_none());
160        assert!(principal.component_id().is_some());
161    }
162
163    #[test]
164    fn principal_system() {
165        let principal = Principal::System;
166
167        assert!(!principal.is_user());
168        assert!(!principal.is_component());
169        assert!(principal.is_system());
170        assert!(principal.user_id().is_none());
171        assert!(principal.component_id().is_none());
172    }
173
174    #[test]
175    fn principal_display() {
176        let user = Principal::User(PrincipalId::new());
177        assert!(format!("{user}").starts_with("user:"));
178
179        let component = Principal::Component(ComponentId::builtin("llm"));
180        assert_eq!(format!("{component}"), "component:builtin::llm");
181
182        let system = Principal::System;
183        assert_eq!(format!("{system}"), "system");
184    }
185
186    #[test]
187    fn principal_equality() {
188        let id1 = PrincipalId::new();
189        let id2 = PrincipalId::new();
190
191        let p1 = Principal::User(id1);
192        let p2 = Principal::User(id1);
193        let p3 = Principal::User(id2);
194
195        assert_eq!(p1, p2);
196        assert_ne!(p1, p3);
197        assert_ne!(Principal::System, p1);
198    }
199}