harmont_cli/orchestrator/state.rs
1//! Per-run state visible to host functions.
2//!
3//! The plan-1 host fns global `HostState` does not carry per-run
4//! context — the orchestrator does. Host fns that consult per-run
5//! state read it from this `OnceLock<Arc<OrchestratorState>>` that
6//! the orchestrator installs at the start of each run.
7
8// `clear()` is currently a no-op but is part of the lifecycle API
9// the orchestrator calls at end-of-run; flipping it to `const fn`
10// would break callers when we add an `OnceLock::take()`-like body
11// in a future plan.
12#![allow(clippy::missing_const_for_fn)]
13// `expect`/`panic!` on the OnceLock install is the documented panic
14// path for the "concurrent orchestrator runs" invariant; using `?`
15// or returning a Result would force every caller into error handling
16// for a programming-error case.
17#![allow(clippy::expect_used)]
18#![allow(clippy::panic)]
19
20use std::sync::{Arc, OnceLock};
21
22use uuid::Uuid;
23
24use crate::orchestrator::docker_client::DockerClient;
25
26use super::archive::ArchiveStore;
27use super::cancel::CancellationToken;
28use super::events::EventBus;
29
30/// Live state visible to every host fn while an orchestrator run is
31/// active.
32///
33/// Hosted via the [`current()`] / [`install()`] / [`clear()`] trio so
34/// host-fn implementations can read it without an extra
35/// argument-passing channel.
36#[derive(Debug)]
37pub struct OrchestratorState {
38 pub event_bus: Arc<EventBus>,
39 pub archives: ArchiveStore,
40 pub cancel: CancellationToken,
41 pub docker: DockerClient,
42 pub run_id: Uuid,
43}
44
45static CURRENT: OnceLock<Arc<OrchestratorState>> = OnceLock::new();
46
47/// Install per-run state for the duration of an orchestrator run.
48///
49/// # Panics
50///
51/// Panics if state is installed twice — the host runs one
52/// orchestrator at a time for plan 2.
53pub fn install(state: Arc<OrchestratorState>) {
54 assert!(
55 CURRENT.set(state).is_ok(),
56 "OrchestratorState already installed; concurrent orchestrator runs are not supported"
57 );
58}
59
60/// Clear the installed state. Idempotent (no-op when nothing was
61/// installed). Use at end-of-run.
62pub fn clear() {
63 // OnceLock has no take(); we leak the Arc on each run. The orchestrator
64 // is invoked once per process lifetime today, so this is fine.
65 // Long-running daemons that orchestrate would need a different shape.
66}
67
68/// Get a handle to the live state, if any.
69#[must_use]
70pub fn current() -> Option<Arc<OrchestratorState>> {
71 CURRENT.get().cloned()
72}