nixfleet_agent/runtime/
wire.rs

1//! Wire types for the agent's HTTP traffic against CP.
2//!
3//! These mirror the request / response shapes defined in
4//! `nixfleet_control_plane::server::routes::{dispatch,heartbeat,events}`.
5//! Hand-mirrored here because they live inside the CP crate (not in
6//! `nixfleet-proto`); a future cleanup could move them into
7//! `nixfleet-proto::agent_wire` so the two sides share one type.
8
9use chrono::{DateTime, Utc};
10use nixfleet_proto::RolloutId;
11use serde::{Deserialize, Serialize};
12
13/// Body of `GET /v1/agent/dispatch?wait=60` when CP has a queued
14/// dispatch for this agent. Empty body / 204 means no work pending.
15///
16/// `rollout_id` is the canonical `"{channel}@{channel_ref}"` composite
17/// (RFC-0008 §6.3); serde-transparent so the wire JSON shape is
18/// indistinguishable from a plain `String`.
19#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
20pub struct DispatchResponse {
21    pub hostname: String,
22    pub rollout_id: RolloutId,
23    pub target_closure: String,
24    pub soak_due_at: DateTime<Utc>,
25    pub enqueued_at: DateTime<Utc>,
26}
27
28/// Body of `POST /v1/agent/heartbeat`.
29#[derive(Debug, Clone, Deserialize, Serialize)]
30pub struct HeartbeatRequest {
31    pub hostname: String,
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub rollout_id: Option<RolloutId>,
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub current_closure: Option<String>,
36    pub at: DateTime<Utc>,
37}
38
39#[derive(Debug, Clone, Deserialize, Serialize)]
40pub struct HeartbeatResponse {
41    #[serde(default)]
42    pub received_at: Option<DateTime<Utc>>,
43    /// LIFT #3: per active rollout on this host, a HostRolloutSnapshot
44    /// the agent should apply to its in-memory reducer (RFC-0005 §9.5
45    /// scenario 3, agent-side half). Populated by CP only when the
46    /// heartbeat carried `rollout_id = None` AND CP holds non-terminal
47    /// records for the host. Empty in steady-state.
48    #[serde(default, skip_serializing_if = "Vec::is_empty")]
49    pub bootstrap_rollouts: Vec<nixfleet_proto::agent_wire::HostRolloutSnapshot>,
50}
51
52/// Reset signal sent by the applier to the probe worker. Cleared on
53/// `Effect::LocalResetProbeCache`. Per-rollout scope.
54#[derive(Debug, Clone)]
55pub struct ProbeResetCommand {
56    pub rollout_id: RolloutId,
57}
58
59/// Intent signal sent by the applier to the activation worker. Triggers
60/// real systemd-run dispatch. (Stub in 7c — activation worker just emits
61/// the corresponding Started/Completed/Failed events directly through
62/// the input MPSC.)
63#[derive(Debug, Clone)]
64pub struct ActivationIntent {
65    pub rollout_id: RolloutId,
66    pub target_closure: String,
67    pub rollback: bool,
68}