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}