//! The hive-status KV bucket: its name, and the shape it is created with. //! //! Two processes touch this bucket from opposite ends — a hive writes its //! own key, the swarm controller reads every key — and they live in //! different crates. That is the whole reason this module exists rather //! than a `const` on each side: **the two ends must agree, and a literal //! repeated across crates is an agreement nothing checks.** //! //! The name is the obvious half. The sharper half is the *config*: both //! ends open the bucket with [`crate::status::open_or_create`], because either may //! arrive first on a fresh swarm and neither can assume the other has //! run. If the two ends passed different `Config`s, whichever created it //! would win and the other's `get_key_value` would succeed against a //! bucket it did not ask for — no error, no log, just a retention policy //! nobody chose. Sharing the constructor makes the race have one outcome //! instead of two. //! //! Feature-gated (`kv`) so the crate's other consumer, the auth-callout //! responder, still pulls neither `jetstream` nor `kv`: it speaks the //! connect and nothing else. use crate::Error; /// The KV bucket hives publish their status snapshots into, one key per /// hive keyed by `hiveName`. /// /// A constant and not an option: reader and writer must name the same /// bucket, and an option is a way for two deployments to disagree about /// which one that is. Nothing about a bucket name is site-specific. pub const BUCKET: &str = "hive-status"; /// Open the status bucket, creating it if nothing has yet. /// /// `history: 1` is the shape: every consumer reads *the last thing each /// hive said*, and retaining more would be storage bought for a query /// nobody makes. /// /// Creating rather than requiring a provisioning step is deliberate — the /// controller and the hives come up in no particular order, and a bucket /// that must pre-exist turns "the swarm was deployed in the wrong order" /// into a permanent, silent absence of data. pub async fn open_or_create( client: &async_nats::Client, ) -> Result { let js = async_nats::jetstream::new(client.clone()); match js.get_key_value(BUCKET).await { Ok(store) => Ok(store), Err(e) => { tracing::info!( bucket = BUCKET, reason = %e, "status bucket not available, creating it" ); js.create_key_value(async_nats::jetstream::kv::Config { bucket: BUCKET.to_owned(), description: "Last status snapshot offered by each hive".to_owned(), history: 1, ..Default::default() }) .await .map_err(|source| Error::CreateBucket { bucket: BUCKET, source, }) } } }