Skip to main content

orcs_runtime/channel/
config.rs

1//! Channel configuration for behavior customization.
2//!
3//! [`ChannelConfig`] defines the behavioral attributes of a Channel,
4//! such as scheduling priority, spawn permissions, and privilege limits.
5//!
6//! # Design
7//!
8//! Instead of using separate types (PrimaryChannel, BackgroundChannel),
9//! we use configuration to express behavioral variations. This allows:
10//!
11//! - Simple struct-based design
12//! - Easy extension via new config fields
13//! - Future migration to trait-based design if needed
14//!
15//! # Priority Levels
16//!
17//! ```text
18//! 255 ─ Primary (Human direct)
19//! 100 ─ Tool (spawned for execution)
20//!  50 ─ Normal (default)
21//!  10 ─ Background (parallel tasks)
22//! ```
23//!
24//! # Privilege Inheritance
25//!
26//! Child channels inherit `max_privilege` from their parent and cannot exceed it.
27//! This ensures privilege reduction propagates down the channel tree.
28//!
29//! # Example
30//!
31//! ```
32//! use orcs_runtime::{ChannelConfig, MaxPrivilege};
33//!
34//! // Use predefined configs
35//! let primary = ChannelConfig::interactive();
36//! assert_eq!(primary.priority(), 255);
37//! assert_eq!(primary.max_privilege(), MaxPrivilege::Elevated);
38//!
39//! // Background channels have restricted privileges
40//! let bg = ChannelConfig::background();
41//! assert_eq!(bg.max_privilege(), MaxPrivilege::Standard);
42//! ```
43
44use serde::{Deserialize, Serialize};
45
46/// Maximum privilege level a channel can operate at.
47///
48/// This is a static capability bound, not a runtime state.
49/// Even if a Session is elevated, a channel with `MaxPrivilege::Standard`
50/// cannot perform elevated operations.
51///
52/// # Inheritance
53///
54/// When spawning child channels:
55/// - Child's `max_privilege` is capped by parent's `max_privilege`
56/// - A Standard parent cannot spawn an Elevated child
57///
58/// # Example
59///
60/// ```
61/// use orcs_runtime::MaxPrivilege;
62///
63/// let elevated = MaxPrivilege::Elevated;
64/// let standard = MaxPrivilege::Standard;
65///
66/// // Elevated > Standard
67/// assert!(elevated > standard);
68///
69/// // min() for inheritance
70/// assert_eq!(elevated.min(standard), standard);
71/// ```
72#[derive(
73    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
74)]
75pub enum MaxPrivilege {
76    /// Can only perform standard operations.
77    ///
78    /// Cannot:
79    /// - Send global signals
80    /// - Perform destructive file/git operations
81    #[default]
82    Standard,
83
84    /// Can perform elevated operations (if Session is also elevated).
85    ///
86    /// This is the maximum capability - actual permission still requires
87    /// an elevated Session at runtime.
88    Elevated,
89}
90
91impl MaxPrivilege {
92    /// Returns the minimum of two privilege levels.
93    ///
94    /// Used for privilege inheritance: child = parent.min(requested).
95    #[must_use]
96    pub fn min(self, other: Self) -> Self {
97        if self <= other {
98            self
99        } else {
100            other
101        }
102    }
103
104    /// Returns true if this is the Elevated level.
105    #[must_use]
106    pub fn is_elevated(self) -> bool {
107        matches!(self, Self::Elevated)
108    }
109
110    /// Returns true if this is the Standard level.
111    #[must_use]
112    pub fn is_standard(self) -> bool {
113        matches!(self, Self::Standard)
114    }
115}
116
117/// Priority constants for common channel types.
118pub mod priority {
119    /// Human direct interaction (highest priority).
120    pub const PRIMARY: u8 = 255;
121    /// Tool execution spawned from parent.
122    pub const TOOL: u8 = 100;
123    /// Normal operations (default).
124    pub const NORMAL: u8 = 50;
125    /// Background/parallel tasks (lowest priority).
126    pub const BACKGROUND: u8 = 10;
127}
128
129/// Configuration for a [`Channel`](super::Channel).
130///
131/// Defines behavioral attributes without requiring separate types.
132/// This approach allows flexible channel behavior while maintaining
133/// a simple, unified Channel struct.
134///
135/// # Privilege Inheritance
136///
137/// When spawning child channels, `max_privilege` is inherited:
138/// - Child cannot exceed parent's `max_privilege`
139/// - Use [`inherit_from()`](Self::inherit_from) to apply inheritance
140///
141/// # Example
142///
143/// ```
144/// use orcs_runtime::{ChannelConfig, MaxPrivilege};
145///
146/// let config = ChannelConfig::interactive();
147/// assert!(config.can_spawn());
148/// assert_eq!(config.priority(), 255);
149/// assert_eq!(config.max_privilege(), MaxPrivilege::Elevated);
150///
151/// let bg = ChannelConfig::background();
152/// assert!(!bg.can_spawn());
153/// assert_eq!(bg.max_privilege(), MaxPrivilege::Standard);
154/// ```
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
156pub struct ChannelConfig {
157    /// Scheduling priority (0-255, higher = more priority).
158    priority: u8,
159    /// Whether this channel can spawn child channels.
160    can_spawn: bool,
161    /// Maximum privilege level this channel can operate at.
162    #[serde(default)]
163    max_privilege: MaxPrivilege,
164}
165
166impl ChannelConfig {
167    /// Creates a new configuration with specified attributes.
168    ///
169    /// Uses `MaxPrivilege::Standard` by default. For elevated channels,
170    /// use [`with_max_privilege()`](Self::with_max_privilege).
171    ///
172    /// # Arguments
173    ///
174    /// * `priority` - Scheduling priority (0-255)
175    /// * `can_spawn` - Whether child channels can be spawned
176    ///
177    /// # Example
178    ///
179    /// ```
180    /// use orcs_runtime::ChannelConfig;
181    ///
182    /// let config = ChannelConfig::new(100, true);
183    /// assert_eq!(config.priority(), 100);
184    /// assert!(config.can_spawn());
185    /// ```
186    #[must_use]
187    pub const fn new(priority: u8, can_spawn: bool) -> Self {
188        Self {
189            priority,
190            can_spawn,
191            max_privilege: MaxPrivilege::Standard,
192        }
193    }
194
195    /// Configuration for interactive channels.
196    ///
197    /// Interactive channels:
198    /// - Have highest priority (255)
199    /// - Can spawn child channels
200    /// - Have elevated privilege capability
201    /// - Used for Human direct interaction (IO)
202    ///
203    /// # Example
204    ///
205    /// ```
206    /// use orcs_runtime::{ChannelConfig, MaxPrivilege};
207    ///
208    /// let config = ChannelConfig::interactive();
209    /// assert_eq!(config.priority(), 255);
210    /// assert!(config.can_spawn());
211    /// assert_eq!(config.max_privilege(), MaxPrivilege::Elevated);
212    /// ```
213    #[must_use]
214    pub const fn interactive() -> Self {
215        Self {
216            priority: priority::PRIMARY,
217            can_spawn: true,
218            max_privilege: MaxPrivilege::Elevated,
219        }
220    }
221
222    /// Configuration for background channels.
223    ///
224    /// Background channels:
225    /// - Have lowest priority (10)
226    /// - Cannot spawn child channels
227    /// - Have standard privilege only
228    /// - Used for parallel/async tasks
229    ///
230    /// # Example
231    ///
232    /// ```
233    /// use orcs_runtime::{ChannelConfig, MaxPrivilege};
234    ///
235    /// let config = ChannelConfig::background();
236    /// assert_eq!(config.priority(), 10);
237    /// assert!(!config.can_spawn());
238    /// assert_eq!(config.max_privilege(), MaxPrivilege::Standard);
239    /// ```
240    #[must_use]
241    pub const fn background() -> Self {
242        Self {
243            priority: priority::BACKGROUND,
244            can_spawn: false,
245            max_privilege: MaxPrivilege::Standard,
246        }
247    }
248
249    /// Configuration for tool execution channels.
250    ///
251    /// Tool channels:
252    /// - Have medium-high priority (100)
253    /// - Can spawn child channels (for sub-tools)
254    /// - Have elevated privilege capability
255    /// - Spawned for specific operations
256    ///
257    /// # Example
258    ///
259    /// ```
260    /// use orcs_runtime::{ChannelConfig, MaxPrivilege};
261    ///
262    /// let config = ChannelConfig::tool();
263    /// assert_eq!(config.priority(), 100);
264    /// assert!(config.can_spawn());
265    /// assert_eq!(config.max_privilege(), MaxPrivilege::Elevated);
266    /// ```
267    #[must_use]
268    pub const fn tool() -> Self {
269        Self {
270            priority: priority::TOOL,
271            can_spawn: true,
272            max_privilege: MaxPrivilege::Elevated,
273        }
274    }
275
276    /// Returns the scheduling priority.
277    ///
278    /// Higher values indicate higher priority for scheduling.
279    #[must_use]
280    pub const fn priority(&self) -> u8 {
281        self.priority
282    }
283
284    /// Returns whether this channel can spawn children.
285    #[must_use]
286    pub const fn can_spawn(&self) -> bool {
287        self.can_spawn
288    }
289
290    /// Returns the maximum privilege level.
291    #[must_use]
292    pub const fn max_privilege(&self) -> MaxPrivilege {
293        self.max_privilege
294    }
295
296    /// Returns a new config with the specified priority.
297    #[must_use]
298    pub const fn with_priority(mut self, priority: u8) -> Self {
299        self.priority = priority;
300        self
301    }
302
303    /// Returns a new config with spawn permission set.
304    #[must_use]
305    pub const fn with_spawn(mut self, can_spawn: bool) -> Self {
306        self.can_spawn = can_spawn;
307        self
308    }
309
310    /// Returns a new config with the specified max privilege.
311    #[must_use]
312    pub const fn with_max_privilege(mut self, max_privilege: MaxPrivilege) -> Self {
313        self.max_privilege = max_privilege;
314        self
315    }
316
317    /// Returns a config that inherits constraints from a parent.
318    ///
319    /// The child's `max_privilege` is capped by the parent's level.
320    /// This ensures privilege reduction propagates down the channel tree.
321    ///
322    /// # Example
323    ///
324    /// ```
325    /// use orcs_runtime::{ChannelConfig, MaxPrivilege};
326    ///
327    /// let parent = ChannelConfig::background(); // Standard privilege
328    /// let child_request = ChannelConfig::tool(); // Elevated privilege
329    ///
330    /// let actual = child_request.inherit_from(&parent);
331    /// // Child is capped to parent's Standard level
332    /// assert_eq!(actual.max_privilege(), MaxPrivilege::Standard);
333    /// ```
334    #[must_use]
335    pub fn inherit_from(mut self, parent: &ChannelConfig) -> Self {
336        self.max_privilege = self.max_privilege.min(parent.max_privilege);
337        self
338    }
339}
340
341impl Default for ChannelConfig {
342    /// Default configuration with normal priority, spawn enabled, and standard privilege.
343    fn default() -> Self {
344        Self {
345            priority: priority::NORMAL,
346            can_spawn: true,
347            max_privilege: MaxPrivilege::Standard,
348        }
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    // === MaxPrivilege tests ===
357
358    #[test]
359    fn max_privilege_ordering() {
360        assert!(MaxPrivilege::Elevated > MaxPrivilege::Standard);
361        assert!(MaxPrivilege::Standard < MaxPrivilege::Elevated);
362    }
363
364    #[test]
365    fn max_privilege_min() {
366        assert_eq!(
367            MaxPrivilege::Elevated.min(MaxPrivilege::Standard),
368            MaxPrivilege::Standard
369        );
370        assert_eq!(
371            MaxPrivilege::Standard.min(MaxPrivilege::Elevated),
372            MaxPrivilege::Standard
373        );
374        assert_eq!(
375            MaxPrivilege::Elevated.min(MaxPrivilege::Elevated),
376            MaxPrivilege::Elevated
377        );
378    }
379
380    #[test]
381    fn max_privilege_is_elevated() {
382        assert!(MaxPrivilege::Elevated.is_elevated());
383        assert!(!MaxPrivilege::Standard.is_elevated());
384    }
385
386    #[test]
387    fn max_privilege_default() {
388        assert_eq!(MaxPrivilege::default(), MaxPrivilege::Standard);
389    }
390
391    // === ChannelConfig tests ===
392
393    #[test]
394    fn config_new() {
395        let config = ChannelConfig::new(100, true);
396        assert_eq!(config.priority(), 100);
397        assert!(config.can_spawn());
398        assert_eq!(config.max_privilege(), MaxPrivilege::Standard);
399    }
400
401    #[test]
402    fn config_interactive() {
403        let config = ChannelConfig::interactive();
404        assert_eq!(config.priority(), priority::PRIMARY);
405        assert!(config.can_spawn());
406        assert_eq!(config.max_privilege(), MaxPrivilege::Elevated);
407    }
408
409    #[test]
410    fn config_background() {
411        let config = ChannelConfig::background();
412        assert_eq!(config.priority(), priority::BACKGROUND);
413        assert!(!config.can_spawn());
414        assert_eq!(config.max_privilege(), MaxPrivilege::Standard);
415    }
416
417    #[test]
418    fn config_tool() {
419        let config = ChannelConfig::tool();
420        assert_eq!(config.priority(), priority::TOOL);
421        assert!(config.can_spawn());
422        assert_eq!(config.max_privilege(), MaxPrivilege::Elevated);
423    }
424
425    #[test]
426    fn config_default() {
427        let config = ChannelConfig::default();
428        assert_eq!(config.priority(), priority::NORMAL);
429        assert!(config.can_spawn());
430        assert_eq!(config.max_privilege(), MaxPrivilege::Standard);
431    }
432
433    #[test]
434    fn config_with_priority() {
435        let config = ChannelConfig::default().with_priority(200);
436        assert_eq!(config.priority(), 200);
437        assert!(config.can_spawn());
438    }
439
440    #[test]
441    fn config_with_spawn() {
442        let config = ChannelConfig::default().with_spawn(false);
443        assert_eq!(config.priority(), priority::NORMAL);
444        assert!(!config.can_spawn());
445    }
446
447    #[test]
448    fn config_with_max_privilege() {
449        let config = ChannelConfig::default().with_max_privilege(MaxPrivilege::Elevated);
450        assert_eq!(config.max_privilege(), MaxPrivilege::Elevated);
451    }
452
453    #[test]
454    fn config_equality() {
455        assert_eq!(ChannelConfig::interactive(), ChannelConfig::interactive());
456        assert_ne!(ChannelConfig::interactive(), ChannelConfig::background());
457    }
458
459    #[test]
460    fn config_serialize() {
461        let config = ChannelConfig::interactive();
462        let json = serde_json::to_string(&config).expect("serialize ChannelConfig to JSON");
463        assert!(json.contains("255"));
464        assert!(json.contains("true"));
465        assert!(json.contains("Elevated"));
466    }
467
468    // === Inheritance tests ===
469
470    #[test]
471    fn inherit_from_elevated_parent() {
472        let parent = ChannelConfig::interactive(); // Elevated
473        let child = ChannelConfig::tool().inherit_from(&parent); // Elevated
474
475        // Elevated from Elevated parent stays Elevated
476        assert_eq!(child.max_privilege(), MaxPrivilege::Elevated);
477    }
478
479    #[test]
480    fn inherit_from_standard_parent() {
481        let parent = ChannelConfig::background(); // Standard
482        let child = ChannelConfig::tool().inherit_from(&parent); // requests Elevated
483
484        // Elevated from Standard parent becomes Standard
485        assert_eq!(child.max_privilege(), MaxPrivilege::Standard);
486    }
487
488    #[test]
489    fn inherit_standard_from_elevated() {
490        let parent = ChannelConfig::interactive(); // Elevated
491        let child = ChannelConfig::background().inherit_from(&parent); // Standard
492
493        // Standard request stays Standard (already lower)
494        assert_eq!(child.max_privilege(), MaxPrivilege::Standard);
495    }
496
497    #[test]
498    fn config_deserialize() {
499        let json = r#"{"priority":100,"can_spawn":false}"#;
500        let config: ChannelConfig =
501            serde_json::from_str(json).expect("deserialize ChannelConfig from JSON");
502        assert_eq!(config.priority(), 100);
503        assert!(!config.can_spawn());
504    }
505}