nixfleet_agent/activation/
types.rs

1//! Activation outcome types + `ActivationBackend` trait + cfg-selected default.
2//!
3//! Backend trait contract:
4//! - `is_switch_in_progress` is fail-open (false = no contender OR unknown).
5//! - `read_unit_exit_code` returns `None` rather than synthesising a 0.
6//! - `fire_*` are fire-and-forget: `Ok(None)` -> caller polls; `Ok(Some)` ->
7//!   fire-step failure, no poll; `Err` -> spawn-level I/O error only.
8
9use std::time::Duration;
10
11use anyhow::Result;
12
13/// Pipeline input. Carries the minimum the activation pipeline needs:
14/// the target closure hash (drives realise + set-profile +
15/// verify-poll) and the channel_ref (for tracing-only correlation
16/// with CP's rollout records).
17///
18/// Constructed from `runtime::wire::ActivationIntent` at the worker
19/// entry (`runtime::workers::activation::handle_intent`); shape is
20/// intentionally minimal so future wire-format evolutions don't
21/// ripple through the activation internals.
22#[derive(Debug, Clone)]
23pub struct ActivationTarget {
24    pub closure_hash: String,
25    pub channel_ref: String,
26}
27
28// LOADBEARING: 300s must stay inside CP's DEFAULT_CONFIRM_DEADLINE_SECS=360 - exceeding splits state.
29pub const POLL_BUDGET: Duration = Duration::from_secs(300);
30pub const POLL_INTERVAL: Duration = Duration::from_secs(2);
31
32#[derive(Debug)]
33pub enum ActivationOutcome {
34    FiredAndPolled,
35    RealiseFailed {
36        reason: String,
37    },
38    /// Distinct from RealiseFailed so dashboards can route trust violations.
39    SignatureMismatch {
40        closure_hash: String,
41        stderr_tail: String,
42    },
43    SwitchFailed {
44        phase: String,
45        exit_code: Option<i32>,
46    },
47    /// `/run/current-system` flipped to a basename that is neither expected
48    /// nor pre-switch - caller rolls back.
49    VerifyMismatch {
50        expected: String,
51        actual: String,
52    },
53    /// Profile flipped via `nix-env --set` but live switch was skipped because
54    /// activating component `component` (dbus, systemd, kernel, init) on a
55    /// running system is unsafe - nixos-rebuild refuses the same. New gen
56    /// activates on next boot.
57    DeferredPendingReboot {
58        component: String,
59    },
60}
61
62#[derive(Debug)]
63pub enum RollbackOutcome {
64    /// Rollback subprocess returned, `/run/current-system` settled on
65    /// `reverted_to_closure` (the basename of the post-rollback
66    /// symlink target, read by `verify_poll`). The worker uses this
67    /// value to emit `Event::LocalRollbackCompleted` which drives the
68    /// reducer `Failed → Reverted` transition and populates
69    /// `state.reverted_to`.
70    FiredAndPolled { reverted_to_closure: String },
71    Failed {
72        phase: String,
73        exit_code: Option<i32>,
74    },
75}
76
77impl RollbackOutcome {
78    pub fn success(&self) -> bool {
79        matches!(self, RollbackOutcome::FiredAndPolled { .. })
80    }
81    pub fn exit_code(&self) -> Option<i32> {
82        match self {
83            RollbackOutcome::Failed { exit_code, .. } => *exit_code,
84            RollbackOutcome::FiredAndPolled { .. } => None,
85        }
86    }
87    pub fn phase(&self) -> Option<&str> {
88        match self {
89            RollbackOutcome::Failed { phase, .. } => Some(phase.as_str()),
90            RollbackOutcome::FiredAndPolled { .. } => None,
91        }
92    }
93}
94
95#[cfg(target_os = "macos")]
96pub use super::darwin::DarwinBackend;
97#[cfg(target_os = "linux")]
98pub use super::linux::LinuxBackend;
99
100#[cfg(target_os = "linux")]
101pub type DefaultBackend = LinuxBackend;
102#[cfg(target_os = "macos")]
103pub type DefaultBackend = DarwinBackend;
104
105#[cfg(target_os = "linux")]
106pub const DEFAULT_BACKEND: DefaultBackend = LinuxBackend;
107#[cfg(target_os = "macos")]
108pub const DEFAULT_BACKEND: DefaultBackend = DarwinBackend;
109
110pub trait ActivationBackend: Send + Sync {
111    fn is_switch_in_progress(&self) -> impl std::future::Future<Output = bool> + Send;
112    fn read_unit_exit_code(
113        &self,
114        unit_name: &str,
115    ) -> impl std::future::Future<Output = Option<i32>> + Send;
116    fn fire_switch(
117        &self,
118        target: &ActivationTarget,
119        store_path: &str,
120    ) -> impl std::future::Future<Output = Result<Option<ActivationOutcome>>> + Send;
121    fn fire_rollback(
122        &self,
123        target_basename: &str,
124    ) -> impl std::future::Future<Output = Result<Option<RollbackOutcome>>> + Send;
125}