nixfleet_proto/
agent_wire.rs

1//! Agent ↔ control-plane wire types. LOADBEARING: within a major version,
2//! additions must be backwards-compatible (older consumers serde-ignore unknown
3//! fields); bump `PROTOCOL_MAJOR_VERSION` for any breaking change.
4//!
5//! Phase 8d trimmed the legacy v0.1 checkin / confirm / activate wire shape;
6//! Phase 9a then deleted the legacy `/v1/agent/report` surface (`ReportRequest`,
7//! `ReportEvent`, `ReportResponse`) — the unified event-driven wire (RFC-0005
8//! §4.2 → `runtime/wire.rs` + CP's `server/routes/events.rs`) is now the
9//! sole agent→CP event channel.
10
11use chrono::{DateTime, Utc};
12use serde::{Deserialize, Serialize};
13use serde_with::skip_serializing_none;
14
15use crate::RolloutId;
16use crate::host_rollout_state::HostRolloutState;
17
18/// Sent in `X-Nixfleet-Protocol`; CP rejects mismatched majors with 426.
19pub const PROTOCOL_MAJOR_VERSION: u32 = 1;
20
21pub const PROTOCOL_VERSION_HEADER: &str = "x-nixfleet-protocol";
22
23#[skip_serializing_none]
24#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct GenerationRef {
27    pub closure_hash: String,
28    #[serde(default)]
29    pub channel_ref: Option<String>,
30    pub boot_id: String,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct PendingGeneration {
36    pub closure_hash: String,
37}
38
39/// LIFT #3: snapshot of a host's per-rollout state, carried in the
40/// HeartbeatResponse when CP detects the agent's reducer is empty but
41/// CP holds non-terminal records for the host (typical post-restart
42/// shape after LIFT #1 synthesizes the state advance). The agent's
43/// boot-recovery handler applies each snapshot to its in-memory
44/// HostRolloutState before workers spawn, restoring the cache so
45/// probe runners + advance-ticker resume their work post-restart.
46///
47/// Fields mirror `nixfleet_state_machine::HostRolloutState`'s
48/// LOADBEARING set (RFC-0005 §5) — anything the agent's reducer or
49/// downstream workers (probe topology, advance-ticker, soak-elapsed
50/// detection) need to drive the state forward. Probe state itself is
51/// NOT carried: probe runners re-emit `LocalProbeTopologyDeclared`
52/// from health-checks.json on startup and probes repopulate via
53/// fresh runs. A pre-restart sustained-failure timer (`probe_failure_
54/// first_at`) resets across restart; tracked as v0.2.1 polish.
55#[skip_serializing_none]
56#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
57#[serde(rename_all = "camelCase")]
58pub struct HostRolloutSnapshot {
59    pub rollout_id: RolloutId,
60    pub hostname: String,
61    pub channel: String,
62    pub state: HostRolloutState,
63    pub target_closure: String,
64    #[serde(default)]
65    pub current_closure_at_dispatch: Option<String>,
66    #[serde(default)]
67    pub current_closure: Option<String>,
68    pub dispatched_at: DateTime<Utc>,
69    #[serde(default)]
70    pub dispatch_acked_at: Option<DateTime<Utc>>,
71    #[serde(default)]
72    pub activation_started_at: Option<DateTime<Utc>>,
73    #[serde(default)]
74    pub activation_completed_at: Option<DateTime<Utc>>,
75    #[serde(default)]
76    pub soak_due_at: Option<DateTime<Utc>>,
77    pub last_event_seq: u64,
78}