Skip to main content

kanade_shared/
subject.rs

1pub const COMMANDS_ALL: &str = "commands.all";
2
3pub fn commands_group(name: &str) -> String {
4    format!("commands.group.{name}")
5}
6
7pub fn commands_pc(pc_id: &str) -> String {
8    format!("commands.pc.{pc_id}")
9}
10
11// `commands_exec` (subject `commands.exec.<job_id>`) was removed in
12// v0.22.1. The STREAM_EXEC stream now catches the existing
13// `commands.{all,group.X,pc.Y}` subjects directly, so the dedicated
14// per-exec subject isn't needed any more. See
15// `kanade-agent::command_replay` for how reconnecting agents catch
16// up on missed messages.
17
18pub fn results(request_id: &str) -> String {
19    format!("results.{request_id}")
20}
21
22pub fn heartbeat(pc_id: &str) -> String {
23    format!("heartbeat.{pc_id}")
24}
25
26/// `kill.<exec_id>` — Spec §2.6 Layer 3 abort signal. The exec_id is
27/// the deployment / scheduler-fire UUID (formerly named `job_id`
28/// pre-v0.29; renamed for accuracy — every `Command.exec_id` is a
29/// per-deploy UUID, not a job-catalog id).
30pub fn kill(exec_id: &str) -> String {
31    format!("kill.{exec_id}")
32}
33
34pub fn inventory(pc_id: &str, category: &str) -> String {
35    format!("inventory.{pc_id}.{category}")
36}
37
38/// `events.started.<exec_id>.<pc_id>` — v0.30 / PR α' lifecycle
39/// event published by the agent just before spawning a script's
40/// child process. Lets the backend project an in-flight row into
41/// `execution_results` (with `finished_at = NULL`) so the SPA
42/// Activity table can show running rows alongside finished ones.
43/// Backend subscribes via [`EVENTS_STARTED_FILTER`].
44pub fn events_started(exec_id: &str, pc_id: &str) -> String {
45    format!("events.started.{exec_id}.{pc_id}")
46}
47
48/// Wildcard the backend events projector consumes on STREAM_EVENTS.
49/// Narrow (`events.started.>`) rather than the whole `events.>` so
50/// future event types can carry their own filters without rerouting
51/// the started subset.
52pub const EVENTS_STARTED_FILTER: &str = "events.started.>";
53
54pub const INVENTORY_HW: &str = "hw";
55pub const INVENTORY_SW: &str = "sw";
56pub const INVENTORY_NET: &str = "net";
57
58/// `logs.fetch.<pc_id>` — request/reply: operator (or backend) sends
59/// a `LogsRequest`; the addressed agent replies with the tail of its
60/// local log file. On-demand only, no stream.
61pub fn logs_fetch(pc_id: &str) -> String {
62    format!("logs.fetch.{pc_id}")
63}
64
65// v0.14: subject::inventory_request was retired alongside the
66// hardcoded inventory loop. On-demand collection now goes through
67// the normal exec path (`kanade exec configs/jobs/inventory-
68// hw.yaml`) — Command + ExecResult + the inventory-fact projector
69// give operators the same effect with no extra subject.
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn commands_all_constant() {
77        assert_eq!(COMMANDS_ALL, "commands.all");
78    }
79
80    #[test]
81    fn commands_group_formats_name() {
82        assert_eq!(commands_group("canary"), "commands.group.canary");
83        assert_eq!(commands_group("wave1"), "commands.group.wave1");
84    }
85
86    #[test]
87    fn commands_pc_formats_id() {
88        assert_eq!(commands_pc("minipc"), "commands.pc.minipc");
89        assert_eq!(commands_pc("PC1234"), "commands.pc.PC1234");
90    }
91
92    #[test]
93    fn results_formats_request_id() {
94        assert_eq!(results("req-1"), "results.req-1");
95    }
96
97    #[test]
98    fn heartbeat_formats_pc_id() {
99        assert_eq!(heartbeat("minipc"), "heartbeat.minipc");
100    }
101
102    #[test]
103    fn kill_formats_exec_id() {
104        assert_eq!(kill("exec-uuid-1"), "kill.exec-uuid-1");
105    }
106
107    #[test]
108    fn logs_fetch_formats_pc_id() {
109        assert_eq!(logs_fetch("minipc"), "logs.fetch.minipc");
110    }
111
112    #[test]
113    fn events_started_formats_exec_id_and_pc_id() {
114        assert_eq!(
115            events_started("exec-uuid-1", "minipc"),
116            "events.started.exec-uuid-1.minipc",
117        );
118    }
119
120    #[test]
121    fn events_started_filter_is_narrow_wildcard() {
122        assert_eq!(EVENTS_STARTED_FILTER, "events.started.>");
123    }
124
125    #[test]
126    fn inventory_formats_pc_id_and_category() {
127        assert_eq!(inventory("minipc", "hw"), "inventory.minipc.hw");
128        assert_eq!(inventory("minipc", INVENTORY_HW), "inventory.minipc.hw");
129        assert_eq!(inventory("minipc", INVENTORY_SW), "inventory.minipc.sw");
130        assert_eq!(inventory("minipc", INVENTORY_NET), "inventory.minipc.net");
131    }
132}