nixfleet_agent/activation/
mod.rs

1//! Activation pipeline (RFC-0005 §4). The runtime worker
2//! (`runtime/workers/activation.rs`) is the wire-layer entry point;
3//! this module owns the seven LOADBEARING operational steps the
4//! activation must preserve:
5//!
6//!   1. `realise.rs` — `nix-store --realise` forces fetch + signature
7//!      verification of the target closure with trust-failure detection
8//!      distinct from generic failure (SignatureMismatch outcome).
9//!   2. `profile.rs::set` — `nix-env --set` flips
10//!      `/nix/var/nix/profiles/system` to point at the target. Required
11//!      before fire because `switch-to-configuration` reads that profile
12//!      to derive the generation number it writes into bootloader entries.
13//!   3. Pre-switch basename read — captured for `verify_poll`'s
14//!      flip-to-unexpected detection (third basename = symlink got
15//!      switched to something OTHER than expected OR previous; caller
16//!      rolls back).
17//!   4. `linux::fire_switch` / `darwin::fire_switch` — per-OS detached
18//!      subprocess invocation. Linux uses `systemd-run --unit` (cgroup
19//!      isolation so agent SIGTERM cannot kill mid-switch); Darwin uses
20//!      `setsid()` to detach from agent's process group (launchd reload
21//!      sends SIGTERM to the whole group).
22//!   5. `verify_poll.rs` — polls `/run/current-system` until expected,
23//!      with three outcomes: Settled, Timeout, FlippedToUnexpected.
24//!      POLL_BUDGET (300s) locked to CP's DEFAULT_CONFIRM_DEADLINE_SECS
25//!      (360s) minus slack — exceeding splits state.
26//!   6. `profile::self_correct_profile` — recovers when the activation
27//!      script re-pointed the profile (some closures do this). Non-fatal
28//!      if self-correction itself fails (the live switch still happened).
29//!   7. `linux::detect_switch_inhibitors` — detects critical-component
30//!      swaps (dbus, systemd, kernel) that nixos-rebuild refuses to
31//!      live-switch. DeferredPendingReboot outcome triggers
32//!      `switch-to-configuration boot` (writes bootloader entries) so
33//!      the next reboot lands on the new generation.
34//!
35//! The legacy v0.1-shaped `activate(target: &EvaluatedTarget)` entry
36//! point + `comms::confirm()` post-activation report are NOT restored —
37//! they belonged to the pre-v0.2 architecture. The v0.2 entry is
38//! `runtime::workers::activation::handle_intent(ActivationIntent)`,
39//! which constructs an `ActivationTarget` from the intent and calls
40//! `activate_with(&DEFAULT_BACKEND, &target)`. Completion / failure
41//! flows back through the existing `Event::LocalActivation*` reducer
42//! events; CP reads from the outbound queue (no separate confirm HTTP
43//! call needed in v0.2).
44
45pub mod darwin;
46pub mod linux;
47pub mod pipeline;
48pub mod profile;
49pub mod realise;
50pub mod rollback;
51pub mod types;
52pub mod verify_poll;
53
54pub use pipeline::activate_with;
55pub use rollback::rollback_with;
56pub use types::{
57    ActivationBackend, ActivationOutcome, ActivationTarget, DEFAULT_BACKEND, DefaultBackend,
58    POLL_BUDGET, POLL_INTERVAL, RollbackOutcome,
59};
60
61/// High-level activate facade — production callers use `DEFAULT_BACKEND`.
62/// Test paths that need a stub backend call `activate_with` directly.
63pub async fn activate(target: &ActivationTarget) -> anyhow::Result<ActivationOutcome> {
64    activate_with(&DEFAULT_BACKEND, target).await
65}
66
67/// High-level rollback facade.
68pub async fn rollback() -> anyhow::Result<RollbackOutcome> {
69    rollback_with(&DEFAULT_BACKEND).await
70}