Two sites spelled it as a brace group, services.hyperhive.{hiveName,
swarmName}, which no anchored rewrite can handle correctly now that only
one of the two moves; both are written out separately. One of them is an
MCP tool description, so it is rendered into every agent's system prompt.
255 lines
9.4 KiB
Rust
255 lines
9.4 KiB
Rust
//! 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<String> {
|
|
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<String> {
|
|
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<String> {
|
|
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.swarm.name` option is unset.
|
|
#[must_use]
|
|
pub fn swarm_name() -> Option<String> {
|
|
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<F: FnOnce()>(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<F: FnOnce()>(
|
|
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");
|
|
},
|
|
);
|
|
}
|
|
}
|