//! 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 `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. //! //! The bucket *name* is unconditional; only `open_or_create` is behind the //! `kv` feature. (Named in backticks rather than linked: with `kv` off the item //! does not exist, and an intra-doc link to it fails the rustdoc gate in //! exactly the configuration this split exists to support.) A third end names the bucket without ever opening it — the //! auth-callout responder, which derives the subjects a hive may publish to //! from it — and it speaks neither `jetstream` nor `kv`. Gating the name too //! would have made that consumer choose between a JetStream stack it does not //! use and a copied literal, and a copied literal is precisely the agreement //! nothing checks. #[cfg(feature = "kv")] 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. #[cfg(feature = "kv")] 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, }) } } }