//! The egress allow-list KV bucket: one key holding the destination names the //! swarm lets agents reach through their hive's squid. //! //! **swarm-controller is the only writer**, and creates the bucket on its //! first write. Each [`Entry`] is a domain with a scope: every agent in the //! swarm, or one agent. Until the controller has written the key, a hive //! keeps the list its own nix configuration names. //! //! **Every hive reads the one key** ([`direct_get_subject`]), with a grant that //! covers the bucket read-only, and renders the entries for the agents it runs. //! The names go into squid's configuration, so [`normalize_domain`] is the one //! gate both ends use: the controller at write, the hive again at render. /// The KV bucket the list is published into. pub const BUCKET: &str = "egress-allow"; /// The stream backing [`BUCKET`]. `create_key_value` prefixes `KV_`. pub const STREAM: &str = "KV_egress-allow"; /// The key the whole list is stored under. pub const KEY: &str = "list"; /// The subject the list is stored under. #[must_use] pub fn key_subject() -> String { format!("$KV.{BUCKET}.{KEY}") } /// The request subject that reads the current list. #[must_use] pub fn direct_get_subject() -> String { format!("$JS.API.DIRECT.GET.{STREAM}.{}", key_subject()) } /// The most entries a list holds, which keeps the value far below the queue's /// message size and the rendered squid configuration small. pub const MAX_ENTRIES: usize = 1000; /// One allowed destination and who may reach it. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] pub struct Entry { /// A name as [`normalize_domain`] returns it. pub domain: String, /// The agent this entry is for; absent means every agent in the swarm. #[serde(default, skip_serializing_if = "Option::is_none")] pub agent: Option, /// The controller derived this entry from the agent's backend environment /// or a linked account and replaces it whenever that changes; an operator /// can neither add nor remove one. A hive renders it like any other entry, /// and a reader that predates the field ignores it. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub derived: bool, /// What a derived entry follows: [`BACKEND_ENV_SOURCE`], or a name starting /// with [`LINK_SOURCE_PREFIX`] for an account the operator linked. Absent /// on an entry written before the field existed, which counts as /// [`BACKEND_ENV_SOURCE`]. A reader that predates the field ignores it. #[serde(default, skip_serializing_if = "Option::is_none")] pub source: Option, } /// The [`Entry::source`] of an entry derived from the agent's backend /// environment. pub const BACKEND_ENV_SOURCE: &str = "backend-env"; /// What the [`Entry::source`] of an entry derived from a linked account starts /// with: `link:github`, `link:matrix:` or `link:forge: