Skip to main content

orcs_types/
id.rs

1//! Identifier types for ORCS.
2//!
3//! All identifiers are UUID-based for network compatibility and
4//! cloud readiness.
5
6use serde::{Deserialize, Serialize};
7use uuid::{uuid, Uuid};
8
9/// ORCS namespace UUID for deterministic UUID v5 generation.
10///
11/// This UUID is used as the namespace for generating deterministic
12/// UUIDs for builtin components via UUID v5 (SHA-1 based).
13const ORCS_NAMESPACE: Uuid = uuid!("d81843c2-9146-43ca-b016-bee57a14762f");
14
15/// Identifier for a Component in the ORCS architecture.
16///
17/// A Component is a functional domain boundary that communicates
18/// via the EventBus. Examples include:
19///
20/// - `builtin::llm` - LLM integration (chat, complete, embed)
21/// - `builtin::tool` - Tool execution (read, write, bash)
22/// - `builtin::hil` - Human-in-the-loop (approve, reject)
23/// - `plugin::my-tool` - User-defined extensions
24///
25/// # UUID Strategy
26///
27/// - **Builtin components**: Use UUID v5 (deterministic from name)
28/// - **Custom components**: Use UUID v4 (random)
29///
30/// This ensures builtin components have consistent UUIDs across
31/// processes and machines, enabling reliable routing and comparison.
32///
33/// # Equality Semantics
34///
35/// `PartialEq` compares all fields including UUID. For FQN-only
36/// comparison (ignoring UUID), use [`fqn_eq`](Self::fqn_eq).
37///
38/// # Example
39///
40/// ```
41/// use orcs_types::ComponentId;
42///
43/// // Builtin: deterministic UUID
44/// let llm1 = ComponentId::builtin("llm");
45/// let llm2 = ComponentId::builtin("llm");
46/// assert_eq!(llm1, llm2);  // Same UUID, same component
47///
48/// // Custom: random UUID per instance
49/// let p1 = ComponentId::new("plugin", "tool");
50/// let p2 = ComponentId::new("plugin", "tool");
51/// assert_ne!(p1, p2);      // Different UUIDs
52/// assert!(p1.fqn_eq(&p2)); // But same FQN
53/// ```
54#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
55pub struct ComponentId {
56    /// Globally unique identifier.
57    pub uuid: Uuid,
58    /// Namespace (e.g., "builtin", "plugin", "wasm").
59    pub namespace: String,
60    /// Component name within namespace.
61    pub name: String,
62}
63
64impl ComponentId {
65    /// Creates a new [`ComponentId`] with a random UUID v4.
66    ///
67    /// Use this for custom/plugin components where each instance
68    /// should have a unique identity.
69    ///
70    /// # Example
71    ///
72    /// ```
73    /// use orcs_types::ComponentId;
74    ///
75    /// let plugin = ComponentId::new("plugin", "my-tool");
76    /// assert_eq!(plugin.namespace, "plugin");
77    /// assert_eq!(plugin.name, "my-tool");
78    /// assert_eq!(plugin.fqn(), "plugin::my-tool");
79    /// ```
80    #[must_use]
81    pub fn new(namespace: impl Into<String>, name: impl Into<String>) -> Self {
82        Self {
83            uuid: Uuid::new_v4(),
84            namespace: namespace.into(),
85            name: name.into(),
86        }
87    }
88
89    /// Creates a builtin component ID with a deterministic UUID v5.
90    ///
91    /// The UUID is derived from the ORCS namespace UUID and the
92    /// component name using SHA-1. This ensures:
93    ///
94    /// - Same name always produces same UUID
95    /// - Different names produce different UUIDs
96    /// - UUIDs are consistent across processes/machines
97    ///
98    /// # Example
99    ///
100    /// ```
101    /// use orcs_types::ComponentId;
102    ///
103    /// let llm1 = ComponentId::builtin("llm");
104    /// let llm2 = ComponentId::builtin("llm");
105    /// let tool = ComponentId::builtin("tool");
106    ///
107    /// assert_eq!(llm1.uuid, llm2.uuid);  // Same name = same UUID
108    /// assert_ne!(llm1.uuid, tool.uuid);  // Different name = different UUID
109    /// assert!(llm1.is_builtin());
110    /// ```
111    #[must_use]
112    pub fn builtin(name: impl Into<String>) -> Self {
113        let name = name.into();
114        Self {
115            uuid: Uuid::new_v5(&ORCS_NAMESPACE, name.as_bytes()),
116            namespace: "builtin".to_string(),
117            name,
118        }
119    }
120
121    /// Returns the fully qualified name in `namespace::name` format.
122    ///
123    /// # Example
124    ///
125    /// ```
126    /// use orcs_types::ComponentId;
127    ///
128    /// let id = ComponentId::builtin("llm");
129    /// assert_eq!(id.fqn(), "builtin::llm");
130    /// ```
131    #[must_use]
132    pub fn fqn(&self) -> String {
133        format!("{}::{}", self.namespace, self.name)
134    }
135
136    /// Compares two [`ComponentId`]s by FQN only, ignoring UUID.
137    ///
138    /// This is useful when you want to check if two components
139    /// represent the same logical component, even if they were
140    /// created as separate instances.
141    ///
142    /// # Example
143    ///
144    /// ```
145    /// use orcs_types::ComponentId;
146    ///
147    /// let p1 = ComponentId::new("plugin", "tool");
148    /// let p2 = ComponentId::new("plugin", "tool");
149    ///
150    /// assert_ne!(p1, p2);       // Different UUIDs
151    /// assert!(p1.fqn_eq(&p2));  // Same FQN
152    /// ```
153    #[must_use]
154    pub fn fqn_eq(&self, other: &Self) -> bool {
155        self.namespace == other.namespace && self.name == other.name
156    }
157
158    /// Checks if this component matches the given namespace and name.
159    ///
160    /// # Example
161    ///
162    /// ```
163    /// use orcs_types::ComponentId;
164    ///
165    /// let llm = ComponentId::builtin("llm");
166    ///
167    /// assert!(llm.matches("builtin", "llm"));
168    /// assert!(!llm.matches("builtin", "tool"));
169    /// ```
170    #[must_use]
171    pub fn matches(&self, namespace: &str, name: &str) -> bool {
172        self.namespace == namespace && self.name == name
173    }
174
175    /// Returns `true` if this is a builtin component.
176    ///
177    /// # Example
178    ///
179    /// ```
180    /// use orcs_types::ComponentId;
181    ///
182    /// let builtin = ComponentId::builtin("llm");
183    /// let plugin = ComponentId::new("plugin", "tool");
184    ///
185    /// assert!(builtin.is_builtin());
186    /// assert!(!plugin.is_builtin());
187    /// ```
188    #[must_use]
189    pub fn is_builtin(&self) -> bool {
190        self.namespace == "builtin"
191    }
192
193    /// Creates a child component ID with a deterministic UUID v5.
194    ///
195    /// The UUID is derived from the ORCS namespace UUID and the
196    /// child name using SHA-1.
197    ///
198    /// # Example
199    ///
200    /// ```
201    /// use orcs_types::ComponentId;
202    ///
203    /// let child = ComponentId::child("worker-1");
204    /// assert_eq!(child.namespace, "child");
205    /// assert_eq!(child.name, "worker-1");
206    /// ```
207    #[must_use]
208    pub fn child(name: impl Into<String>) -> Self {
209        let name = name.into();
210        Self {
211            uuid: Uuid::new_v5(&ORCS_NAMESPACE, format!("child:{}", name).as_bytes()),
212            namespace: "child".to_string(),
213            name,
214        }
215    }
216
217    /// Returns `true` if this is a child component.
218    #[must_use]
219    pub fn is_child(&self) -> bool {
220        self.namespace == "child"
221    }
222}
223
224impl std::fmt::Display for ComponentId {
225    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226        write!(f, "{}::{}@{}", self.namespace, self.name, self.uuid)
227    }
228}
229
230/// Identifier for a Principal (actor) in the ORCS system.
231///
232/// A Principal represents "who" is performing an action, separate from
233/// "what permission they have". This separation enables:
234///
235/// - **Dynamic privilege**: Same principal can be Standard or Elevated
236/// - **Audit trails**: Actions are attributed to identifiable actors
237/// - **Network identity**: Principals are unique across distributed systems
238///
239/// # Principal vs Component
240///
241/// - [`PrincipalId`]: Who is acting (human user, system process)
242/// - [`ComponentId`]: What is acting (LLM, Tool, Plugin)
243///
244/// A Component may act on behalf of a Principal, but they are distinct
245/// concepts. A human user (Principal) may trigger an LLM Component,
246/// but the LLM Component itself is not the Principal.
247///
248/// # Example
249///
250/// ```
251/// use orcs_types::PrincipalId;
252///
253/// let user = PrincipalId::new();
254/// let admin = PrincipalId::new();
255///
256/// assert_ne!(user, admin);  // Different principals
257/// println!("User: {}", user);
258/// ```
259#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
260pub struct PrincipalId(pub Uuid);
261
262impl PrincipalId {
263    /// Creates a new [`PrincipalId`] with a random UUID v4.
264    ///
265    /// # Example
266    ///
267    /// ```
268    /// use orcs_types::PrincipalId;
269    ///
270    /// let principal = PrincipalId::new();
271    /// println!("Principal: {}", principal);
272    /// ```
273    #[must_use]
274    pub fn new() -> Self {
275        Self(Uuid::new_v4())
276    }
277
278    /// Returns the inner UUID.
279    #[must_use]
280    pub fn uuid(&self) -> Uuid {
281        self.0
282    }
283}
284
285impl Default for PrincipalId {
286    fn default() -> Self {
287        Self::new()
288    }
289}
290
291impl std::fmt::Display for PrincipalId {
292    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293        write!(f, "principal:{}", self.0)
294    }
295}
296
297/// Identifier for a Channel in the ORCS architecture.
298///
299/// A Channel represents a parallel execution context managed by the
300/// World. Channels form a tree structure where:
301///
302/// - **Primary Channel**: Root channel owned by Human
303/// - **Agent Channels**: Spawned for LLM/Tool execution
304/// - **Background Channels**: Isolated tasks (WASM plugins, etc.)
305///
306/// # Channel Tree
307///
308/// ```text
309/// Primary (Human)
310///   ├── Agent Channel
311///   │     ├── LLM Component
312///   │     └── Tool Component
313///   │           └── Subprocess
314///   └── Background Channel
315///         └── WASM Plugin
316/// ```
317///
318/// # Permission Inheritance
319///
320/// Child channels inherit permissions from their parent.
321/// Children cannot have broader scope than their parent.
322///
323/// # Example
324///
325/// ```
326/// use orcs_types::ChannelId;
327///
328/// let primary = ChannelId::new();
329/// let agent = ChannelId::new();
330///
331/// assert_ne!(primary, agent);  // Each channel is unique
332/// println!("Agent channel: {}", agent);
333/// ```
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
335pub struct ChannelId(pub Uuid);
336
337#[allow(clippy::new_without_default)] // Default intentionally not implemented - see below
338impl ChannelId {
339    /// Creates a new [`ChannelId`] with a random UUID v4.
340    ///
341    /// # Example
342    ///
343    /// ```
344    /// use orcs_types::ChannelId;
345    ///
346    /// let ch = ChannelId::new();
347    /// println!("Created channel: {}", ch);
348    /// ```
349    #[must_use]
350    pub fn new() -> Self {
351        Self(Uuid::new_v4())
352    }
353
354    /// Returns the inner UUID.
355    ///
356    /// # Example
357    ///
358    /// ```
359    /// use orcs_types::ChannelId;
360    ///
361    /// let ch = ChannelId::new();
362    /// let uuid = ch.uuid();
363    /// assert_eq!(ch.0, uuid);
364    /// ```
365    #[must_use]
366    pub fn uuid(&self) -> Uuid {
367        self.0
368    }
369}
370
371// NOTE: ChannelId intentionally does NOT implement Default.
372// Default::default() would generate a new UUID that is not registered in World,
373// leading to subtle bugs. Use World::create_primary() or World::spawn() instead.
374
375impl std::fmt::Display for ChannelId {
376    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377        write!(f, "ch:{}", self.0)
378    }
379}
380
381/// Identifier for a Request in the ORCS EventBus.
382///
383/// A Request represents a synchronous query from one Component to
384/// another (or broadcast). Each Request expects exactly one Response.
385///
386/// # Request/Response Pattern
387///
388/// ```text
389/// ┌─────────────┐  Request   ┌─────────────┐
390/// │   Source    │ ─────────► │   Target    │
391/// │  Component  │            │  Component  │
392/// │             │ ◄───────── │             │
393/// └─────────────┘  Response  └─────────────┘
394/// ```
395///
396/// # Example
397///
398/// ```
399/// use orcs_types::RequestId;
400///
401/// let req_id = RequestId::new();
402/// println!("Request: {}", req_id);
403/// ```
404#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
405pub struct RequestId(pub Uuid);
406
407#[allow(clippy::new_without_default)] // Default intentionally not implemented - RequestId is generated internally by Request::new()
408impl RequestId {
409    /// Creates a new [`RequestId`] with a random UUID v4.
410    ///
411    /// # Example
412    ///
413    /// ```
414    /// use orcs_types::RequestId;
415    ///
416    /// let id = RequestId::new();
417    /// println!("Request ID: {}", id);
418    /// ```
419    #[must_use]
420    pub fn new() -> Self {
421        Self(Uuid::new_v4())
422    }
423
424    /// Returns the inner UUID.
425    #[must_use]
426    pub fn uuid(&self) -> Uuid {
427        self.0
428    }
429}
430
431// NOTE: RequestId intentionally does NOT implement Default.
432// RequestId is generated internally by Request::new()/try_new().
433// Direct construction should use RequestId::new() explicitly.
434
435impl std::fmt::Display for RequestId {
436    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
437        write!(f, "req:{}", self.0)
438    }
439}
440
441/// Identifier for an Event in the ORCS EventBus.
442///
443/// An Event represents an asynchronous broadcast notification.
444/// Unlike Requests, Events do not expect a Response.
445///
446/// # Event Pattern
447///
448/// ```text
449/// ┌─────────────┐   Event    ┌─────────────┐
450/// │   Source    │ ─────────► │ Subscriber  │
451/// │  Component  │            │  Component  │
452/// └─────────────┘            └─────────────┘
453///                   Event    ┌─────────────┐
454///                 ─────────► │ Subscriber  │
455///                            │  Component  │
456///                            └─────────────┘
457/// ```
458///
459/// # Example
460///
461/// ```
462/// use orcs_types::EventId;
463///
464/// let evt_id = EventId::new();
465/// println!("Event: {}", evt_id);
466/// ```
467#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
468pub struct EventId(pub Uuid);
469
470impl EventId {
471    /// Creates a new [`EventId`] with a random UUID v4.
472    ///
473    /// # Example
474    ///
475    /// ```
476    /// use orcs_types::EventId;
477    ///
478    /// let id = EventId::new();
479    /// println!("Event ID: {}", id);
480    /// ```
481    #[must_use]
482    pub fn new() -> Self {
483        Self(Uuid::new_v4())
484    }
485
486    /// Returns the inner UUID.
487    #[must_use]
488    pub fn uuid(&self) -> Uuid {
489        self.0
490    }
491}
492
493impl Default for EventId {
494    fn default() -> Self {
495        Self::new()
496    }
497}
498
499impl std::fmt::Display for EventId {
500    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
501        write!(f, "evt:{}", self.0)
502    }
503}
504
505// Tests are in lib.rs as integration tests for public API