nixfleet_state_machine/rollout/event.rs
1//! Rollout-level event vocabulary (RFC-0008 §4). All CP-internal: the
2//! applier synthesizes these from per-host events (RFC-0005 §4.2) and
3//! channel-refs poll outcomes — agents never emit `RolloutEvent`s on the
4//! wire.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9use crate::HostState;
10use crate::rollout::state::{ChannelId, RolloutId};
11use nixfleet_proto::ChannelRef;
12
13pub type HostId = String;
14
15/// Per-host state representation as seen by the rollout reducer.
16///
17/// The rollout reducer reasons about per-host transitions at a coarser
18/// granularity than RFC-0005 §3's six states; this alias names the input
19/// shape it accepts on `HostStateChanged`. Today it carries the full
20/// RFC-0005 state (re-exported from the host reducer); future projection
21/// to a coarser enum is a v0.3 optimisation.
22pub type HostRolloutState = HostState;
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub enum RolloutEvent {
26 /// Channel-refs poll detected a new ref; planner opened the rollout.
27 /// First event in any rollout's lifecycle.
28 RolloutOpened {
29 rollout_id: RolloutId,
30 channel: ChannelId,
31 target_ref: ChannelRef,
32 at: DateTime<Utc>,
33 },
34 /// A host has been dispatched into this rollout (RFC-0005 §4.1
35 /// `Dispatch` queued). First `HostJoined` moves `Opening → Active`.
36 HostJoined {
37 rollout_id: RolloutId,
38 host_id: HostId,
39 wave: u32,
40 at: DateTime<Utc>,
41 },
42 /// Per-host state transitioned (RFC-0005 §3). The rollout reducer
43 /// aggregates these to drive the rollout-level state machine.
44 HostStateChanged {
45 rollout_id: RolloutId,
46 host_id: HostId,
47 from: HostRolloutState,
48 to: HostRolloutState,
49 at: DateTime<Utc>,
50 },
51 /// A wave's hosts all reached Soaked; planner is dispatching the next
52 /// wave. Drives `Converging → Active`.
53 WaveAdvanced {
54 rollout_id: RolloutId,
55 from_wave: u32,
56 to_wave: u32,
57 at: DateTime<Utc>,
58 },
59 /// Aggregate signal: all hosts in all waves Converged. Drives
60 /// `Converging → Terminal`. Emitted by the reducer itself (not by an
61 /// upstream worker) as a consequence of the last per-host `Converged`.
62 RolloutTerminal {
63 rollout_id: RolloutId,
64 at: DateTime<Utc>,
65 },
66 /// Channel-refs poll detected a successor ref. Both rollouts are
67 /// named in one event so the reducer can transition the predecessor
68 /// (→ Superseded) and open the successor in lockstep.
69 SuccessorOpened {
70 superseded_rollout_id: RolloutId,
71 successor_rollout_id: RolloutId,
72 at: DateTime<Utc>,
73 },
74 /// Retention threshold elapsed for a `Terminal | Superseded | Failed |
75 /// Reverted` rollout. Drives the terminal-set → `Pruned` transition.
76 RetentionExpired {
77 rollout_id: RolloutId,
78 at: DateTime<Utc>,
79 },
80 /// Operator-issued state override (currently rare; CLI wiring is a
81 /// future v0.2.x follow-up — RFC-0008 §13 + Phase 10 brief §9.3).
82 OperatorClearance {
83 rollout_id: RolloutId,
84 operator: String,
85 reason: String,
86 at: DateTime<Utc>,
87 },
88}
89
90impl RolloutEvent {
91 /// Static event-kind name for logging / error reporting.
92 pub fn kind(&self) -> &'static str {
93 match self {
94 RolloutEvent::RolloutOpened { .. } => "RolloutOpened",
95 RolloutEvent::HostJoined { .. } => "HostJoined",
96 RolloutEvent::HostStateChanged { .. } => "HostStateChanged",
97 RolloutEvent::WaveAdvanced { .. } => "WaveAdvanced",
98 RolloutEvent::RolloutTerminal { .. } => "RolloutTerminal",
99 RolloutEvent::SuccessorOpened { .. } => "SuccessorOpened",
100 RolloutEvent::RetentionExpired { .. } => "RetentionExpired",
101 RolloutEvent::OperatorClearance { .. } => "OperatorClearance",
102 }
103 }
104}