synwire_sandbox/visibility.rs
1#![allow(clippy::type_complexity, clippy::significant_drop_tightening)]
2//! Parent-child process visibility scoping.
3//!
4//! [`ProcessVisibilityScope`] controls which process registries an agent can
5//! read. Parent agents see their own processes **and** all sub-agent
6//! processes; child agents see only their own.
7//!
8//! Write operations (signal, kill) always target the agent's own registry —
9//! a child agent cannot kill a parent's processes.
10
11use std::sync::Arc;
12
13use tokio::sync::RwLock;
14
15use crate::process_registry::{ProcessRecord, ProcessRegistry};
16
17/// Scoped view of process registries for parent-child visibility.
18///
19/// # Visibility rules
20///
21/// | Operation | Own processes | Child processes |
22/// |-----------|:------------:|:---------------:|
23/// | list | yes | yes |
24/// | stats | yes | yes (read-only) |
25/// | wait | yes | yes |
26/// | read output | yes | yes |
27/// | kill | yes | **no** |
28#[derive(Debug, Clone)]
29pub struct ProcessVisibilityScope {
30 /// This agent's own process registry.
31 pub own: Arc<RwLock<ProcessRegistry>>,
32 /// Child agent registries visible to this agent.
33 children: Arc<tokio::sync::Mutex<Vec<(String, Arc<RwLock<ProcessRegistry>>)>>>,
34}
35
36impl ProcessVisibilityScope {
37 /// Create a new scope backed by the given registry (no children initially).
38 #[must_use]
39 pub fn new(registry: Arc<RwLock<ProcessRegistry>>) -> Self {
40 Self {
41 own: registry,
42 children: Arc::new(tokio::sync::Mutex::new(Vec::new())),
43 }
44 }
45
46 /// Register a child agent's registry as visible to this agent.
47 ///
48 /// `label` is a display name (e.g., agent UUID or name) used to tag
49 /// child processes in listing output.
50 pub async fn add_child_registry(
51 &self,
52 label: impl Into<String>,
53 registry: Arc<RwLock<ProcessRegistry>>,
54 ) {
55 self.children.lock().await.push((label.into(), registry));
56 }
57
58 /// Collect all running processes visible to this agent.
59 ///
60 /// Returns `(agent_label, record)` pairs. `agent_label` is `None` for
61 /// own processes and `Some(label)` for child-agent processes.
62 pub async fn visible_running(&self) -> Vec<(Option<String>, ProcessRecord)> {
63 let mut result = Vec::new();
64
65 {
66 let own = self.own.read().await;
67 for r in own.running() {
68 result.push((None, r.clone()));
69 }
70 }
71
72 let children = self.children.lock().await;
73 for (label, reg) in children.iter() {
74 let child_reg = reg.read().await;
75 for r in child_reg.running() {
76 result.push((Some(label.clone()), r.clone()));
77 }
78 }
79
80 result
81 }
82
83 /// Look up a process in all visible registries (own first, then children).
84 ///
85 /// Returns `(agent_label, record)`. Returns `None` if the PID is not
86 /// found in any visible registry.
87 pub async fn find(&self, pid: u32) -> Option<(Option<String>, ProcessRecord)> {
88 {
89 let own = self.own.read().await;
90 if let Some(r) = own.get(pid) {
91 return Some((None, r.clone()));
92 }
93 }
94
95 let children = self.children.lock().await;
96 for (label, reg) in children.iter() {
97 let child_reg = reg.read().await;
98 if let Some(r) = child_reg.get(pid) {
99 return Some((Some(label.clone()), r.clone()));
100 }
101 }
102
103 None
104 }
105}