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}