Skip to main content

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}