Skip to main content

kanade_shared/
kv.rs

1//! NATS KV bucket name + key helpers (spec §2.3.2).
2//!
3//! NATS KV bucket names must be domain-safe ASCII (a-z, A-Z, 0-9, _, -),
4//! so the spec's dotted names (`script.current`, `script.status`) are
5//! flattened to underscore form here.
6
7pub const BUCKET_SCRIPT_CURRENT: &str = "script_current";
8pub const BUCKET_SCRIPT_STATUS: &str = "script_status";
9pub const BUCKET_AGENTS_STATE: &str = "agents_state";
10pub const BUCKET_AGENT_CONFIG: &str = "agent_config";
11pub const BUCKET_SCHEDULES: &str = "schedules";
12
13/// Object Store bucket holding raw agent binaries (one object per
14/// version, e.g. `0.2.0` → file bytes).
15pub const OBJECT_AGENT_RELEASES: &str = "agent_releases";
16
17/// Key inside [`BUCKET_AGENT_CONFIG`] carrying the broadcast target
18/// version. Agents watch this key and self-update when their running
19/// version drifts.
20pub const KEY_AGENT_TARGET_VERSION: &str = "target_version";
21
22pub const SCRIPT_STATUS_ACTIVE: &str = "ACTIVE";
23pub const SCRIPT_STATUS_REVOKED: &str = "REVOKED";
24
25pub const STREAM_INVENTORY: &str = "INVENTORY";
26pub const STREAM_RESULTS: &str = "RESULTS";
27pub const STREAM_DEPLOY: &str = "DEPLOY";
28pub const STREAM_EVENTS: &str = "EVENTS";
29pub const STREAM_AUDIT: &str = "AUDIT";
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    /// NATS KV bucket names must be domain-safe ASCII (a-z, A-Z, 0-9, _, -).
36    /// Lock the constants down so a future edit doesn't introduce a `.` and
37    /// break create_key_value silently on the broker side.
38    #[test]
39    fn bucket_names_are_domain_safe() {
40        for name in [
41            BUCKET_SCRIPT_CURRENT,
42            BUCKET_SCRIPT_STATUS,
43            BUCKET_AGENTS_STATE,
44            BUCKET_AGENT_CONFIG,
45            BUCKET_SCHEDULES,
46            OBJECT_AGENT_RELEASES,
47        ] {
48            assert!(
49                !name.contains('.'),
50                "bucket name {name:?} contains a dot, which NATS KV rejects"
51            );
52            assert!(
53                name.chars()
54                    .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-'),
55                "bucket name {name:?} has non-domain-safe characters"
56            );
57        }
58    }
59
60    #[test]
61    fn stream_names_are_unique() {
62        let names = [
63            STREAM_INVENTORY,
64            STREAM_RESULTS,
65            STREAM_DEPLOY,
66            STREAM_EVENTS,
67            STREAM_AUDIT,
68        ];
69        let mut deduped = names.to_vec();
70        deduped.sort_unstable();
71        deduped.dedup();
72        assert_eq!(
73            deduped.len(),
74            names.len(),
75            "stream constants collide: {names:?}"
76        );
77    }
78
79    #[test]
80    fn script_status_strings() {
81        assert_eq!(SCRIPT_STATUS_ACTIVE, "ACTIVE");
82        assert_eq!(SCRIPT_STATUS_REVOKED, "REVOKED");
83        assert_ne!(SCRIPT_STATUS_ACTIVE, SCRIPT_STATUS_REVOKED);
84    }
85
86    #[test]
87    fn key_agent_target_version_constant() {
88        assert_eq!(KEY_AGENT_TARGET_VERSION, "target_version");
89    }
90}