//! Agent identity helpers — short label + hive-qualified long name + //! human display names for the hive and swarm. Full env var surface + //! domain-vs-name distinction documented in //! `docs/conventions.md::Hive identity (label + domain + display names)`. use std::env; /// Short, hive-local agent label. Read from `HIVE_LABEL`; falls back to an /// empty string when the env var is missing, so callers downstream can decide /// how to surface "unknown agent" rather than getting a panic from this /// module. #[must_use] pub fn label() -> String { env::var("HIVE_LABEL").unwrap_or_default() } /// `env::var(key)` reduced to `Some(value)` only when the var is set and /// non-empty — the shared shape of the hive/swarm display-name lookups below. fn non_empty_env(key: &str) -> Option { env::var(key).ok().filter(|s| !s.is_empty()) } /// The hive's canonical DNS domain when set, otherwise None. Single-hive /// deployments where `HYPERHIVE_HIVE_DOMAIN` is unset return None — callers /// then degrade gracefully to the short label. #[must_use] pub fn hive_domain() -> Option { non_empty_env("HYPERHIVE_HIVE_DOMAIN") } /// Human display name of this hive (e.g. `pr1ma`). Distinct from /// [`hive_domain`] — the domain is the machine-readable DNS address; /// this is the prose label humans use in conversation. Returns None /// when the host-side `services.hyperhive.hiveName` option is unset, /// in which case callers fall back to the domain or the short label /// at their discretion. #[must_use] pub fn hive_name() -> Option { non_empty_env("HYPERHIVE_HIVE_NAME") } /// Human display name of the wider swarm this hive belongs to (e.g. /// `constellat1on`). Federated hives at different DNS domains can /// share a swarm name. Returns None when the host-side /// `services.hyperhive.swarmName` option is unset. #[must_use] pub fn swarm_name() -> Option { non_empty_env("HYPERHIVE_SWARM_NAME") } /// Hive-qualified agent identity. When the hive domain is configured, returns /// `${label}@${domain}` (e.g. `iris@darkest.space`); when not, returns just /// the short label so callers can render a single string regardless of /// deployment shape. Callers that want to know whether the result is /// qualified should check [`hive_domain`] directly. #[must_use] pub fn qualified_label() -> String { qualify(&label()) } /// Apply hive qualification to an arbitrary agent label (e.g. a peer name /// from the broker). Mirrors [`qualified_label`] but lets a caller qualify /// names it didn't read from the env. Returns `${label}@${domain}` when the /// hive domain is set, else just `label`. #[must_use] pub fn qualify(label: &str) -> String { match hive_domain() { Some(domain) if !label.is_empty() => format!("{label}@{domain}"), _ => label.to_owned(), } } #[cfg(test)] mod tests { use super::*; use std::sync::Mutex; /// cargo's test runner parallelises by default, so a `with_env` /// helper that mutates process-wide env vars races between tests in /// this module. Serialise on a module-scope mutex so each `with_env` /// call holds the lock for its set / run / restore window. Cheap /// (each test body is microseconds) and avoids pulling in /// `serial_test` for just one module. static ENV_LOCK: Mutex<()> = Mutex::new(()); /// Helper: run `f` with a clean env, restoring previous values on exit. /// Acquires `ENV_LOCK` first so concurrent tests don't race the env-var /// state. If a previous test panicked while holding the lock the /// mutex would be poisoned — we use `lock().unwrap_or_else(|e| e.into_inner())` /// to recover so a single test failure doesn't cascade through the /// whole module. fn with_env(label: Option<&str>, domain: Option<&str>, f: F) { with_full_env(label, domain, None, None, f); } /// Extended form of [`with_env`] covering the display-name env vars /// (hive name + swarm name) alongside label + domain. Same SAFETY /// contract — serialised on `ENV_LOCK`, restore in scope. fn with_full_env( label: Option<&str>, domain: Option<&str>, hive_name: Option<&str>, swarm_name: Option<&str>, f: F, ) { let _guard = ENV_LOCK .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); let prev_label = env::var("HIVE_LABEL").ok(); let prev_domain = env::var("HYPERHIVE_HIVE_DOMAIN").ok(); let prev_hive_name = env::var("HYPERHIVE_HIVE_NAME").ok(); let prev_swarm_name = env::var("HYPERHIVE_SWARM_NAME").ok(); // SAFETY: serialised by ENV_LOCK above; restore in the same scope. unsafe { match label { Some(v) => env::set_var("HIVE_LABEL", v), None => env::remove_var("HIVE_LABEL"), } match domain { Some(v) => env::set_var("HYPERHIVE_HIVE_DOMAIN", v), None => env::remove_var("HYPERHIVE_HIVE_DOMAIN"), } match hive_name { Some(v) => env::set_var("HYPERHIVE_HIVE_NAME", v), None => env::remove_var("HYPERHIVE_HIVE_NAME"), } match swarm_name { Some(v) => env::set_var("HYPERHIVE_SWARM_NAME", v), None => env::remove_var("HYPERHIVE_SWARM_NAME"), } } f(); unsafe { match prev_label { Some(v) => env::set_var("HIVE_LABEL", v), None => env::remove_var("HIVE_LABEL"), } match prev_domain { Some(v) => env::set_var("HYPERHIVE_HIVE_DOMAIN", v), None => env::remove_var("HYPERHIVE_HIVE_DOMAIN"), } match prev_hive_name { Some(v) => env::set_var("HYPERHIVE_HIVE_NAME", v), None => env::remove_var("HYPERHIVE_HIVE_NAME"), } match prev_swarm_name { Some(v) => env::set_var("HYPERHIVE_SWARM_NAME", v), None => env::remove_var("HYPERHIVE_SWARM_NAME"), } } } #[test] fn qualified_label_with_domain_set() { with_env(Some("iris"), Some("darkest.space"), || { assert_eq!(qualified_label(), "iris@darkest.space"); assert_eq!(hive_domain().as_deref(), Some("darkest.space")); }); } #[test] fn qualified_label_falls_back_to_short_when_domain_unset() { with_env(Some("iris"), None, || { assert_eq!(qualified_label(), "iris"); assert!(hive_domain().is_none()); }); } #[test] fn qualified_label_falls_back_to_short_when_domain_empty() { // Empty string is treated the same as unset — a misconfigured // module shouldn't surface `iris@` (no domain) to the operator. with_env(Some("iris"), Some(""), || { assert_eq!(qualified_label(), "iris"); assert!(hive_domain().is_none()); }); } #[test] fn qualify_takes_arbitrary_label() { with_env(Some("iris"), Some("darkest.space"), || { // Local label gets local hive applied; useful for rendering // a peer's name when the caller knows it's hive-local. assert_eq!(qualify("damocles"), "damocles@darkest.space"); }); } #[test] fn qualify_empty_label_stays_empty() { with_env(Some("iris"), Some("darkest.space"), || { assert_eq!(qualify(""), ""); }); } #[test] fn label_returns_empty_when_unset() { with_env(None, None, || { assert_eq!(label(), ""); }); } #[test] fn hive_name_returns_some_when_env_set() { with_full_env(Some("iris"), None, Some("pr1ma"), None, || { assert_eq!(hive_name().as_deref(), Some("pr1ma")); }); } #[test] fn hive_name_returns_none_when_env_unset_or_empty() { with_full_env(Some("iris"), None, None, None, || { assert!(hive_name().is_none()); }); with_full_env(Some("iris"), None, Some(""), None, || { assert!(hive_name().is_none(), "empty string treated as unset"); }); } #[test] fn swarm_name_returns_some_when_env_set() { with_full_env(Some("iris"), None, None, Some("constellat1on"), || { assert_eq!(swarm_name().as_deref(), Some("constellat1on")); }); } #[test] fn swarm_name_returns_none_when_env_unset_or_empty() { with_full_env(Some("iris"), None, None, None, || { assert!(swarm_name().is_none()); }); with_full_env(Some("iris"), None, None, Some(""), || { assert!(swarm_name().is_none(), "empty string treated as unset"); }); } #[test] fn name_accessors_independent_from_domain() { // hive_name + swarm_name surface without HYPERHIVE_HIVE_DOMAIN // being set — the names are display labels, not derived from // the DNS domain. with_full_env( Some("iris"), None, Some("pr1ma"), Some("constellat1on"), || { assert!(hive_domain().is_none()); assert_eq!(hive_name().as_deref(), Some("pr1ma")); assert_eq!(swarm_name().as_deref(), Some("constellat1on")); // qualified_label still degrades to short label without domain. assert_eq!(qualified_label(), "iris"); }, ); } }