//! Capability flags ("powers") that bots can be granted. //! //! Powers gate high-level SDK operations so a bot only gets the tools it needs. use bitflags::bitflags; bitflags! { /// Set of powers a [`crate::Bot`] may exercise. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Powers: u32 { /// Call remote MFA via `erpc` / `rex` (`spawn_request`). const RPC = 0b0000_0001; /// Send messages to registered names / pids. const SEND = 0b0000_0010; /// Introspect processes, applications, ETS (via RPC helpers). const INTROSPECT = 0b0000_0100; /// Connect / disconnect peers (iroh, tcp, tls). const CLUSTER = 0b0000_1000; /// Accept inbound distribution connections. const LISTEN = 0b0001_0000; /// Evaluate expressions (`erl_eval` style RPC). const EVAL = 0b0010_0000; /// Start / stop function tracing on peers. const TRACE = 0b0100_0000; } } impl Powers { /// Safe default for a worker bot: rpc + send + introspect + cluster. pub fn worker() -> Self { Self::RPC | Self::SEND | Self::INTROSPECT | Self::CLUSTER } /// Read-only observer: introspect + cluster. pub fn observer() -> Self { Self::INTROSPECT | Self::CLUSTER } /// Human-readable list of set flags. pub fn describe(self) -> String { let mut names = Vec::new(); if self.contains(Self::RPC) { names.push("rpc"); } if self.contains(Self::SEND) { names.push("send"); } if self.contains(Self::INTROSPECT) { names.push("introspect"); } if self.contains(Self::CLUSTER) { names.push("cluster"); } if self.contains(Self::LISTEN) { names.push("listen"); } if self.contains(Self::EVAL) { names.push("eval"); } if self.contains(Self::TRACE) { names.push("trace"); } if names.is_empty() { "none".into() } else { names.join("|") } } /// Return `Ok(())` if `needed` is granted, else [`crate::Error::PowerDenied`]. pub fn require(self, needed: Powers) -> crate::Result<()> { if self.contains(needed) { Ok(()) } else { Err(crate::Error::PowerDenied { needed: power_name(needed), have: self.describe(), }) } } } impl Default for Powers { fn default() -> Self { Self::worker() } } impl std::fmt::Display for Powers { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.describe()) } } fn power_name(p: Powers) -> &'static str { // Match single-flag values used at call sites. if p == Powers::RPC { "rpc" } else if p == Powers::SEND { "send" } else if p == Powers::INTROSPECT { "introspect" } else if p == Powers::CLUSTER { "cluster" } else if p == Powers::LISTEN { "listen" } else if p == Powers::EVAL { "eval" } else if p == Powers::TRACE { "trace" } else { "unknown" } }