//! The per-agent closure KV bucket: one key per agent, holding the latest //! system closure the swarm builder built for it from the swarm meta flake. //! //! **swarm-controller is the only writer**, and creates the bucket on its first //! write. It writes a key from a builder's `Built` reply and from nothing else, //! so a failed build leaves the previous record in place. The builder holds no //! grant on this bucket. //! //! Keyed by the agent alone, like [`crate::agent_limits`]: an agent can move //! between hives, and its closure goes with it. /// The KV bucket closures are recorded in, keyed by agent name. pub const BUCKET: &str = "agent-closures"; /// The stream backing [`BUCKET`]. `create_key_value` prefixes `KV_`. pub const STREAM: &str = "KV_agent-closures"; /// The subject `agent`'s entry is stored under. #[must_use] pub fn key_subject(agent: &str) -> String { format!("$KV.{BUCKET}.{agent}") } /// One agent's latest green build: the value under `BUCKET`/``. #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct AgentClosure { /// The `internal/meta` commit the closure was built from. pub meta_rev: String, /// The system closure's store path, in the builder host's store. pub store_path: String, /// The platform the closure was built for. pub system: String, /// When the controller recorded the build, in unix seconds. pub built_unix: i64, } #[cfg(test)] mod tests { use super::*; #[test] fn names_are_pinned() { assert_eq!(STREAM, format!("KV_{BUCKET}")); assert_eq!(key_subject("iris"), "$KV.agent-closures.iris"); } #[test] fn the_record_serializes_as_pinned() { let value = AgentClosure { meta_rev: "abc".to_owned(), store_path: "/nix/store/x".to_owned(), system: "x86_64-linux".to_owned(), built_unix: 7, }; let wire = serde_json::json!({ "meta_rev": "abc", "store_path": "/nix/store/x", "system": "x86_64-linux", "built_unix": 7, }); assert_eq!(serde_json::to_value(&value).expect("encodes"), wire); assert_eq!( serde_json::from_value::(wire).expect("decodes"), value ); } }