Skip to main content

orcs_component/
traits.rs

1//! Core traits for component identification and control.
2//!
3//! These are the **mandatory** traits that all components and children
4//! must implement to participate in the ORCS system.
5//!
6//! # Trait Hierarchy
7//!
8//! ```text
9//! Required for all:
10//!   ├── Identifiable  (id)
11//!   ├── SignalReceiver (on_signal, abort)
12//!   └── Statusable    (status, status_detail)
13//!
14//! Combinations:
15//!   ├── Child = Identifiable + SignalReceiver + Statusable
16//!   └── Component = Child + on_request + init + shutdown
17//! ```
18//!
19//! # Why These Three?
20//!
21//! 1. **Identifiable**: Everything must be identifiable for routing/logging
22//! 2. **SignalReceiver**: Human control is fundamental ("Human as Superpower")
23//! 3. **Statusable**: Manager components need to monitor children
24//!
25//! # Example
26//!
27//! ```
28//! use orcs_component::{Identifiable, SignalReceiver, Statusable, Status};
29//! use orcs_event::{Signal, SignalResponse};
30//!
31//! struct MyWorker {
32//!     id: String,
33//!     status: Status,
34//! }
35//!
36//! impl Identifiable for MyWorker {
37//!     fn id(&self) -> &str {
38//!         &self.id
39//!     }
40//! }
41//!
42//! impl SignalReceiver for MyWorker {
43//!     fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
44//!         if signal.is_veto() {
45//!             self.abort();
46//!             SignalResponse::Abort
47//!         } else {
48//!             SignalResponse::Ignored
49//!         }
50//!     }
51//!
52//!     fn abort(&mut self) {
53//!         self.status = Status::Aborted;
54//!     }
55//! }
56//!
57//! impl Statusable for MyWorker {
58//!     fn status(&self) -> Status {
59//!         self.status
60//!     }
61//! }
62//! ```
63
64use crate::{Status, StatusDetail};
65use orcs_event::{Signal, SignalResponse};
66
67/// Identifiable component or child.
68///
69/// All entities in ORCS must be identifiable for:
70///
71/// - **Routing**: Directing messages to the right target
72/// - **Logging**: Attributing actions to actors
73/// - **Debugging**: Tracing execution flow
74///
75/// # ID Format
76///
77/// IDs should be:
78///
79/// - Unique within their scope (e.g., within a manager)
80/// - Human-readable for debugging
81/// - Stable across restarts (for serialized entities)
82///
83/// # Example
84///
85/// ```
86/// use orcs_component::Identifiable;
87///
88/// struct Task {
89///     id: String,
90///     description: String,
91/// }
92///
93/// impl Identifiable for Task {
94///     fn id(&self) -> &str {
95///         &self.id
96///     }
97/// }
98///
99/// let task = Task {
100///     id: "task-001".into(),
101///     description: "Process files".into(),
102/// };
103/// assert_eq!(task.id(), "task-001");
104/// ```
105pub trait Identifiable {
106    /// Returns the entity's identifier.
107    ///
108    /// This should be stable and unique within scope.
109    fn id(&self) -> &str;
110}
111
112/// Signal receiver for human control.
113///
114/// All entities must respond to signals because
115/// "Human is Superpower" - humans can always interrupt.
116///
117/// # Signal Priority
118///
119/// Signals are the **highest priority** messages in ORCS.
120/// They must be processed immediately, even during active work.
121///
122/// # Required Responses
123///
124/// | Signal | Expected Response |
125/// |--------|-------------------|
126/// | Veto | `Abort` + call `abort()` |
127/// | Cancel (in scope) | `Abort` or `Handled` |
128/// | Cancel (out of scope) | `Ignored` |
129/// | Pause | `Handled` + change state |
130/// | Resume | `Handled` + resume work |
131///
132/// # Example
133///
134/// ```
135/// use orcs_component::{SignalReceiver, Status};
136/// use orcs_event::{Signal, SignalResponse};
137///
138/// struct Worker {
139///     status: Status,
140/// }
141///
142/// impl SignalReceiver for Worker {
143///     fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
144///         if signal.is_veto() {
145///             self.abort();
146///             SignalResponse::Abort
147///         } else if signal.is_global() {
148///             // Global signals we can't handle
149///             SignalResponse::Ignored
150///         } else {
151///             SignalResponse::Handled
152///         }
153///     }
154///
155///     fn abort(&mut self) {
156///         self.status = Status::Aborted;
157///         // Clean up resources, cancel pending work, etc.
158///     }
159/// }
160/// ```
161pub trait SignalReceiver {
162    /// Handle an incoming signal.
163    ///
164    /// Called for every signal that reaches this entity.
165    /// Must check signal scope to determine relevance.
166    ///
167    /// # Returns
168    ///
169    /// - `Handled`: Signal was processed
170    /// - `Ignored`: Signal not relevant (out of scope)
171    /// - `Abort`: Entity is stopping due to signal
172    fn on_signal(&mut self, signal: &Signal) -> SignalResponse;
173
174    /// Immediate abort.
175    ///
176    /// Called when a Veto signal is received.
177    /// Must stop all work immediately and clean up.
178    ///
179    /// # Contract
180    ///
181    /// After `abort()` is called:
182    /// - No more work should be performed
183    /// - Resources should be released
184    /// - Status should become `Aborted`
185    fn abort(&mut self);
186}
187
188/// Status reporting for monitoring.
189///
190/// Managers need to know the status of their children.
191/// This enables:
192///
193/// - **UI display**: Show progress to user
194/// - **Health monitoring**: Detect stuck/failed children
195/// - **Orchestration**: Coordinate parallel work
196///
197/// # Status Lifecycle
198///
199/// ```text
200/// Initializing → Idle ⇄ Running → Completed
201///                  ↓         ↓
202///                Paused    Error
203///                  ↓         ↓
204///               Aborted ← ───┘
205/// ```
206///
207/// # Example
208///
209/// ```
210/// use orcs_component::{Statusable, Status, StatusDetail, Progress};
211///
212/// struct Compiler {
213///     status: Status,
214///     files_compiled: u64,
215///     total_files: u64,
216/// }
217///
218/// impl Statusable for Compiler {
219///     fn status(&self) -> Status {
220///         self.status
221///     }
222///
223///     fn status_detail(&self) -> Option<StatusDetail> {
224///         Some(StatusDetail {
225///             message: Some("Compiling...".into()),
226///             progress: Some(Progress::new(self.files_compiled, Some(self.total_files))),
227///             metadata: Default::default(),
228///         })
229///     }
230/// }
231/// ```
232pub trait Statusable {
233    /// Returns the current status.
234    ///
235    /// This should reflect the actual state of the entity.
236    fn status(&self) -> Status;
237
238    /// Returns detailed status information.
239    ///
240    /// Optional - returns `None` by default.
241    /// Implement for UI display or debugging.
242    fn status_detail(&self) -> Option<StatusDetail> {
243        None
244    }
245}
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250    use orcs_types::{Principal, PrincipalId};
251
252    struct TestEntity {
253        id: String,
254        status: Status,
255    }
256
257    impl TestEntity {
258        fn new(id: &str) -> Self {
259            Self {
260                id: id.into(),
261                status: Status::Idle,
262            }
263        }
264    }
265
266    impl Identifiable for TestEntity {
267        fn id(&self) -> &str {
268            &self.id
269        }
270    }
271
272    impl SignalReceiver for TestEntity {
273        fn on_signal(&mut self, signal: &Signal) -> SignalResponse {
274            if signal.is_veto() {
275                self.abort();
276                SignalResponse::Abort
277            } else {
278                SignalResponse::Ignored
279            }
280        }
281
282        fn abort(&mut self) {
283            self.status = Status::Aborted;
284        }
285    }
286
287    impl Statusable for TestEntity {
288        fn status(&self) -> Status {
289            self.status
290        }
291    }
292
293    #[test]
294    fn identifiable() {
295        let entity = TestEntity::new("test-entity");
296        assert_eq!(entity.id(), "test-entity");
297    }
298
299    #[test]
300    fn signal_receiver_veto() {
301        let mut entity = TestEntity::new("test");
302        let signal = Signal::veto(Principal::User(PrincipalId::new()));
303
304        let response = entity.on_signal(&signal);
305        assert_eq!(response, SignalResponse::Abort);
306        assert_eq!(entity.status(), Status::Aborted);
307    }
308
309    #[test]
310    fn statusable_default_detail() {
311        let entity = TestEntity::new("test");
312        assert!(entity.status_detail().is_none());
313    }
314}